diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..ef05ebc2a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,2 @@
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..1d61cb934
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+####-*.patch
+*.pyc
+*.swp
+build/
+dist/
+*.egg/
+contrib/pyinstaller/
+Electrum.egg-info/
+contrib/build-wine/LICENSE
+#electrum/gui/qt/icons_rc.py
+locale/
+.devlocaltmp/
+*_trial_temp
+packages
+env/
+.tox/
+.buildozer/
+bin/
+/app.fil
+
+# tox files
+.cache/
+.coverage
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..5a0f914f1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "contrib/deterministic-build/electrum-icons"]
+ path = contrib/deterministic-build/electrum-icons
+ url = https://github.com/spesmilo/electrum-icons
+[submodule "contrib/deterministic-build/electrum-locale"]
+ path = contrib/deterministic-build/electrum-locale
+ url = https://github.com/spesmilo/electrum-locale
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..6f28247b7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,58 @@
+sudo: true
+dist: xenial
+language: python
+python:
+ - 3.5
+ - 3.6
+ - 3.7
+addons:
+ apt:
+ sources:
+ - sourceline: 'ppa:tah83/secp256k1'
+ packages:
+ - libsecp256k1-0
+install:
+ - pip install -r contrib/requirements/requirements-travis.txt
+cache:
+ - pip: true
+ - directories:
+ - /tmp/electrum-build
+script:
+ - tox
+after_success:
+ - if [ "$TRAVIS_BRANCH" = "master" ]; then pip install pycurl requests && contrib/make_locale; fi
+ - coveralls
+jobs:
+ include:
+ - stage: binary builds
+ sudo: true
+ language: c
+ python: false
+ env:
+ - TARGET_OS=Windows
+ services:
+ - docker
+ install:
+ - sudo docker build --no-cache -t electrum-wine-builder-img ./contrib/build-wine/docker/
+ script:
+ - sudo docker run --name electrum-wine-builder-cont -v $PWD:/opt/wine64/drive_c/electrum --rm --workdir /opt/wine64/drive_c/electrum/contrib/build-wine electrum-wine-builder-img ./build.sh
+ after_success: true
+ - os: osx
+ language: c
+ env:
+ - TARGET_OS=macOS
+ python: false
+ install:
+ - git fetch --all --tags
+ - git fetch origin --unshallow
+ script: ./contrib/build-osx/make_osx
+ after_script: ls -lah dist && md5 dist/*
+ after_success: true
+ - stage: release check
+ install:
+ - git fetch --all --tags
+ - git fetch origin --unshallow
+ script:
+ - ./contrib/deterministic-build/check_submodules.sh
+ after_success: true
+ if: tag IS present
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 000000000..9cff06784
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,12 @@
+ThomasV - Creator and maintainer.
+Animazing / Tachikoma - Styled the new GUI. Mac version.
+Azelphur - GUI stuff.
+Coblee - Alternate coin support and py2app support.
+Deafboy - Ubuntu packages.
+EagleTM - Bugfixes.
+ErebusBat - Mac distribution.
+Genjix - Porting pro-mode functionality to lite-gui and worked on server
+Slush - Work on the server. Designed the original Stratum spec.
+Julian Toash (Tuxavant) - Various fixes to the client.
+rdymac - Website and translations.
+kyuupichan - Miscellaneous.
\ No newline at end of file
diff --git a/Info.plist b/Info.plist
new file mode 100644
index 000000000..e914dea11
--- /dev/null
+++ b/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ bitcore
+ CFBundleURLSchemes
+
+ bitcore
+
+
+
+ LSArchitecturePriority
+
+ x86_64
+ i386
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..b8bb97185
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 000000000..a46190b80
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,18 @@
+include LICENSE RELEASE-NOTES AUTHORS
+include README.rst
+include electrum.conf.sample
+include electrum.desktop
+include *.py
+include run_electrum
+include contrib/requirements/requirements.txt
+include contrib/requirements/requirements-hw.txt
+recursive-include packages *.py
+recursive-include packages cacert.pem
+include icons.qrc
+graft icons
+
+graft electrum
+prune electrum/tests
+
+global-exclude __pycache__
+global-exclude *.py[co]
diff --git a/README.rst b/README.rst
new file mode 100644
index 000000000..553f94325
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,115 @@
+Electrum - Lightweight Bitcore client
+=====================================
+
+::
+
+ Licence: MIT Licence
+ Author: Thomas Voegtlin
+ Language: Python
+ Homepage: https://electrum.org/
+
+
+.. image:: https://travis-ci.org/spesmilo/electrum.svg?branch=master
+ :target: https://travis-ci.org/spesmilo/electrum
+ :alt: Build Status
+.. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master
+ :target: https://coveralls.io/github/spesmilo/electrum?branch=master
+ :alt: Test coverage statistics
+.. image:: https://d322cqt584bo4o.cloudfront.net/electrum/localized.svg
+ :target: https://crowdin.com/project/electrum
+ :alt: Help translate Electrum online
+
+
+
+
+
+Getting started
+===============
+
+Electrum is a pure python application. If you want to use the
+Qt interface, install the Qt dependencies::
+
+ sudo apt-get install python3-pyqt5
+
+If you downloaded the official package (tar.gz), you can run
+Electrum from its root directory, without installing it on your
+system; all the python dependencies are included in the 'packages'
+directory. To run Electrum from its root directory, just do::
+
+ ./run_electrum
+
+You can also install Electrum on your system, by running this command::
+
+ sudo apt-get install python3-setuptools
+ pip3 install .[fast]
+
+This will download and install the Python dependencies used by
+Electrum, instead of using the 'packages' directory.
+The 'fast' extra contains some optional dependencies that we think
+are often useful but they are not strictly needed.
+
+If you cloned the git repository, you need to compile extra files
+before you can run Electrum. Read the next section, "Development
+Version".
+
+
+
+Development version
+===================
+
+Check out the code from GitHub::
+
+ git clone git://github.com/spesmilo/electrum.git
+ cd electrum
+
+Run install (this should install dependencies)::
+
+ pip3 install .[fast]
+
+Render the SVG icons to PNGs (optional)::
+
+ for i in lock unlock confirmed status_lagging status_disconnected status_connected_proxy status_connected status_waiting preferences; do convert -background none icons/$i.svg icons/$i.png; done
+
+Compile the icons file for Qt::
+
+ sudo apt-get install pyqt5-dev-tools
+ pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py
+
+Compile the protobuf description file::
+
+ sudo apt-get install protobuf-compiler
+ protoc --proto_path=electrum --python_out=electrum electrum/paymentrequest.proto
+
+Create translations (optional)::
+
+ sudo apt-get install python-requests gettext
+ ./contrib/make_locale
+
+
+
+
+Creating Binaries
+=================
+
+
+To create binaries, create the 'packages' directory::
+
+ ./contrib/make_packages
+
+This directory contains the python dependencies used by Electrum.
+
+Mac OS X / macOS
+--------
+
+See `contrib/build-osx/`.
+
+Windows
+-------
+
+See `contrib/build-wine/`.
+
+
+Android
+-------
+
+See `electrum/gui/kivy/Readme.md` file.
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
new file mode 100644
index 000000000..74b687747
--- /dev/null
+++ b/RELEASE-NOTES
@@ -0,0 +1,1061 @@
+# Release 3.2.3 - (September 3, 2018)
+
+ * hardware wallet: the Safe-T mini from Archos is now supported.
+ * hardware wallet: the Coldcard from Coinkite is now supported.
+ * BIP39 seeds: if a seed extension (aka passphrase) contained
+ multiple consecutive whitespaces or leading/trailing whitespaces
+ then the derived addresses were not following spec. This has been
+ fixed, and affected should move their coins. The wizard will show a
+ warning in this case. (#4566)
+ * Revealer: the PRNG used has been changed (#4649)
+ * fix Linux distributables: 'typing' was not bundled, needed for python 3.4
+ * fix #4626: fix spending from segwit multisig wallets involving a Trezor
+ cosigner when using a custom derivation path
+ * fix #4491: on Android, if user had set "uBTC" as base unit, app crashed
+ * fix #4497: on Android, paying bip70 invoices from cold start did not work
+ * Several other minor bugfixes and usability improvements.
+
+
+# Release 3.2.2 - (July 2nd, 2018)
+
+ * Fix DNS resolution on Windows
+ * Fix websocket bug in daemon
+
+
+# Release 3.2.1 - (July 1st, 2018)
+
+ * fix Windows binaries: due to build process changes, the locale files
+ were not included; the language could not be changed from English
+ * fix Linux distributables: wordlists were not included (#4475)
+
+
+# Release 3.2.0 - Satoshi's Vision (June 30, 2018)
+
+ * If present, libsecp256k1 is used to speed up elliptic curve
+ operations. The library is bundled in the Windows, MacOS, and
+ Android binaries. On Linux, it needs to be installed separately.
+ * Two-factor authentication is available on Android. Note that this
+ will only provide additional security if one time passwords are
+ generated on a separate device.
+ * Semi-automated crash reporting is implemented for Android.
+ * Transactions that are dropped from the mempool are kept in the
+ wallet as 'local', and can be rebroadcast. Previously these
+ transactions were deleted from the wallet.
+ * The scriptSig and witness part of transaction inputs are no longer
+ parsed, unless actually needed. The wallet will no longer display
+ 'from' addresses corresponding to transaction inputs, except for
+ its own inputs.
+ * The partial transaction format has been incompatibly changed. This
+ was needed as for partial transactions the scriptSig/witness has to
+ be parsed, but for signed transactions we did not want to do the
+ parsing. Users should make sure that all instances of Electrum
+ they use to co-sign or offline sign, are updated together.
+ * Signing of partial transactions created with online imported
+ addresses wallets now supports significantly more
+ setups. Previously only online p2pkh address + offline WIF was
+ supported. Now the following setups are all supported:
+ - online {p2pkh, p2wpkh-p2sh, p2wpkh} address + offline WIF,
+ - online {p2pkh, p2wpkh-p2sh, p2wpkh} address + offline seed/xprv,
+ - online {p2sh, p2wsh-p2sh, p2wsh}-multisig address + offline seeds/xprvs
+ (potentially distributed among several different machines)
+ Note that for the online address + offline HD secret case, you need
+ the offline wallet to recognize the address (i.e. within gap
+ limit). Having an xpub on the online machine is still the
+ recommended setup, as this allows the online machine to generate
+ new addresses on demand.
+ * Segwit multisig for bip39 and hardware wallets is now enabled.
+ (both p2wsh-p2sh and native p2wsh)
+ * Ledger: offline signing for segwit inputs (#3302) This has already
+ worked for Trezor and Digital Bitbox. Offline segwit signing can be
+ combined with online imported addresses wallets.
+ * Added Revealer plugin. ( https://revealer.cc ) Revealer is a seed
+ phrase back-up solution. It allows you to create a cold, analog,
+ multi-factor backup of your wallet seeds, or of any arbitrary
+ secret. The Revealer utilizes a transparent plastic visual one time
+ pad.
+ * Fractional fee rates: the Qt GUI now displays fee rates with 0.1
+ sat/byte precision, and also allows this same resolution in the
+ Send tab.
+ * Hardware wallets: a "show address" button is now displayed in the
+ Receive tab of the Qt GUI. (#4316)
+ * Trezor One: implemented advanced/matrix recovery (#4329)
+ * Qt/Kivy: added "sat" as optional base unit.
+ * Kivy GUI: significant performance improvements when displaying
+ history and address list of large wallets; and transaction dialog
+ of large transactions.
+ * Windows: use dnspython to resolve dns instead of socket.getaddrinfo
+ (#4422)
+ * Importing minikeys: use uncompressed pubkey instead of compressed
+ (#4384)
+ * SPV proofs: check inner nodes not to be valid transactions (#4436)
+ * Qt GUI: there is now an optional "dark" theme (#4461)
+ * Several other minor bugfixes and usability improvements.
+
+
+# Release 3.1.3 - (April 16, 2018)
+
+ * Qt GUI: seed word auto-complete during restore
+ * Android: fix some crashes
+ * performance improvements (wallet, and Qt GUI)
+ * hardware wallets: show debug message during device scan
+ * Digital Bitbox: enabled BIP84 (p2wpkh) wallet creation
+ * add regtest support (via --regtest flag)
+ * other minor bugfixes and usability improvements
+
+# Release 3.1.2 - (March 28, 2018)
+
+ * Kivy/android: request PIN on startup
+ * Improve OSX build process
+ * Fix various bugs with hardware wallets
+ * Other minor bugfixes
+
+# Release 3.1.1 - (March 12, 2018)
+
+ * fix #4031: Trezor T support
+ * partial fix #4060: proxy and hardware wallet can't be used together
+ * fix #4039: can't set address labels
+ * fix crash related to coinbase transactions
+ * MacOS: use internal graphics card
+ * fix openalias related crashes
+ * speed-up capital gains calculations
+ * hw wallet encryption: re-prompt for passphrase if incorrect
+ * other minor fixes.
+
+
+
+# Release 3.1.0 - (March 5, 2018)
+
+ * Memory-pool based fee estimation. Dynamic fees can target a desired
+ depth in the memory pool. This feature is optional, and ETA-based
+ estimates from Bitcoin Core are still available. Note that miners
+ could exploit this feature, if they conspired and filled the memory
+ pool with expensive transactions that never get mined. However,
+ since the Electrum client already trusts an Electrum server with
+ fee estimates, activating this feature does not introduce any new
+ vulnerability. In addition, the client uses a hard threshold to
+ protect itself from servers sending excessive fee estimates. In
+ practice, ETA-based estimates have resulted in sticky fees, and
+ caused many users to overpay for transactions. Advanced users tend
+ to visit (and trust) websites that display memory-pool data in
+ order to set their fees.
+ * Capital gains: For each outgoing transaction, the difference
+ between the acquisition and liquidation prices of outgoing coins is
+ displayed in the wallet history. By default, historical exchange
+ rates are used to compute acquisition and liquidation prices. These
+ values can also be entered manually, in order to match the actual
+ price realized by the user. The order of liquidation of coins is
+ the natural order defined by the blockchain; this results in
+ capital gain values that are invariant to changes in the set of
+ addresses that are in the wallet. Any other ordering strategy (such
+ as FIFO, LIFO) would result in capital gain values that depend on
+ the presence of other addresses in the wallet.
+ * Local transactions: Transactions can be saved in the wallet without
+ being broadcast. The inputs of local transactions are considered as
+ spent, and their change outputs can be re-used in subsequent
+ transactions. This can be combined with cold storage, in order to
+ create several transactions before broadcasting them. Outgoing
+ transactions that have been removed from the memory pool are also
+ saved in the wallet, and can be broadcast again.
+ * Checkpoints: The initial download of a headers file was replaced
+ with hardcoded checkpoints. The wallet uses one checkpoint per
+ retargeting period. The headers for a retargeting period are
+ downloaded only if transactions need to be verified in this period.
+ * The 'privacy' and 'priority' coin selection policies have been
+ merged into one. Previously, the 'privacy' policy has been unusable
+ because it was was not prioritizing confirmed coins. The new policy
+ is similar to 'privacy', except that it de-prioritizes addresses
+ that have unconfirmed coins.
+ * The 'Send' tab of the Qt GUI displays how transaction fees are
+ computed from transaction size.
+ * The wallet history can be filtered by time interval.
+ * Replace-by-fee is enabled by default. Note that this might cause
+ some issues with wallets that do not display RBF transactions until
+ they are confirmed.
+ * Watching-only wallets and hardware wallets can be encrypted.
+ * Semi-automated crash reporting
+ * The SSL checkbox option was removed from the GUI.
+ * The Trezor T hardware wallet is now supported.
+ * BIP84: native segwit p2wpkh scripts for bip39 seeds and hardware
+ wallets can now be created when specifying a BIP84 derivation
+ path. This is usable with Trezor and Ledger.
+ * Windows: the binaries now include ZBar, and QR code scanning should work.
+ * The Wallet Import Format (WIF) for private keys that was extended in 3.0
+ is changed. Keys in the previous format can be imported, compatibility
+ is maintained. Newly exported keys will be serialized as
+ "script_type:original_wif_format_key".
+ * BIP32 master keys for testnet once again have different version bytes than
+ on mainnet. For the mainnet prefixes {x,y,Y,z,Z}|{pub,prv}, the
+ corresponding testnet prefixes are {t,u,U,v,V}|{pub,prv}.
+ More details and exact version bytes are specified at:
+ https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
+ Note that due to this change, testnet wallet files created with previous
+ versions of Electrum must be considered broken, and they need to be
+ recreated from seed words.
+ * A new version of the Electrum protocol is required by the client
+ (version 1.2). Servers using older versions of the protocol will
+ not be displayed in the GUI.
+
+
+# Release 3.0.6 :
+ * Fix transaction parsing bug #3788
+
+# Release 3.0.5 : (Security update)
+
+This is a follow-up to the 3.0.4 release, which did not completely fix
+issue #3374. Users should upgrade to 3.0.5.
+
+ * The JSONRPC interface is password protected
+ * JSONRPC commands are disabled if the GUI is running, except 'ping',
+ which is used to determine if a GUI is already running
+
+
+# Release 3.0.4 : (Security update)
+
+ * Fix a vulnerability caused by Cross-Origin Resource Sharing (CORS)
+ in the JSONRPC interface. Previous versions of Electrum are
+ vulnerable to port scanning and deanonimization attacks from
+ malicious websites. Wallets that are not password-protected are
+ vulnerable to theft.
+ * Bundle QR scanner with Android app
+ * Minor bug fixes
+
+# Release 3.0.3
+ * Qt GUI: sweeping now uses the Send tab, allowing fees to be set
+ * Windows: if using the installer binary, there is now a separate shortcut
+ for "Electrum Testnet"
+ * Digital Bitbox: added suport for p2sh-segwit
+ * OS notifications for incoming transactions
+ * better transaction size estimation:
+ - fees for segwit txns were somewhat underestimated (#3347)
+ - some multisig txns were underestimated
+ - handle uncompressed pubkeys
+ * fix #3321: testnet for Windows binaries
+ * fix #3264: Ledger/dbb signing on some platforms
+ * fix #3407: KeepKey sending to p2sh output
+ * other minor fixes and usability improvements
+
+# Release 3.0.2
+ * Android: replace requests tab with address tab, with access to
+ private keys
+ * sweeping minikeys: search for both compressed and uncompressed
+ pubkeys
+ * fix wizard crash when attempting to reset Google Authenticator
+ * fix #3248: fix Ledger+segwit signing
+ * fix #3262: fix SSL payment request signing
+ * other minor fixes.
+
+# Release 3.0.1
+ * minor bug and usability fixes
+
+# Release 3.0 - Uncanny Valley (November 1st, 2017)
+
+ * The project was migrated to Python3 and Qt5. Python2 is no longer
+ supported. If you cloned the source repository, you will need to
+ run "python3 setup.py install" in order to install the new
+ dependencies.
+
+ * Segwit support:
+
+ - Native segwit scripts are supported using a new type of
+ seed. The version number for segwit seeds is 0x100. The install
+ wizard will not create segwit seeds by default; users must
+ opt-in with the segwit option.
+
+ - Native segwit scripts are represented using bech32 addresses,
+ following BIP173. Please note that BIP173 is still in draft
+ status, and that other wallets/websites may not support
+ it. Thus, you should keep a non-segwit wallet in order to be
+ able to receive bitcoins during the transition period. If BIP173
+ ends up being rejected or substantially modified, your wallet
+ may have to be restored from seed. This will not affect funds
+ sent to bech32 addresses, and it will not affect the capacity of
+ Electrum to spend these funds.
+
+ - Segwit scripts embedded in p2sh are supported with hardware
+ wallets or bip39 seeds. To create a segwit-in-p2sh wallet,
+ trezor/ledger users will need to enter a BIP49 derivation path.
+
+ - The BIP32 master keys of segwit wallets are serialized using new
+ version numbers. The new version numbers encode the script type,
+ and they result in the following prefixes:
+
+ * xpub/xprv : p2pkh or p2sh
+ * ypub/yprv : p2wpkh-in-p2sh
+ * Ypub/Yprv : p2wsh-in-p2sh
+ * zpub/zprv : p2wpkh
+ * Zpub/Zprv : p2wsh
+
+ These values are identical for mainnet and testnet; tpub/tprv
+ prefixes are no longer used in testnet wallets.
+
+ - The Wallet Import Format (WIF) is similarly extended for segwit
+ scripts. After a base58-encoded key is decoded to binary, its
+ first byte encodes the script type:
+
+ * 128 + 0: p2pkh
+ * 128 + 1: p2wpkh
+ * 128 + 2: p2wpkh-in-p2sh
+ * 128 + 5: p2sh
+ * 128 + 6: p2wsh
+ * 128 + 7: p2wsh-in-p2sh
+
+ The distinction between p2sh and p2pkh in private key means that
+ it is not possible to import a p2sh private key and associate it
+ to a p2pkh address.
+
+ * A new version of the Electrum protocol is required by the client
+ (version 1.1). Servers using older versions of the protocol will
+ not be displayed in the GUI.
+
+ * By default, transactions are time-locked to the height of the
+ current block. Other values of locktime may be passed using the
+ command line.
+
+
+# Release 2.9.3
+ * fix configuration file issue #2719
+ * fix ledger signing of non-RBF transactions
+ * disable 'spend confirmed only' option by default
+
+# Release 2.9.2
+ * force headers download if headers file is corrupted
+ * add websocket to windows builds
+
+# Release 2.9.1
+ * fix initial headers download
+ * validate contacts on import
+ * command-line option for locktime
+
+# Release 2.9 - Independence (July 27th, 2017)
+ * Multiple Chain Validation: Electrum will download and validate
+ block headers sent by servers that may follow different branches
+ of a fork in the Bitcoin blockchain. Instead of a linear sequence,
+ block headers are organized in a tree structure. Branching points
+ are located efficiently using binary search. The purpose of MCV is
+ to detect and handle blockchain forks that are invisible to the
+ classical SPV model.
+ * The desired branch of a blockchain fork can be selected using the
+ network dialog. Branches are identified by the hash and height of
+ the diverging block. Coin splitting is possible using RBF
+ transaction (a tutorial will be added).
+ * Multibit support: If the user enters a BIP39 seed (or uses a
+ hardware wallet), the full derivation path is configurable in the
+ install wizard.
+ * Option to send only confirmed coins
+ * Qt GUI:
+ - Network dialog uses tabs and gets updated by network events.
+ - The gui tabs use icons
+ * Kivy GUI:
+ - separation between network dialog and wallet settings dialog.
+ - option for manual server entry
+ - proxy configuration
+ * Daemon: The wallet password can be passed as parameter to the
+ JSONRPC API.
+ * Various other bugfixes and improvements.
+
+
+# Release 2.8.3
+ * Fix crash on reading older wallet formats.
+ * TrustedCoin: remove pay-per-tx option
+
+# Release 2.8.2
+ * show paid invoices in history tab
+ * improve CPFP dialog
+ * fixes for trezor, keepkey
+ * other minor bugfixes
+
+# Release 2.8.1
+ * fix Digital Bitbox plugin
+ * fix daemon jsonrpc
+ * fix trustedcoin wallet creation
+ * other minor bugfixes
+
+# Release 2.8.0 (March 9, 2017)
+ * Wallet file encryption using ECIES: A keypair is derived from the
+ wallet password. Once the wallet is decrypted, only the public key
+ is retained in memory, in order to save the encrypted file.
+ * The daemon requires wallets to be explicitly loaded before
+ commands can use them. Wallets can be loaded using: 'electrum
+ daemon load_wallet [-w path]'. This command will require a
+ password if the wallet is encrypted.
+ * Invoices and contacts are stored in the wallet file and are no
+ longer shared between wallets. Previously created invoices and
+ contacts files may be imported from the menu.
+ * Fees improvements:
+ - Dynamic fees are enabled by default.
+ - Child Pays For Parent (CPFP) dialog in the GUI.
+ - RBF is automatically proposed for low fee transactions.
+ * Support for Segregated Witness (testnet only).
+ * Support for Digital Bitbox hardware wallet.
+ * The GUI shows a blue icon when connected using a proxy.
+
+# Release 2.7.18
+ * enforce https on exchange rate APIs
+ * use hardcoded list of exchanges
+ * move 'Freeze' menu to Coins (utxo) tab
+ * various bugfixes
+
+# Release 2.7.17
+ * fix a few minor regressions in the Qt GUI
+
+# Release 2.7.16
+ * add Testnet support (fix #541)
+ * allow daemon to be launched in the foreground (fix #1873)
+ * Qt: use separate tabs for addresses and UTXOs
+ * Qt: update fee slider with a network callback
+ * Ledger: new ui and mobile 2fa validation (neocogent)
+
+# Release 2.7.15
+ * Use fee slider for both static and dynamic fees.
+ * Add fee slider to RBF dialog (fix #2083).
+ * Simplify fee preferences.
+ * Critical: Fix password update issue (#2097). This bug prevents
+ password updates in multisig and 2FA wallets. It may also cause
+ wallet corruption if the wallet contains several master private
+ keys (such as 2FA wallets that have been restored from
+ seed). Affected wallets will need to be restored again.
+
+# Release 2.7.14
+ * Merge exchange_rate plugin with main code
+ * Faster synchronization and transaction creation
+ * Fix bugs #2096, #2016
+
+# Release 2.7.13
+ * fix message signing with imported keys
+ * add size to transaction details window
+ * move plot plugin to main code
+ * minor bugfixes
+
+# Release 2.7.12
+ various bugfixes
+
+# Release 2.7.11
+ * fix offline signing (issue #195)
+ * fix android crashes caused by threads
+
+# Release 2.7.10
+ * various fixes for hardware wallets
+ * improve fee bumping
+ * separate sign and broadcast buttons in Qt tx dialog
+ * allow spaces in private keys
+
+# Release 2.7.9
+ * Fix a bug with the ordering of pubkeys in recent multisig wallets.
+ Affected wallets will regenerate their public keys when opened for
+ the first time. This bug does not affect address generation.
+ * Fix hardware wallet issues #1975, #1976
+
+# Release 2.7.8
+ * Fix a bug with fee bumping
+ * Fix crash when parsing request (issue #1969)
+
+# Release 2.7.7
+ * Fix utf8 encoding bug with old wallet seeds (issue #1967)
+ * Fix delete request from menu (isue #1968)
+
+# Release 2.7.6
+ * Fixes a critical bug with imported private keys (issue #1966). Keys
+ imported in Electrum 2.7.x were not encrypted, even if the wallet
+ had a password. If you imported private keys using Electrum 2.7.x,
+ you will need to import those keys again. If you imported keys in
+ 2.6 and converted with 2.7.x, you don't need to do anything, but
+ you still need to upgrade in order to be able to spend.
+ * Wizard: Hide seed options in a popup dialog.
+
+# Release 2.7.5
+ * Add number of confirmations to request status. (issue #1757)
+ * In the GUI, refer to passphrase as 'seed extension'.
+ * Fix bug with utf8 encoded passphrases.
+ * Kivy wizard: add a dialog for seed options.
+ * Kivy wizard: add current word to suggestions, because some users
+ don't see the space key.
+
+# Release 2.7.4
+ * Fix private key import in wizard
+ * Fix Ledger display (issue #1961)
+ * Fix old watching-only wallets (issue #1959)
+ * Fix Android compatibility (issue #1947)
+
+# Release 2.7.3
+ * fix Trezor and Keepkey support in Windows builds
+ * fix sweep private key dialog
+ * minor fixes: #1958, #1959
+
+# Release 2.7.2
+ * fix bug in password update (issue #1954)
+ * fix fee slider (issue #1953)
+
+# Release 2.7.1
+ * fix wizard crash with old seeds
+ * fix issue #1948: fee slider
+
+# Release 2.7.0 (Oct 2 2016)
+
+ * The wallet file format has been upgraded. This upgrade is not
+ backward compatible, which means that a wallet upgraded to the 2.7
+ format will not be readable by earlier versions of
+ Electrum. Multiple accounts inside the same wallet are not
+ supported in the new format; the Qt GUI will propose to split any
+ wallet that has several accounts. Make sure that you have saved
+ your seed phrase before you upgrade Electrum.
+ * This version introduces a separation between wallets types and
+ keystores types. 'Wallet type' defines the type of Bitcoin contract
+ used in the wallet, while 'keystore type' refers to the method used
+ to store private keys. Therefore, so-called 'hardware wallets' will
+ be referred to as 'hardware keystores'.
+ * Hardware keystores:
+ - The Ledger Nano S is supported.
+ - Hardware keystores can be used as cosigners in multi-signature
+ wallets.
+ - Multiple hardware cosigners can be used in the same multisig
+ wallet. One icon per keystore is displayed in the satus bar. Each
+ connected device will co-sign the transaction.
+ * Replace-By-Fee: RBF transactions are supported in both Qt and
+ Android. A warning is displayed in the history for transactions
+ that are replaceable, have unconfirmed parents, or that have very
+ low fees.
+ * Dynamic fees: Dynamic fees are enabled by default. A slider allows
+ the user to select the expected confirmation time of their
+ transaction. The expected confirmation times of incoming
+ transactions is also displayed in the history.
+ * The install wizards of Qt and Kivy have been unified.
+ * Qt GUI (Desktop):
+ - A fee slider is visible in the in send tab
+ - The Address tab is hidden by default, can be shown with Ctrl-A
+ - UTXOs are displayed in the Address tab
+ * Kivy GUI (Android):
+ - The GUI displays the complete transaction history.
+ - Multisig wallets are supported.
+ - Wallets can be created and deleted in the GUI.
+ * Seed phrases can be extended with a user-chosen passphrase. The
+ length of seed phrases is standardized to 12 words, using 132 bits
+ of entropy (including 2FA seeds). In the wizard, the type of the
+ seed is displayed in the seed input dialog.
+ * TrustedCoin users can request a reset of their Google Authenticator
+ account, if they still have their seed.
+
+
+# Release 2.6.4 (bugfixes)
+ * fix coinchooser bug (#1703)
+ * fix daemon JSONRPC (#1731)
+ * fix command-line broadcast (#1728)
+ * QT: add colors to labels
+
+# Release 2.6.3 (bugfixes)
+ * fix command line parsing of transactions
+ * fix signtransaction --privkey (#1715)
+
+# Release 2.6.2 (bugfixes)
+ * fix Trustedcoin restore from seed (bug #1704)
+ * small improvements to kivy GUI
+
+# Release 2.6.1 (bugfixes)
+ * fix broadcast command (bug #1688)
+ * fix tx dialog (bug #1690)
+ * kivy: support old-type seed phrases in wizard
+
+# Release 2.6
+ * The source code is relicensed under the MIT Licence
+ * First official release of the Kivy GUI, with android APK
+ * The old 'android' and 'gtk' GUIs are deprecated
+ * Separation between plugins and GUIs
+ * The command line uses jsonrpc to communicate with the daemon
+ * New command: 'notify
'
+ * Alternative coin selection policy, designed to help preserve user
+ privacy. Enable it by setting the Coin Selection preference to
+ Privacy.
+ * The install wizard has been rewritten and improved
+ * Support minikeys as used in Casascius coins for private key import
+ and sweeping
+ * Much improved support for TREZOR and KeepKey devices:
+ - full device information display
+ - initialize a new or wiped device in 4 ways:
+ 1) device generates a new wallet
+ 2) you enter a seed
+ 3) you enter a BIP39 mnemonic to generate the seed
+ 4) you enter a master private key
+ - KeepKey secure seed recovery (KeepKey only)
+ - change / set / disable PIN
+ - set homescreen (TREZOR only)
+ - set a session timeout. Once a session has timed out, further use
+ of the device requires your PIN and passhphrase to be re-entered
+ - enable / disable passphrases
+ - device wipe
+ - multiple device support
+
+# Release 2.5.4
+ * increase MIN_RELAY_TX_FEE to avoid dust transactions
+
+# Release 2.5.3 (bugfixes)
+ * installwizard: do not allow direct copy-paste of the seed
+ * installwizard: fix bug #1531 (starting offline)
+
+# Release 2.5.2 (bugfixes)
+ * fix bug #1513 (client tries to broadcast transaction while not connected)
+ * fix synchronization bug (#1520)
+ * fix command line bug (#1494)
+ * fixes for exchange rate plugin
+
+# Release 2.5.1 (bugfixes)
+ * signatures in transactions were still using the old class
+ * make sure that setup.py uses python2
+ * fix wizard crash with trustedcoin plugin
+ * fix socket infinite loop
+ * fix history bug #1479
+
+# Release 2.5
+ * Low-S values are used in signatures (BIP 62).
+ * The Kivy GUI has been merged into master.
+ * The Qt GUI supports multiple windows in the same process. When a
+ new Electrum instance is started, it checks for an already running
+ Electrum process, and connects to it.
+ * The network layer uses select(), so all server communication is
+ handled by a single thread. Moreover, the synchronizer, verifier,
+ and exchange rate plugin now run as separate jobs within the
+ networking thread instead of as their own threads.
+ * Plugins are revamped, particularly the exchange rate plugin.
+
+# Release 2.4.4
+ * Fix bug with TrustedCoin plugin
+
+# Release 2.4.3
+ * Support for KeepKey hardware wallet
+ * Simplified Chinese wordlist
+ * Minor bugfixes and GUI tweaks
+
+# Release 2.4.2
+ * Command line can read arguments from stdin (pipe)
+ * Speedup fee computation for large transactions
+ * Various bugfixes
+
+# Release 2.4.1
+ * Use ssl.PROTOCOL_TLSv1
+ * Fix DNSSEC issues with ECDSA signatures
+ * Replace TLSLite dependency with minimal RSA implementation
+ * Dynamic Fees: using estimatefee value returned by server
+ * Various GUI improvements
+
+# Release 2.4
+ * Payment to DNS names storing a Bitcoin addresses (OpenAlias) is
+ supported directly, without activating a plugin. The verification
+ uses DNSSEC.
+ * The DNSSEC verification code was rewritten. The previous code,
+ which was part of the OpenAlias plugin, is vulnerable and should
+ not be trusted (Electrum 2.0 to 2.3).
+ * Payment requests can be signed using Bitcoin addresses stored
+ in DNS (OpenAlias). The identity of the requestor is verified using
+ DNSSEC.
+ * Payment requests signed with OpenAlias keys can be shared as
+ bitcoin: URIs, if they are simple (a single address-type
+ output). The BIP21 URI scheme is extended with 'name', 'sig',
+ 'time', 'exp'.
+ * Arbitrary m-of-n multisig wallets are supported (n<=15).
+ * Multisig transactions can be signed with TREZOR. When you create
+ the multisig wallet, just enter the xpub of your existing TREZOR
+ wallet.
+ * Transaction fees set manually in the GUI are retained, including
+ when the user uses the '!' shortcut.
+ * New 'email' plugin, that enables sending and receiving payment
+ requests by email.
+ * The daemon supports Websocket notifications of payments.
+
+# Release 2.3.3
+ * fix proxy settings (issue #1309)
+ * improvements to the transaction dialog:
+ - request password after showing transaction
+ - show change addresses in yellow color
+
+# Release 2.3.2
+ * minor bugfixes
+ * updated ledger plugin
+ * sort inputs/outputs lexicographically (BIP-LI01)
+
+# Release 2.3.1
+ * patch a bug with payment requests
+
+# Release 2.3
+ * Improved logic for the network layer.
+ * More efficient coin selection. Spend oldest coins first, and
+ minimize the number of transaction inputs.
+ * Plugins are loaded independently of the GUI. As a result, Openalias,
+ TrustedCoin and TREZOR wallets can be used with the command
+ line. Example: 'electrum payto '
+ * The command line has been refactored:
+ - Arguments are parsed with argparse.
+ - The inline help includes a description of options.
+ - Some commands have been renamed. Notably, 'mktx' and 'payto' have
+ been merged into a single command, with a --broadcast option.
+ Type 'electrum --help' for a complete overview.
+ * The command line accepts the '!' syntax to send the maximum
+ amount available. It can be combined with the '--from' option.
+ Example: 'payto ! --from '
+ * The command line also accepts a '?' shortcut for private keys
+ arguments, that triggers a prompt.
+ * Payment requests can be managed with the command line, using the
+ following commands: 'addrequest', 'rmrequest', 'listrequests'.
+ Payment requests can be signed with a SSL certificate, and published
+ as bip70 files in a public web directory. To see the relevant
+ configuration variables, type 'electrum addrequest --help'
+ * Commands can be called with jsonrpc, using the 'jsonrpc' gui. The
+ jsonrpc interface may be called by php.
+
+# Release 2.2
+ * Show amounts (thousands separators and decimal point)
+ according to locale in GUI
+ * Show unmatured coins in balance
+ * Fix exchange rates plugin
+ * Network layer: refactoring and fixes
+
+# Release 2.1.1
+ * patch a bug that prevents new wallet creation.
+ * fix connection issue on osx binaries
+
+# Release 2.1
+ * Faster startup, thanks to the following optimizations:
+ 1. Transaction input/outputs are cached in the wallet file
+ 2. Fast X509 certificate parser, not using pyasn1 anymore.
+ 3. The Label Sync plugin only requests modified labels.
+ * The 'Invoices' and 'Send' tabs have been merged.
+ * Contacts are stored in a separate file, shared between wallets.
+ * A Search Box is available in the GUI (Ctrl-S)
+ * Payment requests have an expiration date and can be exported to
+ BIP70 files.
+ * file: scheme support in BIP72 URIs: "bitcoin:?r=file:///..."
+ * Own addresses are shown in green in the Transaction dialog.
+ * Address History dialog.
+ * The OpenAlias plugin was improved.
+ * Various bug fixes and GUI improvements.
+ * A new LabelSync backend is being used an import of the old
+ database was made but since the release came later it's
+ recommended that you do a full push when you upgrade.
+
+# Release 2.0.4 - Minor GUI improvements
+ * The password dialog will ask for password again if the user enters
+ a wrong password
+ * The Master Public Key dialog displays which keys belong to the
+ wallet, and which are cosigners
+ * The transaction dialog will ask to save unsaved transaction
+ received from cosigner pool, when user clicks on 'Close'
+ * The multisig restore dialog accepts xprv keys.
+ * The network daemon must be started explicitly before using commands
+ that require a connection
+ Example:
+ electrum daemon start
+ electrum getaddressunspent
+ electrum daemon status
+ electrum daemon stop
+ If a daemon is running, the GUI will use it.
+
+# Release 2.0.3 - bugfixes and minor GUI improvements
+ * Do not use daemon threads (fix #960)
+ * Add a zoom button to receive tab
+ * Add exchange rate conversion to receive tab
+ * Use Tor's default port number in default proxy config
+
+# Release 2.0.2 - bugfixes
+ * Fix transaction sweep (#1066)
+ * Fix thread timing bug (#1054)
+
+# Release 2.0.1 - bugfixes
+ * Fix critical bug in TREZOR address derivation: passphrases were not
+ NFKD normalized. TREZOR users who created a wallet protected by a
+ passphrase containing utf-8 characters with diacritics are
+ affected. These users will have to open their wallet with version
+ 2.0 and to move their funds to a new wallet.
+ * Use a file socket for the daemon (fixes network dialog issues)
+ * Fix crash caused by QR scanner icon when zbar not installed.
+ * Fix CosignerPool plugin
+ * Label Sync plugin: Fix label sharing between multisig wallets
+
+
+# Release 2.0
+
+ * Before you upgrade, make sure you have saved your wallet seed on
+ paper.
+
+ * Documentation is now hosted on a wiki: http://electrum.orain.org
+
+ * New seed derivation method (not compatible with BIP39). The seed
+ phrase includes a version number, that refers to the wallet
+ structure. The version number also serves as a checksum, and it
+ will prevent the import of seeds from incompatible wallets. Old
+ Electrum seeds are still supported.
+
+ * New address derivation (BIP32). Standard wallets are single account
+ and use a gap limit of 20.
+
+ * Support for Multisig wallets using parallel BIP32 derivations and
+ P2SH addresses ("2 of 2", "2 of 3").
+
+ * Compact serialization format for unsigned or partially signed
+ transactions, that includes the BIP32 master public key and
+ derivation needed to sign inputs. Serialized transactions can be
+ sent to cosigners or to cold storage using QR codes (using Andreas
+ Schildbach's base 43 idea).
+
+ * Support for BIP70 payment requests:
+ - Verification of the chain of signatures uses tlslite.
+ - In the GUI, payment requests are shown in the 'Invoices' tab.
+
+ * Support for hardware wallets: TREZOR (SatoshiLabs) and Btchip (Ledger).
+
+ * Two-factor authentication service by TrustedCoin. This service uses
+ "2 of 3" multisig wallets and Google Authenticator. Note that
+ wallets protected by this service can be deterministically restored
+ from seed, without Trustedcoin's server.
+
+ * Cosigner Pool plugin: encrypted communication channel for multisig
+ wallets, to send and receive partially signed transactions.
+
+ * Audio Modem plugin: send and receive transactions by sound.
+
+ * OpenAlias plugin: send bitcoins to aliases verified using DNSSEC.
+
+ * New 'Receive' tab in the GUI:
+ - create and manage payment requests, with QR Codes
+ - the former 'Receive' tab was renamed to 'Addresses'
+ - the former Point of Sale plugin is replaced by a resizeable
+ window that pops up if you click on the QR code
+
+ * The 'Send' tab in the Qt GUI supports transactions with multiple
+ outputs, and raw hexadecimal scripts.
+
+ * The GUI can connect to the Electrum daemon: "electrum -d" will
+ start the daemon if it is not already running, and the GUI will
+ connect to it. The daemon can serve several clients. It times out
+ if no client uses if for more than 5 minutes.
+
+ * The install wizard can be used to import addresses or private
+ keys. A watching-only wallet is created by entering a list of
+ addresses in the wizard dialog.
+
+ * New file format: Wallets files are saved as JSON. Note that new
+ wallet files cannot be read by older versions of Electrum. Old
+ wallet files will be converted to the new format; this operation
+ may take some time, because public keys will be derived for each
+ address of your wallet.
+
+ * The client accepts servers with a CA-signed SSL certificate.
+
+ * ECIES encrypt/decrypt methods, availabe in the GUI and using
+ the command line:
+ encrypt
+ decrypt
+
+ * The Android GUI has received various updates and it is much more
+ stable. Another script was added to Android, called Authenticator,
+ that works completely offline: it reads an unsigned transaction
+ shown as QR code, signs it and shows the result as a QR code.
+
+
+# Release 1.9.8
+
+* Electrum servers were upgraded to version 0.9. The new server stores
+ a Patrica tree of all UTXOs, an idea proposed by Alan Reiner in the
+ bitcointalk forum. This property allows the client to directly
+ request the balance of any address. The new commands are:
+ 1. getaddressbalance
+ 2. getaddressunspent
+ 3. getutxoaddress
+
+* Command-line commands that require a connection to the network spawn
+ a daemon, that remains connected and handles subsequent
+ commands. The daemon terminates itself if it remains unused for more
+ than one minute. The purpose of this is to make scripting more
+ efficient. For example, a bash script using many electrum commands
+ will open only one connection.
+
+# Release 1.9.7
+* Fix for offline signing
+* Various bugfixes
+* GUI usability improvements
+* Coinbase Buyback plugin
+
+# Release 1.9.6
+* During wallet creation, do not write seed to disk until it is encrypted.
+* Confirmation dialog if the transaction fee is higher than 1mBTC.
+* bugfixes
+
+# Release 1.9.5
+
+* Coin control: select addresses to send from
+* Put addresses that have been used in a minimized section (Qt GUI)
+* Allow non ascii chars in passwords
+
+
+# Release 1.9.4
+bugfixes: offline transactions
+
+# Release 1.9.3
+bugfixes: connection problems, transactions staying unverified
+
+# Release 1.9.2
+* fix a syntax error
+
+# Release 1.9.1
+* fix regression with --offline mode
+* fix regression with --portable mode: use a dedicated directory
+
+# Release 1.9
+
+* The client connects to multiple servers in order to retrieve block headers and find the longest chain
+* SSL certificate validation (to prevent MITM)
+* Deterministic signatures (RFC 6979)
+* Menu to create/restore/open wallets
+* Create transactions with multiple outputs from CSV (comma separated values)
+* New text gui: stdio
+* Plugins are no longer tied to the qt GUI, they can reach all GUIs
+* Proxy bugs have been fixed
+
+
+# Release 1.8.1
+
+* Notification option when receiving new tranactions
+* Confirm dialogue before sending large amounts
+* Alternative datafile location for non-windows systems
+* Fix offline wallet creation
+* Remove enforced tx fee
+* Tray icon improvements
+* Various bugfixes
+
+
+# Release 1.8
+
+* Menubar in classic gui
+* Updated the QR Code plugin to enable offline/online wallets to transmit unsigned/signed transactions via QR code.
+* Fixed bug where never-confirmed transactions prevented further spending
+
+
+# Release 1.7.4
+
+* Increase default fee
+* fix create and restore in command line
+* fix verify message in the gui
+
+
+# Release 1.7.3:
+
+* Classic GUI can display amounts in mBTC
+* Account selector in the classic GUI
+* Changed the way the portable flag uses without supplying a -w argument
+* Classic GUI asks users to enter their seed on wallet creation
+
+
+# Release 1.7.2:
+
+* Transactions that are in the same block are displayed in chronological order in the history.
+* The client computes transaction priority and rejects zero-fee transactions that need a fee.
+* The default fee was lowered to 200 uBTC per kb.
+* Due to an internal format change, your history may be pruned when
+ you open your wallet for the first time after upgrading to 1.7.2. If
+ this is the case, please visit a full server to restore your full
+ history. You will only need to do that once.
+
+
+# Release 1.7.1: bugfixes.
+
+
+# Release 1.7
+
+* The Classic GUI can be extended with plugins. Developers who want to
+add new features or third-party services to Electrum are invited to
+write plugins. Some previously existing and non-essential features of
+Electrum (point-of-sale mode, qrcode scanner) were removed from the
+core and are now available as plugins.
+
+* The wallet waits for 2 confirmations before creating new
+addresses. This makes recovery from seed more robust. Note that it
+might create unwanted gaps if you use Electrum 1.7 together with older
+versions of Electrum.
+
+* An interactive Python console replaces the 'Wall' tab. The provided
+python environment gives users access to the wallet and gui. Most
+electrum commands are available as python function in the
+console. Custom scripts an be loaded with a "run(filename)"
+command. Tab-completions are available.
+
+* The location of the Electrum folder in Windows changed from
+LOCALAPPDATA to APPDATA. Discussion on this topic can be found here:
+https://bitcointalk.org/index.php?topic=144575.0
+
+* Private keys can be exported from within the classic GUI:
+ For a single address, use the address menu (right-click).
+ To export the keys of your entire wallet, use the settings dialog (import/export tab).
+
+* It is possible to create, sign and redeem multisig transaction using the
+command line interface. This is made possible by the following new commands:
+ dumpprivkey, listunspent, createmultisig, createrawtransaction, decoderawtransaction, signrawtransaction
+The syntax of these commands is similar to their bitcoind counterpart.
+For an example, see Gavin's tutorial: https://gist.github.com/gavinandresen/3966071
+
+* Offline wallets now work in a way similar to Armory:
+ 1. user creates an unsigned transaction using the online (watching-only) wallet.
+ 2. unsigned transaction is copied to the offline computer, and signed by the offline wallet.
+ 3. signed transaction is copied to the online computer, broadcasted by the online client.
+ 4. All these steps can be done via the command line interface or the classic GUI.
+
+* Many command line commands have been renamed in order to make the syntax consistent with bitcoind.
+
+# Release 1.6.2
+
+== Classic GUI
+* Added new version notification
+
+# Release 1.6.1 (11-01-2013)
+
+== Core
+* It is now possible to restore a wallet from MPK (this will create a watching-only wallet)
+* A switch button allows to easily switch between Lite and Classic GUI.
+
+== Classic GUI
+* Seed and MPK help dialogs were rewritten
+* Point of Sale: requested amounts can be expressed in other currencies and are converted to bitcoin.
+
+== Lite GUI
+* The receiving button was removed in favor of a menu item to keep it consistent with the history toggle.
+
+# Release 1.6.0 (07-01-2013)
+
+== Core
+* (Feature) Add support for importing, signing and verifiying compressed keys
+* (Feature) Auto reconnect to random server on disconnect
+* (Feature) Ultimate fallback to HTTP port 80 if TCP doesn't work on any server
+* (Bug) Under rare circumstances changing password with incorrect password could damage wallet
+
+== Lite GUI
+* (Chore) Use blockchain.info for exchange rate data
+* (Feature) added currency conversion for BRL, CNY, RUB
+* (Feature) Saraha theme
+* (Feature) csv import/export for transactions including labels
+
+== Classic GUI
+* (Chore) pruning servers now called "p", full servers "f" to avoid confusion with terms
+* (Feature) Debits in history shown in red
+* (Feature) csv import/export for transactions including labels
+
+# Release 1.5.8 (02-01-2013)
+
+== Core
+* (Bug) Fix pending address balance on received coins for pruning servers
+* (Bug) Fix history command line option to show output again (regression by SPV)
+* (Chore) Add timeout to blockchain headers file download by HTTP
+* (Feature) new option: -L, --language: default language used in GUI.
+
+== Lite GUI
+* (Bug) Sending to auto-completed contacts works again
+* (Chore) Added version number to title bar
+
+== Classic GUI
+* (Feature) Language selector in options.
+
+# Release 1.5.7 (18-12-2012)
+
+== Core
+* The blockchain headers file is no longer included in the packages, it is downloaded on startup.
+* 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
+
+== Lite GUI
+* (Feature) Added the ability to export your transactions to a CSV file.
+* (Feature) Added a label dialog after sending a transaction.
+* (Feature) Reworked receiving addresses; instead of a random selection from one of your receiving addresses a new widget will show listing unused addresses.
+* (Chore) Removed server selection. With all the new server options a simple menu item does not suffice anymore.
diff --git a/contrib/build-osx/README.md b/contrib/build-osx/README.md
new file mode 100644
index 000000000..c1e96d90b
--- /dev/null
+++ b/contrib/build-osx/README.md
@@ -0,0 +1,36 @@
+Building Mac OS binaries
+========================
+
+This guide explains how to build Electrum binaries for macOS systems.
+
+The build process consists of two steps:
+
+## 1. Building the binary
+
+This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it on High Sierra
+makes the binaries incompatible with older versions.
+
+Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`).
+
+
+ cd electrum
+ ./contrib/build-osx/make_osx
+
+This creates a folder named Electrum.app.
+
+## 2. Building the image
+The usual way to distribute macOS applications is to use image files containing the
+application. Although these images can be created on a Mac with the built-in `hdiutil`,
+they are not deterministic.
+
+Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus.
+These tools do not work on macOS, so you need a separate Linux machine (or VM).
+
+Copy the Electrum.app directory over and install the dependencies, e.g.:
+
+ apt install libcap-dev cmake make gcc faketime
+
+Then you can just invoke `package.sh` with the path to the app:
+
+ cd electrum
+ ./contrib/build-osx/package.sh ~/Electrum.app/
\ No newline at end of file
diff --git a/contrib/build-osx/base.sh b/contrib/build-osx/base.sh
new file mode 100644
index 000000000..c5a5c0d69
--- /dev/null
+++ b/contrib/build-osx/base.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+RED='\033[0;31m'
+BLUE='\033[0,34m'
+NC='\033[0m' # No Color
+function info {
+ printf "\r💬 ${BLUE}INFO:${NC} ${1}\n"
+}
+function fail {
+ printf "\r🗯 ${RED}ERROR:${NC} ${1}\n"
+ exit 1
+}
diff --git a/contrib/build-osx/cdrkit-deterministic.patch b/contrib/build-osx/cdrkit-deterministic.patch
new file mode 100644
index 000000000..d01e5b75e
--- /dev/null
+++ b/contrib/build-osx/cdrkit-deterministic.patch
@@ -0,0 +1,86 @@
+--- cdrkit-1.1.11.old/genisoimage/tree.c 2008-10-21 19:57:47.000000000 -0400
++++ cdrkit-1.1.11/genisoimage/tree.c 2013-12-06 00:23:18.489622668 -0500
+@@ -1139,8 +1139,9 @@
+ scan_directory_tree(struct directory *this_dir, char *path,
+ struct directory_entry *de)
+ {
+- DIR *current_dir;
++ int current_file;
+ char whole_path[PATH_MAX];
++ struct dirent **d_list;
+ struct dirent *d_entry;
+ struct directory *parent;
+ int dflag;
+@@ -1164,7 +1165,8 @@
+ this_dir->dir_flags |= DIR_WAS_SCANNED;
+
+ errno = 0; /* Paranoia */
+- current_dir = opendir(path);
++ //current_dir = opendir(path);
++ current_file = scandir(path, &d_list, NULL, alphasort);
+ d_entry = NULL;
+
+ /*
+@@ -1173,12 +1175,12 @@
+ */
+ old_path = path;
+
+- if (current_dir) {
++ if (current_file >= 0) {
+ errno = 0;
+- d_entry = readdir(current_dir);
++ d_entry = d_list[0];
+ }
+
+- if (!current_dir || !d_entry) {
++ if (current_file < 0 || !d_entry) {
+ int ret = 1;
+
+ #ifdef USE_LIBSCHILY
+@@ -1191,8 +1193,8 @@
+ de->isorec.flags[0] &= ~ISO_DIRECTORY;
+ ret = 0;
+ }
+- if (current_dir)
+- closedir(current_dir);
++ if(d_list)
++ free(d_list);
+ return (ret);
+ }
+ #ifdef ABORT_DEEP_ISO_ONLY
+@@ -1208,7 +1210,7 @@
+ errmsgno(EX_BAD, "use Rock Ridge extensions via -R or -r,\n");
+ errmsgno(EX_BAD, "or allow deep ISO9660 directory nesting via -D.\n");
+ }
+- closedir(current_dir);
++ free(d_list);
+ return (1);
+ }
+ #endif
+@@ -1250,13 +1252,13 @@
+ * The first time through, skip this, since we already asked
+ * for the first entry when we opened the directory.
+ */
+- if (dflag)
+- d_entry = readdir(current_dir);
++ if (dflag && current_file >= 0)
++ d_entry = d_list[current_file];
+ dflag++;
+
+- if (!d_entry)
++ if (current_file < 0)
+ break;
+-
++ current_file--;
+ /* OK, got a valid entry */
+
+ /* If we do not want all files, then pitch the backups. */
+@@ -1348,7 +1350,7 @@
+ insert_file_entry(this_dir, whole_path, d_entry->d_name);
+ #endif /* APPLE_HYB */
+ }
+- closedir(current_dir);
++ free(d_list);
+
+ #ifdef APPLE_HYB
+ /*
\ No newline at end of file
diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx
new file mode 100755
index 000000000..8697838e7
--- /dev/null
+++ b/contrib/build-osx/make_osx
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+
+# Parameterize
+PYTHON_VERSION=3.6.4
+BUILDDIR=/tmp/electrum-build
+PACKAGE=electrum-btx
+GIT_REPO=https://github.com/LIMXTEC/electrum-btx
+LIBSECP_VERSION=452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
+
+. $(dirname "$0")/base.sh
+
+src_dir=$(dirname "$0")
+cd $src_dir/../..
+
+export PYTHONHASHSEED=22
+VERSION=`git describe --tags --dirty`
+
+which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
+
+info "Installing Python $PYTHON_VERSION"
+export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH"
+if [ -d "~/.pyenv" ]; then
+ pyenv update
+else
+ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1
+fi
+PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -s $PYTHON_VERSION && \
+pyenv global $PYTHON_VERSION || \
+fail "Unable to use Python $PYTHON_VERSION"
+
+
+info "Installing pyinstaller"
+python3 -m pip install git+https://github.com/ecdsa/pyinstaller@fix_2952 -I --user || fail "Could not install pyinstaller"
+
+info "Using these versions for building $PACKAGE:"
+sw_vers
+python3 --version
+echo -n "Pyinstaller "
+pyinstaller --version
+
+rm -rf ./dist
+
+git submodule init
+git submodule update
+
+rm -rf $BUILDDIR > /dev/null 2>&1
+mkdir $BUILDDIR
+
+cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/
+cp ./contrib/deterministic-build/electrum-icons/icons_rc.py ./electrum/gui/qt/
+
+
+info "Downloading libusb..."
+curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \
+tar xz --directory $BUILDDIR
+cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx
+
+info "Building libsecp256k1"
+brew install autoconf automake libtool
+git clone https://github.com/bitcoin-core/secp256k1 $BUILDDIR/secp256k1
+pushd $BUILDDIR/secp256k1
+git reset --hard $LIBSECP_VERSION
+git clean -f -x -q
+./autogen.sh
+./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --disable-jni
+make
+popd
+cp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/build-osx
+
+
+info "Installing requirements..."
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \
+fail "Could not install requirements"
+
+info "Installing hardware wallet requirements..."
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \
+fail "Could not install hardware wallet requirements"
+
+info "Building $PACKAGE..."
+python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE"
+
+info "Faking timestamps..."
+for d in ~/Library/Python/ ~/.pyenv .; do
+ pushd $d
+ find . -exec touch -t '200101220000' {} +
+ popd
+done
+
+info "Building binary"
+pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary"
+
+info "Creating .DMG"
+hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"
diff --git a/contrib/build-osx/osx.spec b/contrib/build-osx/osx.spec
new file mode 100644
index 000000000..d27967643
--- /dev/null
+++ b/contrib/build-osx/osx.spec
@@ -0,0 +1,104 @@
+# -*- mode: python -*-
+
+from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
+
+import sys
+import os
+
+PACKAGE='electrum-btx'
+PYPKG='electrum-btx'
+MAIN_SCRIPT='run_electrum'
+ICONS_FILE='icons/electrumBTX.icns'
+
+for i, x in enumerate(sys.argv):
+ if x == '--name':
+ VERSION = sys.argv[i+1]
+ break
+else:
+ raise Exception('no version')
+
+electrum = os.path.abspath(".") + "/"
+block_cipher = None
+
+# see https://github.com/pyinstaller/pyinstaller/issues/2005
+hiddenimports = []
+hiddenimports += collect_submodules('trezorlib')
+hiddenimports += collect_submodules('safetlib')
+hiddenimports += collect_submodules('btchip')
+hiddenimports += collect_submodules('keepkeylib')
+hiddenimports += collect_submodules('websocket')
+hiddenimports += collect_submodules('ckcc')
+
+datas = [
+ (electrum + PYPKG + '/*.json', PYPKG),
+ (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
+ (electrum + PYPKG + '/locale', PYPKG + '/locale'),
+ (electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
+]
+datas += collect_data_files('trezorlib')
+datas += collect_data_files('safetlib')
+datas += collect_data_files('btchip')
+datas += collect_data_files('keepkeylib')
+datas += collect_data_files('ckcc')
+
+# Add libusb so Trezor and Safe-T mini will work
+binaries = [(electrum + "contrib/build-osx/libusb-1.0.dylib", ".")]
+binaries += [(electrum + "contrib/build-osx/libsecp256k1.0.dylib", ".")]
+
+# Workaround for "Retro Look":
+binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
+
+# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
+a = Analysis([electrum+ MAIN_SCRIPT,
+ electrum+'electrum/gui/qt/main_window.py',
+ electrum+'electrum/gui/text.py',
+ electrum+'electrum/util.py',
+ electrum+'electrum/wallet.py',
+ electrum+'electrum/simple_config.py',
+ electrum+'electrum/bitcoin.py',
+ electrum+'electrum/dnssec.py',
+ electrum+'electrum/commands.py',
+ electrum+'electrum/plugins/cosigner_pool/qt.py',
+ electrum+'electrum/plugins/email_requests/qt.py',
+ electrum+'electrum/plugins/trezor/client.py',
+ electrum+'electrum/plugins/trezor/qt.py',
+ electrum+'electrum/plugins/safe_t/client.py',
+ electrum+'electrum/plugins/safe_t/qt.py',
+ electrum+'electrum/plugins/keepkey/qt.py',
+ electrum+'electrum/plugins/ledger/qt.py',
+ electrum+'electrum/plugins/coldcard/qt.py',
+ ],
+ binaries=binaries,
+ datas=datas,
+ hiddenimports=hiddenimports,
+ hookspath=[])
+
+# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
+for d in a.datas:
+ if 'pyconfig' in d[0]:
+ a.datas.remove(d)
+ break
+
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+exe = EXE(pyz,
+ a.scripts,
+ a.binaries,
+ a.datas,
+ name=PACKAGE,
+ debug=False,
+ strip=False,
+ upx=True,
+ icon=electrum+ICONS_FILE,
+ console=False)
+
+app = BUNDLE(exe,
+ version = VERSION,
+ name=PACKAGE + '.app',
+ icon=electrum+ICONS_FILE,
+ bundle_identifier=None,
+ info_plist={
+ 'NSHighResolutionCapable': 'True',
+ 'NSSupportsAutomaticGraphicsSwitching': 'True'
+ }
+)
diff --git a/contrib/build-osx/package.sh b/contrib/build-osx/package.sh
new file mode 100755
index 000000000..dcbc29388
--- /dev/null
+++ b/contrib/build-osx/package.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+cdrkit_version=1.1.11
+cdrkit_download_path=http://distro.ibiblio.org/fatdog/source/600/c
+cdrkit_file_name=cdrkit-${cdrkit_version}.tar.bz2
+cdrkit_sha256_hash=b50d64c214a65b1a79afe3a964c691931a4233e2ba605d793eb85d0ac3652564
+cdrkit_patches=cdrkit-deterministic.patch
+genisoimage=genisoimage-$cdrkit_version
+
+libdmg_url=https://github.com/theuni/libdmg-hfsplus
+
+
+export LD_PRELOAD=$(locate libfaketime.so.1)
+export FAKETIME="2000-01-22 00:00:00"
+export PATH=$PATH:~/bin
+
+. $(dirname "$0")/base.sh
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 Electrum.app"
+ exit -127
+fi
+
+mkdir -p ~/bin
+
+if ! which ${genisoimage} > /dev/null 2>&1; then
+ mkdir -p /tmp/electrum-macos
+ cd /tmp/electrum-macos
+ info "Downloading cdrkit $cdrkit_version"
+ wget -nc ${cdrkit_download_path}/${cdrkit_file_name}
+ tar xvf ${cdrkit_file_name}
+
+ info "Patching genisoimage"
+ cd cdrkit-${cdrkit_version}
+ patch -p1 < ../cdrkit-deterministic.patch
+
+ info "Building genisoimage"
+ cmake . -Wno-dev
+ make genisoimage
+ cp genisoimage/genisoimage ~/bin/${genisoimage}
+fi
+
+if ! which dmg > /dev/null 2>&1; then
+ mkdir -p /tmp/electrum-macos
+ cd /tmp/electrum-macos
+ info "Downloading libdmg"
+ LD_PRELOAD= git clone ${libdmg_url}
+ cd libdmg-hfsplus
+ info "Building libdmg"
+ cmake .
+ make
+ cp dmg/dmg ~/bin
+fi
+
+${genisoimage} -version || fail "Unable to install genisoimage"
+dmg -|| fail "Unable to install libdmg"
+
+plist=$1/Contents/Info.plist
+test -f "$plist" || fail "Info.plist not found"
+VERSION=$(grep -1 ShortVersionString $plist |tail -1|gawk 'match($0, /(.*)<\/string>/, a) {print a[1]}')
+echo $VERSION
+
+rm -rf /tmp/electrum-macos/image > /dev/null 2>&1
+mkdir /tmp/electrum-macos/image/
+cp -r $1 /tmp/electrum-macos/image/
+
+build_dir=$(dirname "$1")
+test -n "$build_dir" -a -d "$build_dir" || exit
+cd $build_dir
+
+${genisoimage} \
+ -no-cache-inodes \
+ -D \
+ -l \
+ -probe \
+ -V "Electrum" \
+ -no-pad \
+ -r \
+ -dir-mode 0755 \
+ -apple \
+ -o Electrum_uncompressed.dmg \
+ /tmp/electrum-macos/image || fail "Unable to create uncompressed dmg"
+
+dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to create compressed dmg"
+rm Electrum_uncompressed.dmg
+
+echo "Done."
+md5sum electrum-$VERSION.dmg
diff --git a/contrib/build-wine/README.md b/contrib/build-wine/README.md
new file mode 100644
index 000000000..c6348c975
--- /dev/null
+++ b/contrib/build-wine/README.md
@@ -0,0 +1,37 @@
+Windows Binary Builds
+=====================
+
+These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.
+
+For reproducible builds, see the `docker` folder.
+
+
+Usage:
+
+
+1. Install the following dependencies:
+
+ - dirmngr
+ - gpg
+ - 7Zip
+ - Wine (>= v2)
+ - (and, for building libsecp256k1)
+ - mingw-w64
+ - autotools-dev
+ - autoconf
+ - libtool
+
+
+For example:
+
+```
+$ sudo apt-get install wine-development dirmngr gnupg2 p7zip-full
+$ sudo apt-get install mingw-w64 autotools-dev autoconf libtool
+```
+
+The binaries are also built by Travis CI, so if you are having problems,
+[that script](https://github.com/spesmilo/electrum/blob/master/.travis.yml) might help.
+
+2. Make sure `/opt` is writable by the current user.
+3. Run `build.sh`.
+4. The generated binaries are in `./dist`.
diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh
new file mode 100755
index 000000000..76797050d
--- /dev/null
+++ b/contrib/build-wine/build-electrum-git.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+NAME_ROOT=electrum
+PYTHON_VERSION=3.6.6
+
+# These settings probably don't need any change
+export WINEPREFIX=/opt/wine64
+export PYTHONDONTWRITEBYTECODE=1
+export PYTHONHASHSEED=22
+
+PYHOME=c:/python$PYTHON_VERSION
+PYTHON="wine $PYHOME/python.exe -OO -B"
+
+
+# Let's begin!
+cd `dirname $0`
+set -e
+
+pushd ../../electrum
+if ! which msgfmt > /dev/null 2>&1; then
+ echo "Please install gettext"
+ exit 1
+fi
+for i in ./locale/*; do
+ dir=$i/LC_MESSAGES
+ mkdir -p $dir
+ msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true
+done
+popd
+
+cp -f ../../LICENSE .
+
+# Install frozen dependencies
+$PYTHON -m pip install -r ../deterministic-build/requirements.txt
+$PYTHON -m pip install -r ../deterministic-build/requirements-hw.txt
+
+pushd $WINEPREFIX/drive_c/electrum
+find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
+popd
+
+pushd $WINEPREFIX/drive_c/electrum
+$PYTHON setup.py install
+popd
+
+#rm -rf dist/
+
+# build standalone and portable versions
+wine "C:/python$PYTHON_VERSION/scripts/pyinstaller.exe" --noconfirm --ascii --clean --name electrum-btx-3.2.3 -w deterministic.spec
+
+# build NSIS installer
+# $VERSION could be passed to the electrum.nsi script, but this would require some rewriting in the script itself.
+wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" /DPRODUCT_VERSION=$VERSION electrum.nsi
+
+echo "Done."
+md5sum dist/electrum*exe
diff --git a/contrib/build-wine/build-secp256k1.sh b/contrib/build-wine/build-secp256k1.sh
new file mode 100755
index 000000000..30d4a598b
--- /dev/null
+++ b/contrib/build-wine/build-secp256k1.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# heavily based on https://github.com/ofek/coincurve/blob/417e726f553460f88d7edfa5dc67bfda397c4e4a/.travis/build_windows_wheels.sh
+
+set -e
+
+build_dll() {
+ #sudo apt-get install -y mingw-w64
+ export SOURCE_DATE_EPOCH=1530212462
+ ./autogen.sh
+ echo "LDFLAGS = -no-undefined" >> Makefile.am
+ LDFLAGS="-Wl,--no-insert-timestamp" ./configure \
+ --host=$1 \
+ --enable-module-recovery \
+ --enable-experimental \
+ --enable-module-ecdh \
+ --disable-jni
+ make
+ ${1}-strip .libs/libsecp256k1-0.dll
+}
+
+
+cd /tmp/electrum-build
+
+if [ ! -d secp256k1 ]; then
+ git clone https://github.com/bitcoin-core/secp256k1.git
+ cd secp256k1;
+else
+ cd secp256k1
+ git pull
+fi
+
+git reset --hard 452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
+git clean -f -x -q
+
+build_dll i686-w64-mingw32 # 64-bit would be: x86_64-w64-mingw32
+mv .libs/libsecp256k1-0.dll libsecp256k1.dll
+
+find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
+
+echo "building libsecp256k1 finished"
diff --git a/contrib/build-wine/build.sh b/contrib/build-wine/build.sh
new file mode 100755
index 000000000..01ca071fe
--- /dev/null
+++ b/contrib/build-wine/build.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Lucky number
+export PYTHONHASHSEED=22
+
+here=$(dirname "$0")
+test -n "$here" -a -d "$here" || exit
+
+echo "Clearing $here/build and $here/dist..."
+rm "$here"/build/* -rf
+rm "$here"/dist/* -rf
+
+mkdir -p /tmp/electrum-build
+mkdir -p /tmp/electrum-build/pip-cache
+export PIP_CACHE_DIR="/tmp/electrum-build/pip-cache"
+
+$here/build-secp256k1.sh || exit 1
+
+$here/prepare-wine.sh || exit 1
+
+echo "Resetting modification time in C:\Python..."
+# (Because of some bugs in pyinstaller)
+pushd /opt/wine64/drive_c/python*
+find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
+popd
+ls -l /opt/wine64/drive_c/python*
+
+$here/build-electrum-git.sh && \
+echo "Done."
diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec
new file mode 100644
index 000000000..875a091ae
--- /dev/null
+++ b/contrib/build-wine/deterministic.spec
@@ -0,0 +1,140 @@
+# -*- mode: python -*-
+
+from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
+
+import sys
+for i, x in enumerate(sys.argv):
+ if x == '--name':
+ cmdline_name = sys.argv[i+1]
+ break
+else:
+ raise Exception('no name')
+
+PYTHON_VERSION = '3.6.6'
+PYHOME = 'c:/python' + PYTHON_VERSION
+
+home = 'C:\\electrum\\'
+
+# see https://github.com/pyinstaller/pyinstaller/issues/2005
+hiddenimports = []
+hiddenimports += collect_submodules('trezorlib')
+hiddenimports += collect_submodules('safetlib')
+hiddenimports += collect_submodules('btchip')
+hiddenimports += collect_submodules('keepkeylib')
+hiddenimports += collect_submodules('websocket')
+hiddenimports += collect_submodules('ckcc')
+
+# Add libusb binary
+binaries = [(PYHOME+"/libusb-1.0.dll", ".")]
+
+# Workaround for "Retro Look":
+binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]]
+
+binaries += [('C:/tmp/libsecp256k1.dll', '.')]
+
+datas = [
+ (home+'electrum/*.json', 'electrum'),
+ (home+'electrum/wordlist/english.txt', 'electrum/wordlist'),
+ (home+'electrum/locale', 'electrum/locale'),
+ (home+'electrum/plugins', 'electrum/plugins'),
+ ('C:\\Program Files (x86)\\ZBar\\bin\\', '.'),
+]
+datas += collect_data_files('trezorlib')
+datas += collect_data_files('safetlib')
+datas += collect_data_files('btchip')
+datas += collect_data_files('keepkeylib')
+datas += collect_data_files('ckcc')
+
+# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
+a = Analysis([home+'run_electrum',
+ home+'electrum/gui/qt/main_window.py',
+ home+'electrum/gui/text.py',
+ home+'electrum/util.py',
+ home+'electrum/wallet.py',
+ home+'electrum/simple_config.py',
+ home+'electrum/bitcoin.py',
+ home+'electrum/dnssec.py',
+ home+'electrum/commands.py',
+ home+'electrum/plugins/cosigner_pool/qt.py',
+ home+'electrum/plugins/email_requests/qt.py',
+ home+'electrum/plugins/trezor/client.py',
+ home+'electrum/plugins/trezor/qt.py',
+ home+'electrum/plugins/safe_t/client.py',
+ home+'electrum/plugins/safe_t/qt.py',
+ home+'electrum/plugins/keepkey/qt.py',
+ home+'electrum/plugins/ledger/qt.py',
+ home+'electrum/plugins/coldcard/qt.py',
+ #home+'packages/requests/utils.py'
+ ],
+ binaries=binaries,
+ datas=datas,
+ #pathex=[home+'lib', home+'gui', home+'plugins'],
+ hiddenimports=hiddenimports,
+ hookspath=[])
+
+
+# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
+for d in a.datas:
+ if 'pyconfig' in d[0]:
+ a.datas.remove(d)
+ break
+
+# hotfix for #3171 (pre-Win10 binaries)
+a.binaries = [x for x in a.binaries if not x[1].lower().startswith(r'c:\windows')]
+
+pyz = PYZ(a.pure)
+
+
+#####
+# "standalone" exe with all dependencies packed into it
+
+exe_standalone = EXE(
+ pyz,
+ a.scripts,
+ a.binaries,
+ a.datas,
+ name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + ".exe"),
+ debug=False,
+ strip=None,
+ upx=False,
+ icon=home+'icons/electrum.ico',
+ console=False)
+ # 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
+
+exe_portable = EXE(
+ pyz,
+ a.scripts,
+ a.binaries,
+ a.datas + [ ('is_portable', 'README.md', 'DATA' ) ],
+ name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + "-portable.exe"),
+ debug=False,
+ strip=None,
+ upx=False,
+ icon=home+'icons/electrum.ico',
+ console=False)
+
+#####
+# exe and separate files that NSIS uses to build installer "setup" exe
+
+exe_dependent = EXE(
+ pyz,
+ a.scripts,
+ exclude_binaries=True,
+ name=os.path.join('build\\pyi.win32\\electrum', cmdline_name),
+ debug=False,
+ strip=None,
+ upx=False,
+ icon=home+'icons/electrum.ico',
+ console=False)
+
+coll = COLLECT(
+ exe_dependent,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ strip=None,
+ upx=True,
+ debug=False,
+ icon=home+'icons/electrum.ico',
+ console=False,
+ name=os.path.join('dist', 'electrum'))
diff --git a/contrib/build-wine/docker/Dockerfile b/contrib/build-wine/docker/Dockerfile
new file mode 100644
index 000000000..f46e98128
--- /dev/null
+++ b/contrib/build-wine/docker/Dockerfile
@@ -0,0 +1,33 @@
+FROM ubuntu:18.04@sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d
+
+ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
+
+RUN dpkg --add-architecture i386 && \
+ apt-get update -q && \
+ apt-get install -qy \
+ wget=1.19.4-1ubuntu2.1 \
+ gnupg2=2.2.4-1ubuntu1.1 \
+ dirmngr=2.2.4-1ubuntu1.1 \
+ software-properties-common=0.96.24.32.4 \
+ && \
+ wget -nc https://dl.winehq.org/wine-builds/Release.key && \
+ apt-key add Release.key && \
+ apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
+ apt-get update -q && \
+ apt-get install -qy \
+ wine-stable-amd64:amd64=3.0.1~bionic \
+ wine-stable-i386:i386=3.0.1~bionic \
+ wine-stable:amd64=3.0.1~bionic \
+ winehq-stable:amd64=3.0.1~bionic \
+ git=1:2.17.1-1ubuntu0.1 \
+ p7zip-full=16.02+dfsg-6 \
+ make=4.1-9.1ubuntu1 \
+ mingw-w64=5.0.3-1 \
+ autotools-dev=20180224.1 \
+ autoconf=2.69-11 \
+ libtool=2.4.6-2 \
+ gettext=0.19.8.1-6 \
+ && \
+ rm -rf /var/lib/apt/lists/* && \
+ apt-get autoremove -y && \
+ apt-get clean
diff --git a/contrib/build-wine/docker/README.md b/contrib/build-wine/docker/README.md
new file mode 100644
index 000000000..0df96ea5a
--- /dev/null
+++ b/contrib/build-wine/docker/README.md
@@ -0,0 +1,90 @@
+Deterministic Windows binaries with Docker
+==========================================
+
+Produced binaries are deterministic, so you should be able to generate
+binaries that match the official releases.
+
+This assumes an Ubuntu host, but it should not be too hard to adapt to another
+similar system. The docker commands should be executed in the project's root
+folder.
+
+1. Install Docker
+
+ ```
+ $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+ $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+ $ sudo apt-get update
+ $ sudo apt-get install -y docker-ce
+ ```
+
+2. Build image
+
+ ```
+ $ sudo docker build --no-cache -t electrum-wine-builder-img contrib/build-wine/docker
+ ```
+
+ Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems
+
+3. Build Windows binaries
+
+ ```
+ $ git checkout $REV
+ $ sudo docker run \
+ --name electrum-wine-builder-cont \
+ -v $PWD:/opt/wine64/drive_c/electrum \
+ --rm \
+ --workdir /opt/wine64/drive_c/electrum/contrib/build-wine \
+ electrum-wine-builder-img \
+ ./build.sh
+ ```
+4. The generated binaries are in `./contrib/build-wine/dist`.
+
+
+
+Note: the `setup` binary (NSIS installer) is not deterministic yet.
+
+
+Code Signing
+============
+
+Electrum Windows builds are signed with a Microsoft Authenticode™ code signing
+certificate in addition to the GPG-based signatures.
+
+The advantage of using Authenticode is that Electrum users won't receive a
+Windows SmartScreen warning when starting it.
+
+The release signing procedure involves a signer (the holder of the
+certificate/key) and one or multiple trusted verifiers:
+
+
+| Signer | Verifier |
+|-----------------------------------------------------------|-----------------------------------|
+| Build .exe files using `build.sh` | |
+| Sign .exe with `./sign.sh` | |
+| Upload signed files to download server | |
+| | Build .exe files using `build.sh` |
+| | Compare files using `unsign.sh` |
+| | Sign .exe file using `gpg -b` |
+
+| Signer and verifiers: |
+|-----------------------------------------------------------------------------------------------|
+| Upload signatures to 'electrum-signatures' repo, as `$version/$filename.$builder.asc` |
+
+
+
+Verify Integrity of signed binary
+=================================
+
+Every user can verify that the official binary was created from the source code in this
+repository. To do so, the Authenticode signature needs to be stripped since the signature
+is not reproducible.
+
+This procedure removes the differences between the signed and unsigned binary:
+
+1. Remove the signature from the signed binary using osslsigncode or signtool.
+2. Set the COFF image checksum for the signed binary to 0x0. This is necessary
+ because pyinstaller doesn't generate a checksum.
+3. Append null bytes to the _unsigned_ binary until the byte count is a multiple
+ of 8.
+
+The script `unsign.sh` performs these steps.
diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi
new file mode 100644
index 000000000..baf210fec
--- /dev/null
+++ b/contrib/build-wine/electrum.nsi
@@ -0,0 +1,170 @@
+;--------------------------------
+;Include Modern UI
+ !include "TextFunc.nsh" ;Needed for the $GetSize function. I know, doesn't sound logical, it isn't.
+ !include "MUI2.nsh"
+
+;--------------------------------
+;Variables
+
+ !define PRODUCT_NAME "electrum-btx"
+ !define PRODUCT_VER "3.2.3"
+ !define PRODUCT_WEB_SITE "https://github.com/LIMXTEC/electrum-btx"
+ !define PRODUCT_PUBLISHER "bitcore.cc"
+ !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}-${PRODUCT_VER}"
+ !define HOME "C:\electrum"
+ !define LICENSE_TXT "LICENSE"
+
+;--------------------------------
+;General
+
+ ;Name and file
+ Name "${PRODUCT_NAME}-${PRODUCT_VER}"
+ OutFile "dist/${PRODUCT_NAME}-${PRODUCT_VER}-setup.exe"
+
+ ;Default installation folder
+ InstallDir "$PROGRAMFILES\${PRODUCT_NAME}-${PRODUCT_VER}"
+
+ ;Get installation folder from registry if available
+ InstallDirRegKey HKCU "Software\${PRODUCT_NAME}-${PRODUCT_VER}" ""
+
+ ;Request application privileges for Windows Vista
+ RequestExecutionLevel admin
+
+ ;Specifies whether or not the installer will perform a CRC on itself before allowing an install
+ CRCCheck on
+
+ ;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.
+ ShowInstDetails show
+
+ ;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.
+ ShowUninstDetails show
+
+ ;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.
+ InstallColors /windows
+
+ ;This command sets the compression algorithm used to compress files/data in the installer. (http://nsis.sourceforge.net/Reference/SetCompressor)
+ SetCompressor /SOLID lzma
+
+ ;Sets the dictionary size in megabytes (MB) used by the LZMA compressor (default is 8 MB).
+ SetCompressorDictSize 64
+
+ ;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).
+ BrandingText "${PRODUCT_NAME} Installer v${PRODUCT_VER}"
+
+ ;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)
+ Caption "${PRODUCT_NAME}-${PRODUCT_VER}"
+
+ ;Adds the Product Version on top of the Version Tab in the Properties of the file.
+ VIProductVersion 1.0.0.0
+
+ ;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.
+ VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
+ VIAddVersionKey Comments "The installer for ${PRODUCT_NAME}"
+ VIAddVersionKey CompanyName "${PRODUCT_PUBLISHER}"
+ VIAddVersionKey LegalCopyright "2017-2018 ${PRODUCT_PUBLISHER}"
+ VIAddVersionKey FileDescription "${PRODUCT_NAME} Installer"
+ VIAddVersionKey FileVersion ${PRODUCT_VER}
+ VIAddVersionKey ProductVersion ${PRODUCT_VER}
+ VIAddVersionKey InternalName "${PRODUCT_NAME} Installer"
+ VIAddVersionKey LegalTrademarks "${PRODUCT_NAME} is a trademark of ${PRODUCT_PUBLISHER}"
+ VIAddVersionKey OriginalFilename "${PRODUCT_NAME}-${PRODUCT_VER}-setup.exe"
+
+;--------------------------------
+;Interface Settings
+
+ !define MUI_ABORTWARNING
+ !define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?"
+
+ !define MUI_ICON "${HOME}\icons\electrum.ico"
+
+;--------------------------------
+;Pages
+
+ !insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}"
+ !insertmacro MUI_PAGE_DIRECTORY
+ !insertmacro MUI_PAGE_INSTFILES
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+ !insertmacro MUI_LANGUAGE "English"
+
+;--------------------------------
+;Installer Sections
+
+;Check if we have Administrator rights
+Function .onInit
+ UserInfo::GetAccountType
+ pop $0
+ ${If} $0 != "admin" ;Require admin rights on NT4+
+ MessageBox mb_iconstop "Administrator rights required!"
+ SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
+ Quit
+ ${EndIf}
+FunctionEnd
+
+Section
+ SetOutPath $INSTDIR
+
+ ;Files to pack into the installer
+ File "dist\${PRODUCT_NAME}-${PRODUCT_VER}.exe"
+ File "${HOME}\icons\electrum.ico"
+
+ ;Store installation folder
+ WriteRegStr HKCU "Software\${PRODUCT_NAME}-${PRODUCT_VER}" "" $INSTDIR
+
+ ;Create uninstaller
+ DetailPrint "Creating uninstaller..."
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+ ;Create desktop shortcut
+ DetailPrint "Creating desktop shortcut..."
+ CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}-${PRODUCT_VER}.exe" ""
+
+ ;Create start-menu items
+ DetailPrint "Creating start-menu items..."
+ CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}-${PRODUCT_VER}"
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}-${PRODUCT_VER}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}-${PRODUCT_VER}\${PRODUCT_NAME}-${PRODUCT_VER}.lnk" "$INSTDIR\${PRODUCT_NAME}-${PRODUCT_VER}.exe" "" "$INSTDIR\${PRODUCT_NAME}-${PRODUCT_VER}.exe" 0
+
+ ;Links bitcoin: URI's to Electrum
+ WriteRegStr HKCU "Software\Classes\${PRODUCT_NAME}-${PRODUCT_VER}" "" "URL:bitcore Protocol"
+ WriteRegStr HKCU "Software\Classes\${PRODUCT_NAME}-${PRODUCT_VER}" "URL Protocol" ""
+ WriteRegStr HKCU "Software\Classes\${PRODUCT_NAME}-${PRODUCT_VER}" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
+ WriteRegStr HKCU "Software\Classes\${PRODUCT_NAME}-${PRODUCT_VER}\shell\open\command" "" "$\"$INSTDIR\${PRODUCT_NAME}-${PRODUCT_VER}.exe$\" $\"%1$\""
+
+ ;Adds an uninstaller possibility to Windows Uninstall or change a program section
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VER}"
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
+ WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\electrum.ico"
+
+ ;Fixes Windows broken size estimates
+ ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
+ IntFmt $0 "0x%08X" $0
+ WriteRegDWORD HKCU "${PRODUCT_UNINST_KEY}" "EstimatedSize" "$0"
+SectionEnd
+
+;--------------------------------
+;Descriptions
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+ RMDir /r "$INSTDIR\*.*"
+
+ RMDir "$INSTDIR"
+
+ Delete "$DESKTOP\${PRODUCT_NAME}-${PRODUCT_VER}.lnk"
+ Delete "$SMPROGRAMS\${PRODUCT_NAME}-${PRODUCT_VER}\*.*"
+ RMDir "$SMPROGRAMS\${PRODUCT_NAME}-${PRODUCT_VER}"
+
+ DeleteRegKey HKCU "Software\Classes\${PRODUCT_NAME}-${PRODUCT_VER}"
+ DeleteRegKey HKCU "Software\${PRODUCT_NAME}-${PRODUCT_VER}"
+ DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}"
+SectionEnd
diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh
new file mode 100755
index 000000000..ffa31e620
--- /dev/null
+++ b/contrib/build-wine/prepare-wine.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+
+# Please update these carefully, some versions won't work under Wine
+NSIS_FILENAME=nsis-3.03-setup.exe
+NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download
+NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743
+
+ZBAR_FILENAME=zbarw-20121031-setup.exe
+ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download
+ZBAR_SHA256=177e32b272fa76528a3af486b74e9cb356707be1c5ace4ed3fcee9723e2c2c02
+
+LIBUSB_FILENAME=libusb-1.0.22.7z
+LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download
+LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b
+
+PYTHON_VERSION=3.6.6
+
+## These settings probably don't need change
+export WINEPREFIX=/opt/wine64
+#export WINEARCH='win32'
+
+PYHOME=c:/python$PYTHON_VERSION
+PYTHON="wine $PYHOME/python.exe -OO -B"
+
+
+# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
+verify_signature() {
+ local file=$1 keyring=$2 out=
+ if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
+ echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
+ return 0
+ else
+ echo "$out" >&2
+ exit 1
+ fi
+}
+
+verify_hash() {
+ local file=$1 expected_hash=$2
+ actual_hash=$(sha256sum $file | awk '{print $1}')
+ if [ "$actual_hash" == "$expected_hash" ]; then
+ return 0
+ else
+ echo "$file $actual_hash (unexpected hash)" >&2
+ rm "$file"
+ exit 1
+ fi
+}
+
+download_if_not_exist() {
+ local file_name=$1 url=$2
+ if [ ! -e $file_name ] ; then
+ wget -O $PWD/$file_name "$url"
+ fi
+}
+
+# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
+retry() {
+ local result=0
+ local count=1
+ while [ $count -le 3 ]; do
+ [ $result -ne 0 ] && {
+ echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2
+ }
+ ! { "$@"; result=$?; }
+ [ $result -eq 0 ] && break
+ count=$(($count + 1))
+ sleep 1
+ done
+
+ [ $count -gt 3 ] && {
+ echo -e "\nThe command \"$@\" failed 3 times.\n" >&2
+ }
+
+ return $result
+}
+
+# Let's begin!
+here=$(dirname $(readlink -e $0))
+set -e
+
+wine 'wineboot'
+
+# HACK to work around https://bugs.winehq.org/show_bug.cgi?id=42474#c22
+# needed for python 3.6+
+rm -f /opt/wine-stable/lib/wine/fakedlls/api-ms-win-core-path-l1-1-0.dll
+rm -f /opt/wine-stable/lib/wine/api-ms-win-core-path-l1-1-0.dll.so
+
+cd /tmp/electrum-build
+
+# Install Python
+# note: you might need "sudo apt-get install dirmngr" for the following
+# keys from https://www.python.org/downloads/#pubkeys
+KEYLIST_PYTHON_DEV="531F072D39700991925FED0C0EDDC5F26A45C816 26DEA9D4613391EF3E25C9FF0A5B101836580288 CBC547978A3964D14B9AB36A6AF053F07D9DC8D2 C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF 12EF3DC38047DA382D18A5B999CDEA9DA4135B38 8417157EDBE73D9EAC1E539B126EB563A74B06BF DBBF2EEBF925FAADCF1F3FFFD9866941EA5BBD71 2BA0DB82515BBB9EFFAC71C5C9BE28DEE6DF025C 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D C9B104B3DD3AA72D7CCB1066FB9921286F5E1540 97FC712E4C024BBEA48A61ED3A5CA953F73C700D 7ED10B6531D7C8E1BC296021FC624643487034E5"
+KEYRING_PYTHON_DEV="keyring-electrum-build-python-dev.gpg"
+for server in $(shuf -e ha.pool.sks-keyservers.net \
+ hkp://p80.pool.sks-keyservers.net:80 \
+ keyserver.ubuntu.com \
+ hkp://keyserver.ubuntu.com:80) ; do
+ retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver "$server" --recv-keys $KEYLIST_PYTHON_DEV \
+ && break || : ;
+done
+for msifile in core dev exe lib pip tools; do
+ echo "Installing $msifile..."
+ wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi"
+ wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc"
+ verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV
+ wine msiexec /i "${msifile}.msi" /qb TARGETDIR=C:/python$PYTHON_VERSION
+done
+
+# upgrade pip
+$PYTHON -m pip install pip --upgrade
+
+# Install pywin32-ctypes (needed by pyinstaller)
+$PYTHON -m pip install pywin32-ctypes==0.1.2
+
+# install PySocks
+$PYTHON -m pip install win_inet_pton==1.0.1
+
+$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt
+
+# Install PyInstaller
+$PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip
+
+# Install ZBar
+download_if_not_exist $ZBAR_FILENAME "$ZBAR_URL"
+verify_hash $ZBAR_FILENAME "$ZBAR_SHA256"
+wine "$PWD/$ZBAR_FILENAME" /S
+
+# Upgrade setuptools (so Electrum can be installed later)
+$PYTHON -m pip install setuptools --upgrade
+
+# Install NSIS installer
+download_if_not_exist $NSIS_FILENAME "$NSIS_URL"
+verify_hash $NSIS_FILENAME "$NSIS_SHA256"
+wine "$PWD/$NSIS_FILENAME" /S
+
+download_if_not_exist $LIBUSB_FILENAME "$LIBUSB_URL"
+verify_hash $LIBUSB_FILENAME "$LIBUSB_SHA256"
+7z x -olibusb $LIBUSB_FILENAME -aoa
+
+cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/python$PYTHON_VERSION/
+
+# add dlls needed for pyinstaller:
+cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/
+
+mkdir -p $WINEPREFIX/drive_c/tmp
+cp secp256k1/libsecp256k1.dll $WINEPREFIX/drive_c/tmp/
+
+echo "Wine is configured."
diff --git a/contrib/build-wine/sign.sh b/contrib/build-wine/sign.sh
new file mode 100755
index 000000000..724b13dd1
--- /dev/null
+++ b/contrib/build-wine/sign.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+here=$(dirname "$0")
+test -n "$here" -a -d "$here" || exit
+cd $here
+
+CERT_FILE=${CERT_FILE:-~/codesigning/cert.pem}
+KEY_FILE=${KEY_FILE:-~/codesigning/key.pem}
+if [[ ! -f "$CERT_FILE" ]]; then
+ ls $CERT_FILE
+ echo "Make sure that $CERT_FILE and $KEY_FILE exist"
+fi
+
+if ! which osslsigncode > /dev/null 2>&1; then
+ echo "Please install osslsigncode"
+fi
+
+rm -rf signed
+mkdir -p signed >/dev/null 2>&1
+
+cd dist
+echo "Found $(ls *.exe | wc -w) files to sign."
+for f in $(ls *.exe); do
+ echo "Signing $f..."
+ osslsigncode sign \
+ -certs "$CERT_FILE" \
+ -key "$KEY_FILE" \
+ -n "Electrum" \
+ -i "https://electrum.org/" \
+ -t "http://timestamp.digicert.com/" \
+ -in "$f" \
+ -out "../signed/$f"
+ ls ../signed/$f -lah
+done
diff --git a/contrib/build-wine/tmp/LICENSE b/contrib/build-wine/tmp/LICENSE
new file mode 100644
index 000000000..b8bb97185
--- /dev/null
+++ b/contrib/build-wine/tmp/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/contrib/build-wine/unsign.sh b/contrib/build-wine/unsign.sh
new file mode 100755
index 000000000..fd1e5da81
--- /dev/null
+++ b/contrib/build-wine/unsign.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+here=$(dirname "$0")
+test -n "$here" -a -d "$here" || exit
+cd $here
+
+if ! which osslsigncode > /dev/null 2>&1; then
+ echo "Please install osslsigncode"
+ exit
+fi
+
+# exit if command fails
+set -e
+
+mkdir -p signed >/dev/null 2>&1
+mkdir -p signed/stripped >/dev/null 2>&1
+
+version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"`
+
+echo "Found $(ls dist/*.exe | wc -w) files to verify."
+
+for mine in $(ls dist/*.exe); do
+ echo "---------------"
+ f=$(basename $mine)
+ echo "Downloading https://download.electrum.org/$version/$f"
+ wget -q https://download.electrum.org/$version/$f -O signed/$f
+ out="signed/stripped/$f"
+ size=$( wc -c < $mine )
+ # Step 1: Remove PE signature from signed binary
+ osslsigncode remove-signature -in signed/$f -out $out > /dev/null 2>&1
+ # Step 2: Remove checksum and padding from signed binary
+ python3 < 0:
+ if binary[-n:] != bytearray(n):
+ print('expecting failure for', str(pe_file))
+ binary = binary[:size]
+with open(pe_file, "wb") as f:
+ f.write(binary)
+EOF
+ chmod +x $out
+ if cmp -s $out $mine; then
+ echo "Success: $f"
+ gpg --sign --armor --detach signed/$f
+ else
+ echo "Failure: $f"
+ fi
+done
diff --git a/contrib/deterministic-build/check_submodules.sh b/contrib/deterministic-build/check_submodules.sh
new file mode 100755
index 000000000..d9c1b61d5
--- /dev/null
+++ b/contrib/deterministic-build/check_submodules.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+here=$(dirname "$0")
+test -n "$here" -a -d "$here" || exit
+
+cd ${here}/../..
+
+git submodule init
+git submodule update
+
+function get_git_mtime {
+ if [ $# -eq 1 ]; then
+ git log --pretty=%at -n1 -- $1
+ else
+ git log --pretty=%ar -n1 -- $2
+ fi
+}
+
+fail=0
+
+for f in icons/* "icons.qrc"; do
+ if (( $(get_git_mtime "$f") > $(get_git_mtime "contrib/deterministic-build/electrum-icons/") )); then
+ echo "Modification time of $f (" $(get_git_mtime --readable "$f") ") is newer than"\
+ "last update of electrum-icons"
+ fail=1
+ fi
+done
+
+if [ $(date +%s -d "2 weeks ago") -gt $(get_git_mtime "contrib/deterministic-build/electrum-locale/") ]; then
+ echo "Last update from electrum-locale is older than 2 weeks."\
+ "Please update it to incorporate the latest translations from crowdin."
+ fail=1
+fi
+
+exit ${fail}
\ No newline at end of file
diff --git a/contrib/deterministic-build/find_restricted_dependencies.py b/contrib/deterministic-build/find_restricted_dependencies.py
new file mode 100755
index 000000000..1734d5750
--- /dev/null
+++ b/contrib/deterministic-build/find_restricted_dependencies.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+import sys
+
+import requests
+
+
+def check_restriction(p, r):
+ # See: https://www.python.org/dev/peps/pep-0496/
+ # Hopefully we don't need to parse the whole microlanguage
+ if "extra" in r and "[" not in p:
+ return False
+ for marker in ["os_name", "platform_release", "sys_platform", "platform_system"]:
+ if marker in r:
+ return True
+
+
+for p in sys.stdin.read().split():
+ p = p.strip()
+ if not p:
+ continue
+ assert "==" in p, "This script expects a list of packages with pinned version, e.g. package==1.2.3, not {}".format(p)
+ p, v = p.rsplit("==", 1)
+ try:
+ data = requests.get("https://pypi.org/pypi/{}/{}/json".format(p, v)).json()["info"]
+ except ValueError:
+ raise Exception("Package could not be found: {}=={}".format(p, v))
+ try:
+ for r in data["requires_dist"]:
+ if ";" not in r:
+ continue
+ d, restricted = r.split(";", 1)
+ if check_restriction(d, restricted):
+ print(d, sep=" ")
+ print("Installing {} from {} although it is only needed for {}".format(d, p, restricted), file=sys.stderr)
+ except TypeError:
+ # Has no dependencies at all
+ continue
+
diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt
new file mode 100644
index 000000000..02561924b
--- /dev/null
+++ b/contrib/deterministic-build/requirements-binaries.txt
@@ -0,0 +1,56 @@
+pip==10.0.1 \
+ --hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
+ --hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
+pycryptodomex==3.6.4 \
+ --hash=sha256:0461e88a7199f9e88f9f90c2c1e109e9e1f7bbb94dc6192e5df52829d31510c1 \
+ --hash=sha256:08d0aba5a72e8af5da118ac4b6a5d75befceca7dd92a031b040ed5ff4417cec2 \
+ --hash=sha256:0e22d47935d5fa95f556d5f5857576bc6750233964de06a840d58459010c3889 \
+ --hash=sha256:10ef21d1728ec0b8afc4f8e1d8d9ea66f317154ea18731a4a05bd996cdc33fdf \
+ --hash=sha256:1962b81eef81bf5c42d625816904a22a0bd23d15ca5d49891a54e3c0d0189d84 \
+ --hash=sha256:24aae88efe3cbcb4a9cf840b2c352e7de1d6c2c5b3df37ff99b5c7e271e8f3a8 \
+ --hash=sha256:43ad6d1d7ca545d53360bf412ee70fcb9ede876b4376fc6db06fc7328f70588c \
+ --hash=sha256:4daabe7c0404e673b9029aa43761c779b9b4df2cbe11ccd94daded6a0acd8808 \
+ --hash=sha256:4e15af025e02b04b0d0728e8248e4384d3dc7a3a89a020f5bd4d04ef2c5d9d4c \
+ --hash=sha256:5b4d3c4a069a05972e0ed7111071bbcb4727ac652b5d7e8f786e8ea2fe63306b \
+ --hash=sha256:67ad8b2ad15a99ae70e287454a112f67d2abaf160ee9c97f9daebf2296066447 \
+ --hash=sha256:6d7e6fb69d9fd2c57e177f8a9cdf6489a725da77568e3d0a226c7dd18504396a \
+ --hash=sha256:7907d7a5adde7cd07d19f129a4afa892b68b0b52a07eaf989e48e2677040b4bf \
+ --hash=sha256:88210edafd564c8ff4a68716aaf0627e3bc43e9c192a33d6f5616743f72c2d9b \
+ --hash=sha256:8a6b14a90bdcbcdc268acae87126c33bf4250d3842803a93a548d7c10135893a \
+ --hash=sha256:94a10446ad61965516aecd610a2dd28d79ab1dfd8723903e1bd19ffa985c208e \
+ --hash=sha256:99bda900a0bf6f9e6c69bdeb6114f7f6730b9d36a47bc1fe144263ce85bfc403 \
+ --hash=sha256:9dae2e738622bd35ba82fe0b06f773be137a14e6b28defb2e36efc2d809cd28a \
+ --hash=sha256:a04cd6021ff2756c38135a95f81b980485507bccbff4d2b8f62e537552270471 \
+ --hash=sha256:a3b61625b60dd5e72556520a77464e2ac568c20b8ad12ea1f4443bf5051dc624 \
+ --hash=sha256:a9a91fd9e7967a5bad88d542c9fce09323e15d16cb6fa9b8978390e46e68cbdf \
+ --hash=sha256:afc44f1b595bd736ec3762dd9a2d0ef276a6ac560c85f643acfc4c0bf0c73384 \
+ --hash=sha256:b5f3c8912b36e6abb843a51eecb414a1161f80c0ca0b65066c23aa449b5f98db \
+ --hash=sha256:cc07c8b7686dd7093f33067a02b92f4fed860d75ad2bcc4e60624f70fdb94576 \
+ --hash=sha256:da646eddbe026306fd1cb2c392a9aee4ebea13f2a9add9af303bb3151786a5d8 \
+ --hash=sha256:df93eaccd5c09e6380fab8f15c06a89944415e4bb9af64a94f467ce4c782ff8e \
+ --hash=sha256:e667303019770834354c75022ab0324d5ae5bf7cd7015939678033a58f87ee70 \
+ --hash=sha256:f921219040ce994c9118b7218b7f7b4e9394e507c97cfc869ce5358437fc26cd
+PyQt5==5.10.1 \
+ --hash=sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac \
+ --hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \
+ --hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \
+ --hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61
+setuptools==40.0.0 \
+ --hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
+ --hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
+SIP==4.19.8 \
+ --hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \
+ --hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \
+ --hash=sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51 \
+ --hash=sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c \
+ --hash=sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7 \
+ --hash=sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2 \
+ --hash=sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679 \
+ --hash=sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68 \
+ --hash=sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a \
+ --hash=sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85 \
+ --hash=sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8 \
+ --hash=sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174
+wheel==0.31.1 \
+ --hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
+ --hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt
new file mode 100644
index 000000000..ea2d91190
--- /dev/null
+++ b/contrib/deterministic-build/requirements-hw.txt
@@ -0,0 +1,122 @@
+btchip-python==0.1.27 \
+ --hash=sha256:e58a941abbb2d8901bf4858baa18012537c60812c7f895f9a039113ecce3032b
+certifi==2018.4.16 \
+ --hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
+ --hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
+chardet==3.0.4 \
+ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
+ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
+click==6.7 \
+ --hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \
+ --hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b
+Cython==0.28.4 \
+ --hash=sha256:01487236575df8f17b46982071438dce4f7eaf8acc8fb99fca3510d343cd7a28 \
+ --hash=sha256:0671d17c7a27634d6819246e535241b951141ed0e3f6f2a6d618fd32344dae3e \
+ --hash=sha256:0e6190d6971c46729f712dd7307a9c0a8c027bfa5b4d8f2edef106b01759926c \
+ --hash=sha256:202587c754901d0678bd6ff89c707f099987928239049a528470c06c6c922cf8 \
+ --hash=sha256:345197ba9278cf6a914cb7421dc665a0531a219b0072abf6b0cebfdf68e75725 \
+ --hash=sha256:3a296b8d6b02f0e01ab04bedea658f43eef5ad2f8e586a820226ead1a677d9b1 \
+ --hash=sha256:484572a2b22823a967be106137a93f7d634db116b3f7accb37dbd760eda2fa9f \
+ --hash=sha256:4c67c9c803e50ceff32cc5e4769c50fc8ae8df9c4e5cc592ce8310b5a1076d23 \
+ --hash=sha256:539038087c321911745fc2e77049209b1231300d481cb4d682b2f95c724814b3 \
+ --hash=sha256:58113e0683c3688594c112103d7e9f2d0092fd2d8297a220240bea22e184dfdd \
+ --hash=sha256:65cb25ca4284804293a2404d1be3b5a98818be21a72791649bacbcfa4e431d41 \
+ --hash=sha256:699e765da2580e34b08473fc0acef3a2d7bcb7f13eb29401cd25236bcf000080 \
+ --hash=sha256:6b54c3470810cea49a8be90814d05c5325ceb9c5bf429fd86c36fc1b32dfc157 \
+ --hash=sha256:71ac1629e4eae2ed329be8caf45efea10bfe1af3d8767e12e64b83e4ea5a3250 \
+ --hash=sha256:722c179d3df8677f3daf45b1a2764678ed4f0aaddbaa7211a8a08ebfd907c0db \
+ --hash=sha256:76ac2b08d3d956d77b574bb43cbf1d37bd58b9d50c04ba281303e695854ebc46 \
+ --hash=sha256:7eff1157be9e26bf7494288c89979ca69d593a009e2c7420a739e2cf1e0635f5 \
+ --hash=sha256:99546c8696d27d0efa639c77b2f8af6e61dc3a5073caae4f27ffd991ca926f42 \
+ --hash=sha256:a0c263b31d335f29c11f4a9e98fbcd908d0731d4ea99bfd27c1c47caaeb4ca2e \
+ --hash=sha256:a29c66292605bff962adc26530c030607aa699206b12dfb84f131b0454e15df4 \
+ --hash=sha256:a4d3724c5a1ddd86d7d830d8e02c40151839b833791dd4b6fe9e144380fa7d37 \
+ --hash=sha256:aed9f33b19d542eea56c38ef3862ca56147f7903648156cd57eabb0fe47c35d6 \
+ --hash=sha256:b57e733dd8871d2cc7358c2e0fe33027453afffbcd0ea6a537f54877cad5131c \
+ --hash=sha256:d5bf4db62236e82955c40bafbaa18d54b20b5ceefa06fb57c7facc443929f4bd \
+ --hash=sha256:d9272dd71ab78e87fa34a0a59bbd6acc9a9c0005c834a6fc8457ff9619dc6795 \
+ --hash=sha256:e9d5671bcbb90a41b0832fcb3872fcbaca3d68ff11ea09724dd6cbdf31d947fb \
+ --hash=sha256:ee54646afb2b73b293c94cf079682d18d404ebd6c01122dc3980f111aec2d8ae \
+ --hash=sha256:f16a87197939977824609005b73f9ebb291b9653a14e5f27afc1c5d6f981ba39
+ecdsa==0.13 \
+ --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
+ --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
+hidapi==0.7.99.post21 \
+ --hash=sha256:1ac170f4d601c340f2cd52fd06e85c5e77bad7ceac811a7bb54b529f7dc28c24 \
+ --hash=sha256:8d3be666f464347022e2b47caf9132287885d9eacc7895314fc8fefcb4e42946 \
+ --hash=sha256:b4b1f6aff0192e9be153fe07c1b7576cb7a1ff52e78e3f76d867be95301a8e87 \
+ --hash=sha256:bf03f06f586ce7d8aeb697a94b7dba12dc9271aae92d7a8d4486360ff711a660 \
+ --hash=sha256:c76de162937326fcd57aa399f94939ce726242323e65c15c67e183da1f6c26f7 \
+ --hash=sha256:d4ad1e46aef98783a9e6274d523b8b1e766acfc3d72828cd44a337564d984cfa \
+ --hash=sha256:d4b5787a04613503357606bb10e59c3e2c1114fa00ee328b838dd257f41cbd7b \
+ --hash=sha256:e0be1aa6566979266a8fc845ab0e18613f4918cf2c977fe67050f5dc7e2a9a97 \
+ --hash=sha256:edfb16b16a298717cf05b8c8a9ad1828b6ff3de5e93048ceccd74e6ae4ff0922
+idna==2.7 \
+ --hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
+ --hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
+keepkey==4.0.2 \
+ --hash=sha256:cddee60ae405841cdff789cbc54168ceaeb2282633420f2be155554c25c69138
+libusb1==1.6.4 \
+ --hash=sha256:8c930d9c1d037d9c83924c82608aa6a1adcaa01ca0e4a23ee0e8e18d7eee670d
+mnemonic==0.18 \
+ --hash=sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d
+pbkdf2==1.3 \
+ --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979
+pip==10.0.1 \
+ --hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
+ --hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
+protobuf==3.6.0 \
+ --hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
+ --hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
+ --hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
+ --hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
+ --hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
+ --hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
+ --hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
+ --hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
+ --hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
+ --hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
+ --hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
+ --hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
+ --hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
+ --hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
+ --hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
+pyblake2==1.1.2 \
+ --hash=sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9 \
+ --hash=sha256:407e02c7f8f36fcec1b7aa114ddca0c1060c598142ea6f6759d03710b946a7e3 \
+ --hash=sha256:4d47b4a2c1d292b1e460bde1dda4d13aa792ed2ed70fcc263b6bc24632c8e902 \
+ --hash=sha256:5ccc7eb02edb82fafb8adbb90746af71460fbc29aa0f822526fc976dff83e93f \
+ --hash=sha256:8043267fbc0b2f3748c6920591cd0b8b5609dcce60c504c32858aa36206386f2 \
+ --hash=sha256:982295a87907d50f4723db6bc724660da76b6547826d52160171d54f95b919ac \
+ --hash=sha256:baa2190bfe549e36163aa44664d4ee3a9080b236fc5d42f50dc6fd36bbdc749e \
+ --hash=sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4 \
+ --hash=sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358
+requests==2.19.1 \
+ --hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
+ --hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
+safet==0.1.3 \
+ --hash=sha256:ba80fe9f6ba317ab9514a8726cd3792e68eb46dd419f380d48ae4a0ccae646dc \
+ --hash=sha256:e5d8e6a87c8bdf1cefd07004181b93fd7631557fdab09d143ba8d1b29291d6dc
+setuptools==40.0.0 \
+ --hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
+ --hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
+six==1.11.0 \
+ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
+ --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
+trezor==0.10.2 \
+ --hash=sha256:4dba4d5c53d3ca22884d79fb4aa68905fb8353a5da5f96c734645d8cf537138d \
+ --hash=sha256:d2b32f25982ab403758d870df1d0de86d0751c106ef1cd1289f452880ce68b84
+urllib3==1.23 \
+ --hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
+ --hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
+websocket-client==0.48.0 \
+ --hash=sha256:18f1170e6a1b5463986739d9fd45c4308b0d025c1b2f9b88788d8f69e8a5eb4a \
+ --hash=sha256:db70953ae4a064698b27ae56dcad84d0ee68b7b43cb40940f537738f38f510c1
+wheel==0.31.1 \
+ --hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
+ --hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
+pyaes==1.6.1 \
+ --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
+ckcc-protocol==0.7.2 \
+ --hash=sha256:498db4ccdda018cd9f40210f5bd02ddcc98e7df583170b2eab4035c86c3cc03b \
+ --hash=sha256:31ee5178cfba8895eb2a6b8d06dc7830b51461a0ff767a670a64707c63e6b264
diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt
new file mode 100644
index 000000000..1b226f9dd
--- /dev/null
+++ b/contrib/deterministic-build/requirements.txt
@@ -0,0 +1,69 @@
+certifi==2018.4.16 \
+ --hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
+ --hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
+chardet==3.0.4 \
+ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
+ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
+dnspython==1.15.0 \
+ --hash=sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c \
+ --hash=sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31
+ecdsa==0.13 \
+ --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
+ --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
+idna==2.7 \
+ --hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
+ --hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
+jsonrpclib-pelix==0.3.1 \
+ --hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \
+ --hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4
+pip==10.0.1 \
+ --hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
+ --hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
+protobuf==3.6.0 \
+ --hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
+ --hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
+ --hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
+ --hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
+ --hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
+ --hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
+ --hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
+ --hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
+ --hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
+ --hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
+ --hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
+ --hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
+ --hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
+ --hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
+ --hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
+pyaes==1.6.1 \
+ --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
+PySocks==1.6.8 \
+ --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672
+QDarkStyle==2.5.4 \
+ --hash=sha256:3eb60922b8c4d9cedecb6897ca4c9f8a259d81bdefe5791976ccdf12432de1f0 \
+ --hash=sha256:51331fc6490b38c376e6ba8d8c814320c8d2d1c2663055bc396321a7c28fa8be
+qrcode==6.0 \
+ --hash=sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf \
+ --hash=sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3
+requests==2.19.1 \
+ --hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
+ --hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
+setuptools==40.0.0 \
+ --hash=sha256:012adb8e25fbfd64c652e99e7bab58799a3aaf05d39ab38561f69190a909015f \
+ --hash=sha256:d68abee4eed409fbe8c302ac4d8429a1ffef912cd047a903b5701c024048dd49
+six==1.11.0 \
+ --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
+ --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
+typing==3.6.4 \
+ --hash=sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf \
+ --hash=sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8 \
+ --hash=sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2
+urllib3==1.23 \
+ --hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
+ --hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
+wheel==0.31.1 \
+ --hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
+ --hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
+colorama==0.3.9 \
+ --hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \
+ --hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1
diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh
new file mode 100755
index 000000000..19d6b5fcc
--- /dev/null
+++ b/contrib/freeze_packages.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Run this after a new release to update dependencies
+
+venv_dir=~/.electrum-venv
+contrib=$(dirname "$0")
+
+which virtualenv > /dev/null 2>&1 || { echo "Please install virtualenv" && exit 1; }
+python3 -m hashin -h > /dev/null 2>&1 || { python3 -m pip install hashin; }
+other_python=$(which python3)
+
+for i in '' '-hw' '-binaries'; do
+ rm -rf "$venv_dir"
+ virtualenv -p $(which python3) $venv_dir
+
+ source $venv_dir/bin/activate
+
+ echo "Installing $m dependencies"
+
+ python -m pip install -r $contrib/requirements/requirements${i}.txt --upgrade
+
+ echo "OK."
+
+ requirements=$(pip freeze --all)
+ restricted=$(echo $requirements | $other_python $contrib/deterministic-build/find_restricted_dependencies.py)
+ requirements="$requirements $restricted"
+
+ echo "Generating package hashes..."
+ rm $contrib/deterministic-build/requirements${i}.txt
+ touch $contrib/deterministic-build/requirements${i}.txt
+
+ for requirement in $requirements; do
+ echo -e "\r Hashing $requirement..."
+ $other_python -m hashin -r $contrib/deterministic-build/requirements${i}.txt ${requirement}
+ done
+
+ echo "OK."
+done
+
+echo "Done. Updated requirements"
diff --git a/contrib/make_apk b/contrib/make_apk
new file mode 100755
index 000000000..773aeab54
--- /dev/null
+++ b/contrib/make_apk
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+pushd ./electrum/gui/kivy/
+
+if [[ -n "$1" && "$1" == "release" ]] ; then
+ echo -n Keystore Password:
+ read -s password
+ export P4A_RELEASE_KEYSTORE=~/.keystore
+ export P4A_RELEASE_KEYSTORE_PASSWD=$password
+ export P4A_RELEASE_KEYALIAS_PASSWD=$password
+ export P4A_RELEASE_KEYALIAS=electrum
+ make release
+else
+ make apk
+fi
+
+popd
diff --git a/contrib/make_download b/contrib/make_download
new file mode 100755
index 000000000..097fbf06d
--- /dev/null
+++ b/contrib/make_download
@@ -0,0 +1,57 @@
+#!/usr/bin/python3
+import re
+import os
+import sys
+
+from electrum.version import ELECTRUM_VERSION, APK_VERSION
+print("version", ELECTRUM_VERSION)
+
+dirname = sys.argv[1]
+print("directory", dirname)
+
+download_page = os.path.join(dirname, "panel-download.html")
+download_template = download_page + ".template"
+
+with open(download_template) as f:
+ string = f.read()
+
+version = version_win = version_mac = version_android = ELECTRUM_VERSION
+string = string.replace("##VERSION##", version)
+string = string.replace("##VERSION_WIN##", version_win)
+string = string.replace("##VERSION_MAC##", version_mac)
+string = string.replace("##VERSION_ANDROID##", version_android)
+string = string.replace("##VERSION_APK##", APK_VERSION)
+
+files = {
+ 'tgz': "Electrum-%s.tar.gz" % version,
+ 'zip': "Electrum-%s.zip" % version,
+ 'mac': "electrum-%s.dmg" % version_mac,
+ 'win': "electrum-%s.exe" % version_win,
+ 'win_setup': "electrum-%s-setup.exe" % version_win,
+ 'win_portable': "electrum-%s-portable.exe" % version_win,
+}
+
+for k, n in files.items():
+ path = "dist/%s"%n
+ link = "https://download.electrum.org/%s/%s"%(version,n)
+ if not os.path.exists(path):
+ os.system("wget -q %s -O %s" % (link, path))
+ if not os.path.getsize(path):
+ os.unlink(path)
+ string = re.sub("(.*?)
"%k, '', string, flags=re.DOTALL + re.MULTILINE)
+ continue
+ sigpath = path + '.asc'
+ siglink = link + '.asc'
+ if not os.path.exists(sigpath):
+ os.system("wget -q %s -O %s" % (siglink, sigpath))
+ if not os.path.getsize(sigpath):
+ os.unlink(sigpath)
+ string = re.sub("(.*?)
"%k, '', string, flags=re.DOTALL + re.MULTILINE)
+ continue
+ if os.system("gpg --verify %s"%sigpath) != 0:
+ raise Exception(sigpath)
+ string = string.replace("##link_%s##"%k, link)
+
+
+with open(download_page,'w') as f:
+ f.write(string)
diff --git a/contrib/make_locale b/contrib/make_locale
new file mode 100755
index 000000000..3c28d5702
--- /dev/null
+++ b/contrib/make_locale
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+import os
+import subprocess
+import io
+import zipfile
+import requests
+
+os.chdir(os.path.dirname(os.path.realpath(__file__)))
+os.chdir('..')
+
+cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
+
+files = subprocess.check_output(cmd, shell=True)
+
+with open("app.fil", "wb") as f:
+ f.write(files)
+
+print("Found {} files to translate".format(len(files.splitlines())))
+
+# Generate fresh translation template
+if not os.path.exists('electrum/locale'):
+ os.mkdir('electrum/locale')
+cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
+print('Generate template')
+os.system(cmd)
+
+os.chdir('electrum')
+
+crowdin_identifier = 'electrum'
+crowdin_file_name = 'files[electrum-client/messages.pot]'
+locale_file_name = 'locale/messages.pot'
+crowdin_api_key = None
+
+filename = os.path.expanduser('~/.crowdin_api_key')
+if os.path.exists(filename):
+ with open(filename) as f:
+ crowdin_api_key = f.read().strip()
+
+if "crowdin_api_key" in os.environ:
+ crowdin_api_key = os.environ["crowdin_api_key"]
+
+if crowdin_api_key:
+ # Push to Crowdin
+ print('Push to Crowdin')
+ url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key)
+ with open(locale_file_name, 'rb') as f:
+ files = {crowdin_file_name: f}
+ response = requests.request('POST', url, files=files)
+ print("", "update-file:", "-"*20, response.text, "-"*20, sep="\n")
+ # Build translations
+ print('Build translations')
+ response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key)
+ print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n")
+
+# Download & unzip
+print('Download translations')
+s = requests.request('GET', 'https://crowdin.com/backend/download/project/' + crowdin_identifier + '.zip').content
+zfobj = zipfile.ZipFile(io.BytesIO(s))
+
+print('Unzip translations')
+for name in zfobj.namelist():
+ if not name.startswith('electrum-client/locale'):
+ continue
+ if name.endswith('/'):
+ if not os.path.exists(name[16:]):
+ os.mkdir(name[16:])
+ else:
+ with open(name[16:], 'wb') as output:
+ output.write(zfobj.read(name))
+
+# Convert .po to .mo
+print('Installing')
+for lang in os.listdir('locale'):
+ if lang.startswith('messages'):
+ continue
+ # Check LC_MESSAGES folder
+ mo_dir = 'locale/%s/LC_MESSAGES' % lang
+ if not os.path.exists(mo_dir):
+ os.mkdir(mo_dir)
+ cmd = 'msgfmt --output-file="%s/electrum.mo" "locale/%s/electrum.po"' % (mo_dir,lang)
+ print('Installing', lang)
+ os.system(cmd)
diff --git a/contrib/make_packages b/contrib/make_packages
new file mode 100755
index 000000000..9cfd32bb2
--- /dev/null
+++ b/contrib/make_packages
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+contrib=$(dirname "$0")
+test -n "$contrib" -a -d "$contrib" || exit
+
+whereis pip3
+if [ $? -ne 0 ] ; then echo "Install pip3" ; exit ; fi
+
+rm "$contrib"/../packages/ -r
+
+#Install pure python modules in electrum directory
+pip3 install -r $contrib/deterministic-build/requirements.txt -t $contrib/../packages
+
diff --git a/contrib/make_tgz b/contrib/make_tgz
new file mode 100755
index 000000000..9e53dd288
--- /dev/null
+++ b/contrib/make_tgz
@@ -0,0 +1 @@
+python3 setup.py sdist --format=zip,gztar
diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt
new file mode 100644
index 000000000..9faf682e9
--- /dev/null
+++ b/contrib/requirements/requirements-binaries.txt
@@ -0,0 +1,2 @@
+PyQt5<5.11
+pycryptodomex
diff --git a/contrib/requirements/requirements-hw.txt b/contrib/requirements/requirements-hw.txt
new file mode 100644
index 000000000..a6ae0a3a5
--- /dev/null
+++ b/contrib/requirements/requirements-hw.txt
@@ -0,0 +1,8 @@
+Cython>=0.27
+trezor[hidapi]>=0.9.0
+safet[hidapi]>=0.1.0
+keepkey
+btchip-python
+ckcc-protocol>=0.7.2
+websocket-client
+hidapi
diff --git a/contrib/requirements/requirements-travis.txt b/contrib/requirements/requirements-travis.txt
new file mode 100644
index 000000000..b0aaeff51
--- /dev/null
+++ b/contrib/requirements/requirements-travis.txt
@@ -0,0 +1,3 @@
+tox
+python-coveralls
+tox-travis
diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt
new file mode 100644
index 000000000..99b859c30
--- /dev/null
+++ b/contrib/requirements/requirements.txt
@@ -0,0 +1,10 @@
+pyaes>=0.1a1
+ecdsa>=0.9
+requests
+qrcode
+protobuf
+dnspython
+jsonrpclib-pelix
+PySocks>=1.6.6
+qdarkstyle<3.0
+typing>=3.0.0
diff --git a/contrib/sign_packages b/contrib/sign_packages
new file mode 100755
index 000000000..d11ef5fc3
--- /dev/null
+++ b/contrib/sign_packages
@@ -0,0 +1,18 @@
+#!/usr/bin/python2
+
+import os
+import getpass
+
+if __name__ == '__main__':
+
+ os.chdir("dist")
+ password = getpass.getpass("Password:")
+ for f in os.listdir('.'):
+ if f.endswith('asc'):
+ continue
+ os.system( "gpg --sign --armor --detach --passphrase \"%s\" %s"%(password, f) )
+
+ os.chdir("..")
+
+
+
diff --git a/contrib/upload b/contrib/upload
new file mode 100755
index 000000000..29c7e0276
--- /dev/null
+++ b/contrib/upload
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -e
+
+version=`git describe --tags`
+echo $version
+
+here=$(dirname "$0")
+cd $here/../dist
+
+sftp -oBatchMode=no -b - thomasv@download.electrum.org << !
+ cd electrum-downloads
+ mkdir $version
+ cd $version
+ mput *
+ bye
+!
\ No newline at end of file
diff --git a/electrum-env b/electrum-env
new file mode 100755
index 000000000..71dfd5958
--- /dev/null
+++ b/electrum-env
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# This script creates a virtualenv named 'env' and installs all
+# python dependencies before activating the env and running Electrum.
+# If 'env' already exists, it is activated and Electrum is started
+# without any installations. Additionally, the PYTHONPATH environment
+# variable is set properly before running Electrum.
+#
+# python-qt and its dependencies will still need to be installed with
+# your package manager.
+
+PYTHON_VER="$(python3 -c 'import sys; print(sys.version[:3])')"
+
+cd $(dirname $0)
+if [ -e ./env/bin/activate ]; then
+ source ./env/bin/activate
+else
+ virtualenv env -p `which python3`
+ source ./env/bin/activate
+ python3 setup.py install
+fi
+
+export PYTHONPATH="/usr/local/lib/python${PYTHON_VER}/site-packages:$PYTHONPATH"
+
+./run_electrum "$@"
+
+deactivate
diff --git a/electrum.conf.sample b/electrum.conf.sample
new file mode 100644
index 000000000..72a50b238
--- /dev/null
+++ b/electrum.conf.sample
@@ -0,0 +1,16 @@
+# Configuration file for the Electrum client
+# Settings defined here are shared across wallets
+#
+# copy this file to /etc/electrum.conf if you want read-only settings
+
+[client]
+server = electrum.novit.ro:50001:t
+proxy = None
+gap_limit = 5
+# booleans use python syntax
+use_change = True
+gui = qt
+num_zeros = 2
+# default transaction fee is in Satoshis
+fee = 10000
+winpos-qt = [799, 226, 877, 435]
diff --git a/electrum.desktop b/electrum.desktop
new file mode 100644
index 000000000..2eba0b6f7
--- /dev/null
+++ b/electrum.desktop
@@ -0,0 +1,21 @@
+# If you want Electrum to appear in a Linux app launcher ("start menu"), install this by doing:
+# sudo desktop-file-install electrum.desktop
+
+[Desktop Entry]
+Comment=Lightweight Bitcore Client
+Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum %u"
+GenericName[en_US]=Bitcore Wallet
+GenericName=Bitcore Wallet
+Icon=electrumBTX
+Name[en_US]=Electrum Bitcore Wallet
+Name=Electrum Bitcore Wallet
+Categories=Finance;Network;
+StartupNotify=false
+Terminal=false
+Type=Application
+MimeType=x-scheme-handler/bitcore;
+Actions=Testnet;
+
+[Desktop Action Testnet]
+Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum-btx --testnet %u"
+Name=Testnet mode
diff --git a/electrum.icns b/electrum.icns
new file mode 100644
index 000000000..977b124d0
Binary files /dev/null and b/electrum.icns differ
diff --git a/electrum/__init__.py b/electrum/__init__.py
new file mode 100644
index 000000000..48a60c15c
--- /dev/null
+++ b/electrum/__init__.py
@@ -0,0 +1,14 @@
+from .version import ELECTRUM_VERSION
+from .util import format_satoshis, print_msg, print_error, set_verbosity
+from .wallet import Wallet
+from .storage import WalletStorage
+from .coinchooser import COIN_CHOOSERS
+from .network import Network, pick_random_server
+from .interface import Connection, Interface
+from .simple_config import SimpleConfig, get_config, set_config
+from . import bitcoin
+from . import transaction
+from . import daemon
+from .transaction import Transaction
+from .plugin import BasePlugin
+from .commands import Commands, known_commands
diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
new file mode 100644
index 000000000..4aa6f2880
--- /dev/null
+++ b/electrum/address_synchronizer.py
@@ -0,0 +1,794 @@
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 The Electrum Developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import threading
+import itertools
+from collections import defaultdict
+
+from . import bitcoin
+from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
+from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus
+from .transaction import Transaction, TxOutput
+from .synchronizer import Synchronizer
+from .verifier import SPV
+from .blockchain import hash_header
+from .i18n import _
+
+TX_HEIGHT_LOCAL = -2
+TX_HEIGHT_UNCONF_PARENT = -1
+TX_HEIGHT_UNCONFIRMED = 0
+
+class AddTransactionException(Exception):
+ pass
+
+
+class UnrelatedTransactionException(AddTransactionException):
+ def __str__(self):
+ return _("Transaction is unrelated to this wallet.")
+
+
+class AddressSynchronizer(PrintError):
+ """
+ inherited by wallet
+ """
+
+ def __init__(self, storage):
+ self.storage = storage
+ self.network = None
+ # verifier (SPV) and synchronizer are started in start_threads
+ self.synchronizer = None
+ self.verifier = None
+ # locks: if you need to take multiple ones, acquire them in the order they are defined here!
+ self.lock = threading.RLock()
+ self.transaction_lock = threading.RLock()
+ # address -> list(txid, height)
+ self.history = storage.get('addr_history',{})
+ # Verified transactions. txid -> VerifiedTxInfo. Access with self.lock.
+ verified_tx = storage.get('verified_tx3', {})
+ self.verified_tx = {}
+ for txid, (height, timestamp, txpos, header_hash) in verified_tx.items():
+ self.verified_tx[txid] = VerifiedTxInfo(height, timestamp, txpos, header_hash)
+ # Transactions pending verification. txid -> tx_height. Access with self.lock.
+ self.unverified_tx = defaultdict(int)
+ # true when synchronized
+ self.up_to_date = False
+ # thread local storage for caching stuff
+ self.threadlocal_cache = threading.local()
+
+ self.load_and_cleanup()
+
+ def load_and_cleanup(self):
+ self.load_transactions()
+ self.load_local_history()
+ self.check_history()
+ self.load_unverified_transactions()
+ self.remove_local_transactions_we_dont_have()
+
+ def is_mine(self, address):
+ return address in self.history
+
+ def get_addresses(self):
+ return sorted(self.history.keys())
+
+ def get_address_history(self, addr):
+ h = []
+ # we need self.transaction_lock but get_tx_height will take self.lock
+ # so we need to take that too here, to enforce order of locks
+ with self.lock, self.transaction_lock:
+ related_txns = self._history_local.get(addr, set())
+ for tx_hash in related_txns:
+ tx_height = self.get_tx_height(tx_hash).height
+ h.append((tx_hash, tx_height))
+ return h
+
+ def get_address_history_len(self, addr: str) -> int:
+ """Return number of transactions where address is involved."""
+ return len(self._history_local.get(addr, ()))
+
+ def get_txin_address(self, txi):
+ addr = txi.get('address')
+ if addr and addr != "(pubkey)":
+ return addr
+ prevout_hash = txi.get('prevout_hash')
+ prevout_n = txi.get('prevout_n')
+ dd = self.txo.get(prevout_hash, {})
+ for addr, l in dd.items():
+ for n, v, is_cb in l:
+ if n == prevout_n:
+ return addr
+ return None
+
+ def get_txout_address(self, txo: TxOutput):
+ if txo.type == TYPE_ADDRESS:
+ addr = txo.address
+ elif txo.type == TYPE_PUBKEY:
+ addr = bitcoin.public_key_to_p2pkh(bfh(txo.address))
+ else:
+ addr = None
+ return addr
+
+ def load_unverified_transactions(self):
+ # review transactions that are in the history
+ for addr, hist in self.history.items():
+ for tx_hash, tx_height in hist:
+ # add it in case it was previously unconfirmed
+ self.add_unverified_tx(tx_hash, tx_height)
+
+ def start_threads(self, network):
+ self.network = network
+ if self.network is not None:
+ self.verifier = SPV(self.network, self)
+ self.synchronizer = Synchronizer(self, network)
+ network.add_jobs([self.verifier, self.synchronizer])
+ else:
+ self.verifier = None
+ self.synchronizer = None
+
+ def stop_threads(self):
+ if self.network:
+ self.network.remove_jobs([self.synchronizer, self.verifier])
+ self.synchronizer.release()
+ self.synchronizer = None
+ self.verifier = None
+ # Now no references to the synchronizer or verifier
+ # remain so they will be GC-ed
+ self.storage.put('stored_height', self.get_local_height())
+ self.save_transactions()
+ self.save_verified_tx()
+ self.storage.write()
+
+ def add_address(self, address):
+ if address not in self.history:
+ self.history[address] = []
+ self.set_up_to_date(False)
+ if self.synchronizer:
+ self.synchronizer.add(address)
+
+ def get_conflicting_transactions(self, tx):
+ """Returns a set of transaction hashes from the wallet history that are
+ directly conflicting with tx, i.e. they have common outpoints being
+ spent with tx. If the tx is already in wallet history, that will not be
+ reported as a conflict.
+ """
+ conflicting_txns = set()
+ with self.transaction_lock:
+ for txin in tx.inputs():
+ if txin['type'] == 'coinbase':
+ continue
+ prevout_hash = txin['prevout_hash']
+ prevout_n = txin['prevout_n']
+ spending_tx_hash = self.spent_outpoints[prevout_hash].get(prevout_n)
+ if spending_tx_hash is None:
+ continue
+ # this outpoint has already been spent, by spending_tx
+ assert spending_tx_hash in self.transactions
+ conflicting_txns |= {spending_tx_hash}
+ txid = tx.txid()
+ if txid in conflicting_txns:
+ # this tx is already in history, so it conflicts with itself
+ if len(conflicting_txns) > 1:
+ raise Exception('Found conflicting transactions already in wallet history.')
+ conflicting_txns -= {txid}
+ return conflicting_txns
+
+ def add_transaction(self, tx_hash, tx, allow_unrelated=False):
+ assert tx_hash, tx_hash
+ assert tx, tx
+ assert tx.is_complete()
+ # we need self.transaction_lock but get_tx_height will take self.lock
+ # so we need to take that too here, to enforce order of locks
+ with self.lock, self.transaction_lock:
+ # NOTE: returning if tx in self.transactions might seem like a good idea
+ # BUT we track is_mine inputs in a txn, and during subsequent calls
+ # of add_transaction tx, we might learn of more-and-more inputs of
+ # being is_mine, as we roll the gap_limit forward
+ is_coinbase = tx.inputs()[0]['type'] == 'coinbase'
+ tx_height = self.get_tx_height(tx_hash).height
+ if not allow_unrelated:
+ # note that during sync, if the transactions are not properly sorted,
+ # it could happen that we think tx is unrelated but actually one of the inputs is is_mine.
+ # this is the main motivation for allow_unrelated
+ is_mine = any([self.is_mine(self.get_txin_address(txin)) for txin in tx.inputs()])
+ is_for_me = any([self.is_mine(self.get_txout_address(txo)) for txo in tx.outputs()])
+ if not is_mine and not is_for_me:
+ raise UnrelatedTransactionException()
+ # Find all conflicting transactions.
+ # In case of a conflict,
+ # 1. confirmed > mempool > local
+ # 2. this new txn has priority over existing ones
+ # When this method exits, there must NOT be any conflict, so
+ # either keep this txn and remove all conflicting (along with dependencies)
+ # or drop this txn
+ conflicting_txns = self.get_conflicting_transactions(tx)
+ if conflicting_txns:
+ existing_mempool_txn = any(
+ self.get_tx_height(tx_hash2).height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT)
+ for tx_hash2 in conflicting_txns)
+ existing_confirmed_txn = any(
+ self.get_tx_height(tx_hash2).height > 0
+ for tx_hash2 in conflicting_txns)
+ if existing_confirmed_txn and tx_height <= 0:
+ # this is a non-confirmed tx that conflicts with confirmed txns; drop.
+ return False
+ if existing_mempool_txn and tx_height == TX_HEIGHT_LOCAL:
+ # this is a local tx that conflicts with non-local txns; drop.
+ return False
+ # keep this txn and remove all conflicting
+ to_remove = set()
+ to_remove |= conflicting_txns
+ for conflicting_tx_hash in conflicting_txns:
+ to_remove |= self.get_depending_transactions(conflicting_tx_hash)
+ for tx_hash2 in to_remove:
+ self.remove_transaction(tx_hash2)
+ # add inputs
+ def add_value_from_prev_output():
+ dd = self.txo.get(prevout_hash, {})
+ # note: this nested loop takes linear time in num is_mine outputs of prev_tx
+ for addr, outputs in dd.items():
+ # note: instead of [(n, v, is_cb), ...]; we could store: {n -> (v, is_cb)}
+ for n, v, is_cb in outputs:
+ if n == prevout_n:
+ if addr and self.is_mine(addr):
+ if d.get(addr) is None:
+ d[addr] = set()
+ d[addr].add((ser, v))
+ return
+ self.txi[tx_hash] = d = {}
+ for txi in tx.inputs():
+ if txi['type'] == 'coinbase':
+ continue
+ prevout_hash = txi['prevout_hash']
+ prevout_n = txi['prevout_n']
+ ser = prevout_hash + ':%d' % prevout_n
+ self.spent_outpoints[prevout_hash][prevout_n] = tx_hash
+ add_value_from_prev_output()
+ # add outputs
+ self.txo[tx_hash] = d = {}
+ for n, txo in enumerate(tx.outputs()):
+ v = txo[2]
+ ser = tx_hash + ':%d'%n
+ addr = self.get_txout_address(txo)
+ if addr and self.is_mine(addr):
+ if d.get(addr) is None:
+ d[addr] = []
+ d[addr].append((n, v, is_coinbase))
+ # give v to txi that spends me
+ next_tx = self.spent_outpoints[tx_hash].get(n)
+ if next_tx is not None:
+ dd = self.txi.get(next_tx, {})
+ if dd.get(addr) is None:
+ dd[addr] = set()
+ if (ser, v) not in dd[addr]:
+ dd[addr].add((ser, v))
+ self._add_tx_to_local_history(next_tx)
+ # add to local history
+ self._add_tx_to_local_history(tx_hash)
+ # save
+ self.transactions[tx_hash] = tx
+ return True
+
+ def remove_transaction(self, tx_hash):
+ def remove_from_spent_outpoints():
+ # undo spends in spent_outpoints
+ if tx is not None: # if we have the tx, this branch is faster
+ for txin in tx.inputs():
+ if txin['type'] == 'coinbase':
+ continue
+ prevout_hash = txin['prevout_hash']
+ prevout_n = txin['prevout_n']
+ self.spent_outpoints[prevout_hash].pop(prevout_n, None)
+ if not self.spent_outpoints[prevout_hash]:
+ self.spent_outpoints.pop(prevout_hash)
+ else: # expensive but always works
+ for prevout_hash, d in list(self.spent_outpoints.items()):
+ for prevout_n, spending_txid in d.items():
+ if spending_txid == tx_hash:
+ self.spent_outpoints[prevout_hash].pop(prevout_n, None)
+ if not self.spent_outpoints[prevout_hash]:
+ self.spent_outpoints.pop(prevout_hash)
+ # Remove this tx itself; if nothing spends from it.
+ # It is not so clear what to do if other txns spend from it, but it will be
+ # removed when those other txns are removed.
+ if not self.spent_outpoints[tx_hash]:
+ self.spent_outpoints.pop(tx_hash)
+
+ with self.transaction_lock:
+ self.print_error("removing tx from history", tx_hash)
+ tx = self.transactions.pop(tx_hash, None)
+ remove_from_spent_outpoints()
+ self._remove_tx_from_local_history(tx_hash)
+ self.txi.pop(tx_hash, None)
+ self.txo.pop(tx_hash, None)
+
+ def get_depending_transactions(self, tx_hash):
+ """Returns all (grand-)children of tx_hash in this wallet."""
+ children = set()
+ for other_hash in self.spent_outpoints[tx_hash].values():
+ children.add(other_hash)
+ children |= self.get_depending_transactions(other_hash)
+ return children
+
+ def receive_tx_callback(self, tx_hash, tx, tx_height):
+ self.add_unverified_tx(tx_hash, tx_height)
+ self.add_transaction(tx_hash, tx, allow_unrelated=True)
+
+ def receive_history_callback(self, addr, hist, tx_fees):
+ with self.lock:
+ old_hist = self.get_address_history(addr)
+ for tx_hash, height in old_hist:
+ if (tx_hash, height) not in hist:
+ # make tx local
+ self.unverified_tx.pop(tx_hash, None)
+ self.verified_tx.pop(tx_hash, None)
+ if self.verifier:
+ self.verifier.remove_spv_proof_for_tx(tx_hash)
+ self.history[addr] = hist
+
+ for tx_hash, tx_height in hist:
+ # add it in case it was previously unconfirmed
+ self.add_unverified_tx(tx_hash, tx_height)
+ # if addr is new, we have to recompute txi and txo
+ tx = self.transactions.get(tx_hash)
+ if tx is None:
+ continue
+ self.add_transaction(tx_hash, tx, allow_unrelated=True)
+
+ # Store fees
+ self.tx_fees.update(tx_fees)
+
+ @profiler
+ def load_transactions(self):
+ # load txi, txo, tx_fees
+ self.txi = self.storage.get('txi', {})
+ for txid, d in list(self.txi.items()):
+ for addr, lst in d.items():
+ self.txi[txid][addr] = set([tuple(x) for x in lst])
+ self.txo = self.storage.get('txo', {})
+ self.tx_fees = self.storage.get('tx_fees', {})
+ tx_list = self.storage.get('transactions', {})
+ # load transactions
+ self.transactions = {}
+ for tx_hash, raw in tx_list.items():
+ tx = Transaction(raw)
+ self.transactions[tx_hash] = tx
+ if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None:
+ self.print_error("removing unreferenced tx", tx_hash)
+ self.transactions.pop(tx_hash)
+ # load spent_outpoints
+ _spent_outpoints = self.storage.get('spent_outpoints', {})
+ self.spent_outpoints = defaultdict(dict)
+ for prevout_hash, d in _spent_outpoints.items():
+ for prevout_n_str, spending_txid in d.items():
+ prevout_n = int(prevout_n_str)
+ self.spent_outpoints[prevout_hash][prevout_n] = spending_txid
+
+ @profiler
+ def load_local_history(self):
+ self._history_local = {} # address -> set(txid)
+ for txid in itertools.chain(self.txi, self.txo):
+ self._add_tx_to_local_history(txid)
+
+ @profiler
+ def check_history(self):
+ save = False
+ hist_addrs_mine = list(filter(lambda k: self.is_mine(k), self.history.keys()))
+ hist_addrs_not_mine = list(filter(lambda k: not self.is_mine(k), self.history.keys()))
+ for addr in hist_addrs_not_mine:
+ self.history.pop(addr)
+ save = True
+ for addr in hist_addrs_mine:
+ hist = self.history[addr]
+ for tx_hash, tx_height in hist:
+ if self.txi.get(tx_hash) or self.txo.get(tx_hash):
+ continue
+ tx = self.transactions.get(tx_hash)
+ if tx is not None:
+ self.add_transaction(tx_hash, tx, allow_unrelated=True)
+ save = True
+ if save:
+ self.save_transactions()
+
+ def remove_local_transactions_we_dont_have(self):
+ txid_set = set(self.txi) | set(self.txo)
+ for txid in txid_set:
+ tx_height = self.get_tx_height(txid).height
+ if tx_height == TX_HEIGHT_LOCAL and txid not in self.transactions:
+ self.remove_transaction(txid)
+
+ @profiler
+ def save_transactions(self, write=False):
+ with self.transaction_lock:
+ tx = {}
+ for k,v in self.transactions.items():
+ tx[k] = str(v)
+ self.storage.put('transactions', tx)
+ self.storage.put('txi', self.txi)
+ self.storage.put('txo', self.txo)
+ self.storage.put('tx_fees', self.tx_fees)
+ self.storage.put('addr_history', self.history)
+ self.storage.put('spent_outpoints', self.spent_outpoints)
+ if write:
+ self.storage.write()
+
+ def save_verified_tx(self, write=False):
+ with self.lock:
+ self.storage.put('verified_tx3', self.verified_tx)
+ if write:
+ self.storage.write()
+
+ def clear_history(self):
+ with self.lock:
+ with self.transaction_lock:
+ self.txi = {}
+ self.txo = {}
+ self.tx_fees = {}
+ self.spent_outpoints = defaultdict(dict)
+ self.history = {}
+ self.verified_tx = {}
+ self.transactions = {}
+ self.save_transactions()
+
+ def get_txpos(self, tx_hash):
+ """Returns (height, txpos) tuple, even if the tx is unverified."""
+ with self.lock:
+ if tx_hash in self.verified_tx:
+ info = self.verified_tx[tx_hash]
+ return info.height, info.txpos
+ elif tx_hash in self.unverified_tx:
+ height = self.unverified_tx[tx_hash]
+ return (height, 0) if height > 0 else ((1e9 - height), 0)
+ else:
+ return (1e9+1, 0)
+
+ def with_local_height_cached(func):
+ # get local height only once, as it's relatively expensive.
+ # take care that nested calls work as expected
+ def f(self, *args, **kwargs):
+ orig_val = getattr(self.threadlocal_cache, 'local_height', None)
+ self.threadlocal_cache.local_height = orig_val or self.get_local_height()
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ self.threadlocal_cache.local_height = orig_val
+ return f
+
+ @with_local_height_cached
+ def get_history(self, domain=None):
+ # get domain
+ if domain is None:
+ domain = self.history.keys()
+ domain = set(domain)
+ # 1. Get the history of each address in the domain, maintain the
+ # delta of a tx as the sum of its deltas on domain addresses
+ tx_deltas = defaultdict(int)
+ for addr in domain:
+ h = self.get_address_history(addr)
+ for tx_hash, height in h:
+ delta = self.get_tx_delta(tx_hash, addr)
+ if delta is None or tx_deltas[tx_hash] is None:
+ tx_deltas[tx_hash] = None
+ else:
+ tx_deltas[tx_hash] += delta
+ # 2. create sorted history
+ history = []
+ for tx_hash in tx_deltas:
+ delta = tx_deltas[tx_hash]
+ tx_mined_status = self.get_tx_height(tx_hash)
+ history.append((tx_hash, tx_mined_status, delta))
+ history.sort(key = lambda x: self.get_txpos(x[0]))
+ history.reverse()
+ # 3. add balance
+ c, u, x = self.get_balance(domain)
+ balance = c + u + x
+ h2 = []
+ for tx_hash, tx_mined_status, delta in history:
+ h2.append((tx_hash, tx_mined_status, delta, balance))
+ if balance is None or delta is None:
+ balance = None
+ else:
+ balance -= delta
+ h2.reverse()
+ # fixme: this may happen if history is incomplete
+ if balance not in [None, 0]:
+ self.print_error("Error: history not synchronized")
+ return []
+
+ return h2
+
+ def _add_tx_to_local_history(self, txid):
+ with self.transaction_lock:
+ for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])):
+ cur_hist = self._history_local.get(addr, set())
+ cur_hist.add(txid)
+ self._history_local[addr] = cur_hist
+
+ def _remove_tx_from_local_history(self, txid):
+ with self.transaction_lock:
+ for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])):
+ cur_hist = self._history_local.get(addr, set())
+ try:
+ cur_hist.remove(txid)
+ except KeyError:
+ pass
+ else:
+ self._history_local[addr] = cur_hist
+
+ def add_unverified_tx(self, tx_hash, tx_height):
+ if tx_hash in self.verified_tx:
+ if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT):
+ with self.lock:
+ self.verified_tx.pop(tx_hash)
+ if self.verifier:
+ self.verifier.remove_spv_proof_for_tx(tx_hash)
+ else:
+ with self.lock:
+ # tx will be verified only if height > 0
+ self.unverified_tx[tx_hash] = tx_height
+ # to remove pending proof requests:
+ if self.verifier:
+ self.verifier.remove_spv_proof_for_tx(tx_hash)
+
+ def add_verified_tx(self, tx_hash: str, info: VerifiedTxInfo):
+ # Remove from the unverified map and add to the verified map
+ with self.lock:
+ self.unverified_tx.pop(tx_hash, None)
+ self.verified_tx[tx_hash] = info
+ tx_mined_status = self.get_tx_height(tx_hash)
+ self.network.trigger_callback('verified', tx_hash, tx_mined_status)
+
+ def get_unverified_txs(self):
+ '''Returns a map from tx hash to transaction height'''
+ with self.lock:
+ return dict(self.unverified_tx) # copy
+
+ def undo_verifications(self, blockchain, height):
+ '''Used by the verifier when a reorg has happened'''
+ txs = set()
+ with self.lock:
+ for tx_hash, info in list(self.verified_tx.items()):
+ tx_height = info.height
+ if tx_height >= height:
+ header = blockchain.read_header(tx_height)
+ if not header or hash_header(header) != info.header_hash:
+ self.verified_tx.pop(tx_hash, None)
+ # NOTE: we should add these txns to self.unverified_tx,
+ # but with what height?
+ # If on the new fork after the reorg, the txn is at the
+ # same height, we will not get a status update for the
+ # address. If the txn is not mined or at a diff height,
+ # we should get a status update. Unless we put tx into
+ # unverified_tx, it will turn into local. So we put it
+ # into unverified_tx with the old height, and if we get
+ # a status update, that will overwrite it.
+ self.unverified_tx[tx_hash] = tx_height
+ txs.add(tx_hash)
+ return txs
+
+ def get_local_height(self):
+ """ return last known height if we are offline """
+ cached_local_height = getattr(self.threadlocal_cache, 'local_height', None)
+ if cached_local_height is not None:
+ return cached_local_height
+ return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)
+
+ def get_tx_height(self, tx_hash: str) -> TxMinedStatus:
+ """ Given a transaction, returns (height, conf, timestamp, header_hash) """
+ with self.lock:
+ if tx_hash in self.verified_tx:
+ info = self.verified_tx[tx_hash]
+ conf = max(self.get_local_height() - info.height + 1, 0)
+ return TxMinedStatus(info.height, conf, info.timestamp, info.header_hash)
+ elif tx_hash in self.unverified_tx:
+ height = self.unverified_tx[tx_hash]
+ return TxMinedStatus(height, 0, None, None)
+ else:
+ # local transaction
+ return TxMinedStatus(TX_HEIGHT_LOCAL, 0, None, None)
+
+ def set_up_to_date(self, up_to_date):
+ with self.lock:
+ self.up_to_date = up_to_date
+ if up_to_date:
+ self.save_transactions(write=True)
+ # if the verifier is also up to date, persist that too;
+ # otherwise it will persist its results when it finishes
+ if self.verifier and self.verifier.is_up_to_date():
+ self.save_verified_tx(write=True)
+
+ def is_up_to_date(self):
+ with self.lock: return self.up_to_date
+
+ def get_tx_delta(self, tx_hash, address):
+ "effect of tx on address"
+ delta = 0
+ # substract the value of coins sent from address
+ d = self.txi.get(tx_hash, {}).get(address, [])
+ for n, v in d:
+ delta -= v
+ # add the value of the coins received at address
+ d = self.txo.get(tx_hash, {}).get(address, [])
+ for n, v, cb in d:
+ delta += v
+ return delta
+
+ def get_tx_value(self, txid):
+ " effect of tx on the entire domain"
+ delta = 0
+ for addr, d in self.txi.get(txid, {}).items():
+ for n, v in d:
+ delta -= v
+ for addr, d in self.txo.get(txid, {}).items():
+ for n, v, cb in d:
+ delta += v
+ return delta
+
+ def get_wallet_delta(self, tx):
+ """ effect of tx on wallet """
+ is_relevant = False # "related to wallet?"
+ is_mine = False
+ is_pruned = False
+ is_partial = False
+ v_in = v_out = v_out_mine = 0
+ for txin in tx.inputs():
+ addr = self.get_txin_address(txin)
+ if self.is_mine(addr):
+ is_mine = True
+ is_relevant = True
+ d = self.txo.get(txin['prevout_hash'], {}).get(addr, [])
+ for n, v, cb in d:
+ if n == txin['prevout_n']:
+ value = v
+ break
+ else:
+ value = None
+ if value is None:
+ is_pruned = True
+ else:
+ v_in += value
+ else:
+ is_partial = True
+ if not is_mine:
+ is_partial = False
+ for addr, value in tx.get_outputs():
+ v_out += value
+ if self.is_mine(addr):
+ v_out_mine += value
+ is_relevant = True
+ if is_pruned:
+ # some inputs are mine:
+ fee = None
+ if is_mine:
+ v = v_out_mine - v_out
+ else:
+ # no input is mine
+ v = v_out_mine
+ else:
+ v = v_out_mine - v_in
+ if is_partial:
+ # some inputs are mine, but not all
+ fee = None
+ else:
+ # all inputs are mine
+ fee = v_in - v_out
+ if not is_mine:
+ fee = None
+ return is_relevant, is_mine, v, fee
+
+ def get_addr_io(self, address):
+ h = self.get_address_history(address)
+ received = {}
+ sent = {}
+ for tx_hash, height in h:
+ l = self.txo.get(tx_hash, {}).get(address, [])
+ for n, v, is_cb in l:
+ received[tx_hash + ':%d'%n] = (height, v, is_cb)
+ for tx_hash, height in h:
+ l = self.txi.get(tx_hash, {}).get(address, [])
+ for txi, v in l:
+ sent[txi] = height
+ return received, sent
+
+ def get_addr_utxo(self, address):
+ coins, spent = self.get_addr_io(address)
+ for txi in spent:
+ coins.pop(txi)
+ out = {}
+ for txo, v in coins.items():
+ tx_height, value, is_cb = v
+ prevout_hash, prevout_n = txo.split(':')
+ x = {
+ 'address':address,
+ 'value':value,
+ 'prevout_n':int(prevout_n),
+ 'prevout_hash':prevout_hash,
+ 'height':tx_height,
+ 'coinbase':is_cb
+ }
+ out[txo] = x
+ return out
+
+ # return the total amount ever received by an address
+ def get_addr_received(self, address):
+ received, sent = self.get_addr_io(address)
+ return sum([v for height, v, is_cb in received.values()])
+
+ @with_local_height_cached
+ def get_addr_balance(self, address):
+ """Return the balance of a bitcore address:
+ confirmed and matured, unconfirmed, unmatured
+ """
+ received, sent = self.get_addr_io(address)
+ c = u = x = 0
+ local_height = self.get_local_height()
+ for txo, (tx_height, v, is_cb) in received.items():
+ if is_cb and tx_height + COINBASE_MATURITY > local_height:
+ x += v
+ elif tx_height > 0:
+ c += v
+ else:
+ u += v
+ if txo in sent:
+ if sent[txo] > 0:
+ c -= v
+ else:
+ u -= v
+ return c, u, x
+
+ @with_local_height_cached
+ def get_utxos(self, domain=None, excluded=None, mature=False, confirmed_only=False):
+ coins = []
+ if domain is None:
+ domain = self.get_addresses()
+ domain = set(domain)
+ if excluded:
+ domain = set(domain) - excluded
+ for addr in domain:
+ utxos = self.get_addr_utxo(addr)
+ for x in utxos.values():
+ if confirmed_only and x['height'] <= 0:
+ continue
+ if mature and x['coinbase'] and x['height'] + COINBASE_MATURITY > self.get_local_height():
+ continue
+ coins.append(x)
+ continue
+ return coins
+
+ def get_balance(self, domain=None):
+ if domain is None:
+ domain = self.get_addresses()
+ domain = set(domain)
+ cc = uu = xx = 0
+ for addr in domain:
+ c, u, x = self.get_addr_balance(addr)
+ cc += c
+ uu += u
+ xx += x
+ return cc, uu, xx
+
+ def is_used(self, address):
+ h = self.history.get(address,[])
+ return len(h) != 0
+
+ def is_empty(self, address):
+ c, u, x = self.get_addr_balance(address)
+ return c+u+x == 0
diff --git a/electrum/base_crash_reporter.py b/electrum/base_crash_reporter.py
new file mode 100644
index 000000000..a5702e2e6
--- /dev/null
+++ b/electrum/base_crash_reporter.py
@@ -0,0 +1,128 @@
+# Electrum - lightweight Bitcoin client
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import json
+import locale
+import traceback
+import subprocess
+import sys
+import os
+
+import requests
+
+from .version import ELECTRUM_VERSION
+from .import constants
+from .i18n import _
+
+
+class BaseCrashReporter(object):
+ report_server = "https://crashhub.electrum.org"
+ config_key = "show_crash_reporter"
+ issue_template = """Traceback
+
+{traceback}
+
+
+Additional information
+
+ Electrum version: {app_version}
+ Python version: {python_version}
+ Operating system: {os}
+ Wallet type: {wallet_type}
+ Locale: {locale}
+
+ """
+ CRASH_MESSAGE = _('Something went wrong while executing Electrum.')
+ CRASH_TITLE = _('Sorry!')
+ REQUEST_HELP_MESSAGE = _('To help us diagnose and fix the problem, you can send us a bug report that contains '
+ 'useful debug information:')
+ DESCRIBE_ERROR_MESSAGE = _("Please briefly describe what led to the error (optional):")
+ ASK_CONFIRM_SEND = _("Do you want to send this report?")
+
+ def __init__(self, exctype, value, tb):
+ self.exc_args = (exctype, value, tb)
+
+ def send_report(self, endpoint="/crash"):
+ if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
+ # Gah! Some kind of altcoin wants to send us crash reports.
+ raise Exception(_("Missing report URL."))
+ report = self.get_traceback_info()
+ report.update(self.get_additional_info())
+ report = json.dumps(report)
+ response = requests.post(BaseCrashReporter.report_server + endpoint, data=report)
+ return response
+
+ def get_traceback_info(self):
+ exc_string = str(self.exc_args[1])
+ stack = traceback.extract_tb(self.exc_args[2])
+ readable_trace = "".join(traceback.format_list(stack))
+ id = {
+ "file": stack[-1].filename,
+ "name": stack[-1].name,
+ "type": self.exc_args[0].__name__
+ }
+ return {
+ "exc_string": exc_string,
+ "stack": readable_trace,
+ "id": id
+ }
+
+ def get_additional_info(self):
+ args = {
+ "app_version": ELECTRUM_VERSION,
+ "python_version": sys.version,
+ "os": self.get_os_version(),
+ "wallet_type": "unknown",
+ "locale": locale.getdefaultlocale()[0] or "?",
+ "description": self.get_user_description()
+ }
+ try:
+ args["wallet_type"] = self.get_wallet_type()
+ except:
+ # Maybe the wallet isn't loaded yet
+ pass
+ try:
+ args["app_version"] = self.get_git_version()
+ except:
+ # This is probably not running from source
+ pass
+ return args
+
+ @staticmethod
+ def get_git_version():
+ dir = os.path.dirname(os.path.realpath(sys.argv[0]))
+ version = subprocess.check_output(
+ ['git', 'describe', '--always', '--dirty'], cwd=dir)
+ return str(version, "utf8").strip()
+
+ def get_report_string(self):
+ info = self.get_additional_info()
+ info["traceback"] = "".join(traceback.format_exception(*self.exc_args))
+ return self.issue_template.format(**info)
+
+ def get_user_description(self):
+ raise NotImplementedError
+
+ def get_wallet_type(self):
+ raise NotImplementedError
+
+ def get_os_version(self):
+ raise NotImplementedError
diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py
new file mode 100644
index 000000000..50f5243d8
--- /dev/null
+++ b/electrum/base_wizard.py
@@ -0,0 +1,572 @@
+# -*- coding: utf-8 -*-
+#
+# Electrum - lightweight Bitcore client
+# Copyright (C) 2016 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import sys
+import traceback
+from functools import partial
+
+from . import bitcoin
+from . import keystore
+from .keystore import bip44_derivation, purpose48_derivation
+from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types, Wallet
+from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption
+from .i18n import _
+from .util import UserCancelled, InvalidPassword, WalletFileException
+
+# hardware device setup purpose
+HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2)
+
+
+class ScriptTypeNotSupported(Exception): pass
+
+
+class GoBack(Exception): pass
+
+
+class BaseWizard(object):
+
+ def __init__(self, config, plugins, storage):
+ super(BaseWizard, self).__init__()
+ self.config = config
+ self.plugins = plugins
+ self.storage = storage
+ self.wallet = None
+ self.stack = []
+ self.plugin = None
+ self.keystores = []
+ self.is_kivy = config.get('gui') == 'kivy'
+ self.seed_type = None
+
+ def set_icon(self, icon):
+ pass
+
+ def run(self, *args):
+ action = args[0]
+ args = args[1:]
+ self.stack.append((action, args))
+ if not action:
+ return
+ if type(action) is tuple:
+ self.plugin, action = action
+ if self.plugin and hasattr(self.plugin, action):
+ f = getattr(self.plugin, action)
+ f(self, *args)
+ elif hasattr(self, action):
+ f = getattr(self, action)
+ f(*args)
+ else:
+ raise Exception("unknown action", action)
+
+ def can_go_back(self):
+ return len(self.stack)>1
+
+ def go_back(self):
+ if not self.can_go_back():
+ return
+ self.stack.pop()
+ action, args = self.stack.pop()
+ self.run(action, *args)
+
+ def new(self):
+ name = os.path.basename(self.storage.path)
+ title = _("Create") + ' ' + name
+ message = '\n'.join([
+ _("What kind of wallet do you want to create?")
+ ])
+ wallet_kinds = [
+ ('standard', _("Standard wallet")),
+ ('2fa', _("Wallet with two-factor authentication")),
+ ('multisig', _("Multi-signature wallet")),
+ ('imported', _("Import Bitcore addresses or private keys")),
+ ]
+ choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
+ self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
+
+ def upgrade_storage(self):
+ exc = None
+ def on_finished():
+ if exc is None:
+ self.wallet = Wallet(self.storage)
+ self.terminate()
+ else:
+ raise exc
+ def do_upgrade():
+ nonlocal exc
+ try:
+ self.storage.upgrade()
+ except Exception as e:
+ exc = e
+ self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished)
+
+ def load_2fa(self):
+ self.storage.put('wallet_type', '2fa')
+ self.storage.put('use_trustedcoin', True)
+ self.plugin = self.plugins.load_plugin('trustedcoin')
+
+ def on_wallet_type(self, choice):
+ self.wallet_type = choice
+ if choice == 'standard':
+ action = 'choose_keystore'
+ elif choice == 'multisig':
+ action = 'choose_multisig'
+ elif choice == '2fa':
+ self.load_2fa()
+ action = self.storage.get_action()
+ elif choice == 'imported':
+ action = 'import_addresses_or_keys'
+ self.run(action)
+
+ def choose_multisig(self):
+ def on_multisig(m, n):
+ self.multisig_type = "%dof%d"%(m, n)
+ self.storage.put('wallet_type', self.multisig_type)
+ self.n = n
+ self.run('choose_keystore')
+ self.multisig_dialog(run_next=on_multisig)
+
+ def choose_keystore(self):
+ assert self.wallet_type in ['standard', 'multisig']
+ i = len(self.keystores)
+ title = _('Add cosigner') + ' (%d of %d)'%(i+1, self.n) if self.wallet_type=='multisig' else _('Keystore')
+ if self.wallet_type =='standard' or i==0:
+ message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
+ choices = [
+ ('choose_seed_type', _('Create a new seed')),
+ ('restore_from_seed', _('I already have a seed')),
+ ('restore_from_key', _('Use a master key')),
+ ]
+ if not self.is_kivy:
+ choices.append(('choose_hw_device', _('Use a hardware device')))
+ else:
+ message = _('Add a cosigner to your multi-sig wallet')
+ choices = [
+ ('restore_from_key', _('Enter cosigner key')),
+ ('restore_from_seed', _('Enter cosigner seed')),
+ ]
+ if not self.is_kivy:
+ choices.append(('choose_hw_device', _('Cosign with hardware device')))
+
+ self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
+
+ def import_addresses_or_keys(self):
+ v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)
+ title = _("Import Bitcore Addresses")
+ message = _("Enter a list of Bitcore addresses (this will create a watching-only wallet), or a list of private keys.")
+ self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
+ is_valid=v, allow_multi=True, show_wif_help=True)
+
+ def on_import(self, text):
+ # create a temporary wallet and exploit that modifications
+ # will be reflected on self.storage
+ if keystore.is_address_list(text):
+ w = Imported_Wallet(self.storage)
+ for x in text.split():
+ w.import_address(x)
+ elif keystore.is_private_key_list(text):
+ k = keystore.Imported_KeyStore({})
+ self.storage.put('keystore', k.dump())
+ w = Imported_Wallet(self.storage)
+ for x in keystore.get_private_keys(text):
+ w.import_private_key(x, None)
+ self.keystores.append(w.keystore)
+ else:
+ return self.terminate()
+ return self.run('create_wallet')
+
+ def restore_from_key(self):
+ if self.wallet_type == 'standard':
+ v = keystore.is_master_key
+ title = _("Create keystore from a master key")
+ message = ' '.join([
+ _("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."),
+ _("To create a spending wallet, please enter a master private key (xprv/yprv/zprv).")
+ ])
+ self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)
+ else:
+ i = len(self.keystores) + 1
+ self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key)
+
+ def on_restore_from_key(self, text):
+ k = keystore.from_master_key(text)
+ self.on_keystore(k)
+
+ def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET):
+ title = _('Hardware Keystore')
+ # check available plugins
+ support = self.plugins.get_hardware_support()
+ if not support:
+ msg = '\n'.join([
+ _('No hardware wallet support found on your system.'),
+ _('Please install the relevant libraries (eg python-trezor for Trezor).'),
+ ])
+ self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
+ return
+ # scan devices
+ devices = []
+ devmgr = self.plugins.device_manager
+ try:
+ scanned_devices = devmgr.scan_devices()
+ except BaseException as e:
+ devmgr.print_error('error scanning devices: {}'.format(e))
+ debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
+ else:
+ debug_msg = ''
+ for name, description, plugin in support:
+ try:
+ # FIXME: side-effect: unpaired_device_info sets client.handler
+ u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
+ except BaseException as e:
+ devmgr.print_error('error getting device infos for {}: {}'.format(name, e))
+ indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
+ debug_msg += ' {}:\n{}\n'.format(plugin.name, indented_error_msg)
+ continue
+ devices += list(map(lambda x: (name, x), u))
+ if not debug_msg:
+ debug_msg = ' {}'.format(_('No exceptions encountered.'))
+ if not devices:
+ msg = ''.join([
+ _('No hardware device detected.') + '\n',
+ _('To trigger a rescan, press \'Next\'.') + '\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.') + ' ',
+ _('On Linux, you might have to add a new permission to your udev rules.') + '\n\n',
+ _('Debug message') + '\n',
+ debug_msg
+ ])
+ self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
+ return
+ # select device
+ self.devices = devices
+ choices = []
+ for name, info in devices:
+ state = _("initialized") if info.initialized else _("wiped")
+ label = info.label or _("An unnamed {}").format(name)
+ descr = "%s [%s, %s]" % (label, name, state)
+ choices.append(((name, info), descr))
+ msg = _('Select a device') + ':'
+ self.choice_dialog(title=title, message=msg, choices=choices, run_next= lambda *args: self.on_device(*args, purpose=purpose))
+
+ def on_device(self, name, device_info, *, purpose):
+ self.plugin = self.plugins.get_plugin(name)
+ try:
+ self.plugin.setup_device(device_info, self, purpose)
+ except OSError as e:
+ self.show_error(_('We encountered an error while connecting to your device:')
+ + '\n' + str(e) + '\n'
+ + _('To try to fix this, we will now re-pair with your device.') + '\n'
+ + _('Please try again.'))
+ devmgr = self.plugins.device_manager
+ devmgr.unpair_id(device_info.device.id_)
+ self.choose_hw_device(purpose)
+ return
+ except (UserCancelled, GoBack):
+ self.choose_hw_device(purpose)
+ return
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.show_error(str(e))
+ self.choose_hw_device(purpose)
+ return
+ if purpose == HWD_SETUP_NEW_WALLET:
+ def f(derivation, script_type):
+ self.run('on_hw_derivation', name, device_info, derivation, script_type)
+ self.derivation_and_script_type_dialog(f)
+ elif purpose == HWD_SETUP_DECRYPT_WALLET:
+ derivation = get_derivation_used_for_hw_device_encryption()
+ xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self)
+ password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
+ try:
+ self.storage.decrypt(password)
+ except InvalidPassword:
+ # try to clear session so that user can type another passphrase
+ devmgr = self.plugins.device_manager
+ client = devmgr.client_by_id(device_info.device.id_)
+ if hasattr(client, 'clear_session'): # FIXME not all hw wallet plugins have this
+ client.clear_session()
+ raise
+ else:
+ raise Exception('unknown purpose: %s' % purpose)
+
+ def derivation_and_script_type_dialog(self, f):
+ message1 = _('Choose the type of addresses in your wallet.')
+ message2 = '\n'.join([
+ _('You can override the suggested derivation path.'),
+ _('If you are not sure what this is, leave this field unchanged.')
+ ])
+ if self.wallet_type == 'multisig':
+ # There is no general standard for HD multisig.
+ # For legacy, this is partially compatible with BIP45; assumes index=0
+ # For segwit, a custom path is used, as there is no standard at all.
+ choices = [
+ ('standard', 'legacy multisig (p2sh)', "m/45'/0"),
+ ('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
+ ('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
+ ]
+ else:
+ choices = [
+ ('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
+ ('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
+ ('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)),
+ ]
+ while True:
+ try:
+ self.choice_and_line_dialog(
+ run_next=f, title=_('Script type and Derivation path'), message1=message1,
+ message2=message2, choices=choices, test_text=bitcoin.is_bip32_derivation)
+ return
+ except ScriptTypeNotSupported as e:
+ self.show_error(e)
+ # let the user choose again
+
+ def on_hw_derivation(self, name, device_info, derivation, xtype):
+ from .keystore import hardware_keystore
+ try:
+ xpub = self.plugin.get_xpub(device_info.device.id_, derivation, xtype, self)
+ except ScriptTypeNotSupported:
+ raise # this is handled in derivation_dialog
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.show_error(e)
+ return
+ d = {
+ 'type': 'hardware',
+ 'hw_type': name,
+ 'derivation': derivation,
+ 'xpub': xpub,
+ 'label': device_info.label,
+ }
+ k = hardware_keystore(d)
+ self.on_keystore(k)
+
+ def passphrase_dialog(self, run_next, is_restoring=False):
+ title = _('Seed extension')
+ message = '\n'.join([
+ _('You may extend your seed with custom words.'),
+ _('Your seed extension must be saved together with your seed.'),
+ ])
+ warning = '\n'.join([
+ _('Note that this is NOT your encryption password.'),
+ _('If you do not know what this is, leave this field empty.'),
+ ])
+ warn_issue4566 = is_restoring and self.seed_type == 'bip39'
+ self.line_dialog(title=title, message=message, warning=warning,
+ default='', test=lambda x:True, run_next=run_next,
+ warn_issue4566=warn_issue4566)
+
+ def restore_from_seed(self):
+ self.opt_bip39 = True
+ self.opt_ext = True
+ is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit']
+ test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
+ self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)
+
+ def on_restore_seed(self, seed, is_bip39, is_ext):
+ self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)
+ if self.seed_type == 'bip39':
+ f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
+ self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
+ elif self.seed_type in ['standard', 'segwit']:
+ f = lambda passphrase: self.run('create_keystore', seed, passphrase)
+ self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
+ elif self.seed_type == 'old':
+ self.run('create_keystore', seed, '')
+ elif self.seed_type == '2fa':
+ self.load_2fa()
+ self.run('on_restore_seed', seed, is_ext)
+ else:
+ raise Exception('Unknown seed type', self.seed_type)
+
+ def on_restore_bip39(self, seed, passphrase):
+ def f(derivation, script_type):
+ self.run('on_bip43', seed, passphrase, derivation, script_type)
+ self.derivation_and_script_type_dialog(f)
+
+ def create_keystore(self, seed, passphrase):
+ k = keystore.from_seed(seed, passphrase, self.wallet_type == 'multisig')
+ self.on_keystore(k)
+
+ def on_bip43(self, seed, passphrase, derivation, script_type):
+ k = keystore.from_bip39_seed(seed, passphrase, derivation, xtype=script_type)
+ self.on_keystore(k)
+
+ def on_keystore(self, k):
+ has_xpub = isinstance(k, keystore.Xpub)
+ if has_xpub:
+ from .bitcoin import xpub_type
+ t1 = xpub_type(k.xpub)
+ if self.wallet_type == 'standard':
+ if has_xpub and t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
+ self.show_error(_('Wrong key type') + ' %s'%t1)
+ self.run('choose_keystore')
+ return
+ self.keystores.append(k)
+ self.run('create_wallet')
+ elif self.wallet_type == 'multisig':
+ assert has_xpub
+ if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']:
+ self.show_error(_('Wrong key type') + ' %s'%t1)
+ self.run('choose_keystore')
+ return
+ if k.xpub in map(lambda x: x.xpub, self.keystores):
+ self.show_error(_('Error: duplicate master public key'))
+ self.run('choose_keystore')
+ return
+ if len(self.keystores)>0:
+ t2 = xpub_type(self.keystores[0].xpub)
+ if t1 != t2:
+ self.show_error(_('Cannot add this cosigner:') + '\n' + "Their key type is '%s', we are '%s'"%(t1, t2))
+ self.run('choose_keystore')
+ return
+ self.keystores.append(k)
+ if len(self.keystores) == 1:
+ xpub = k.get_master_public_key()
+ self.stack = []
+ self.run('show_xpub_and_add_cosigners', xpub)
+ elif len(self.keystores) < self.n:
+ self.run('choose_keystore')
+ else:
+ self.run('create_wallet')
+
+ def create_wallet(self):
+ encrypt_keystore = any(k.may_have_password() for k in self.keystores)
+ # note: the following condition ("if") is duplicated logic from
+ # wallet.get_available_storage_encryption_version()
+ if self.wallet_type == 'standard' and isinstance(self.keystores[0], keystore.Hardware_KeyStore):
+ # offer encrypting with a pw derived from the hw device
+ k = self.keystores[0]
+ try:
+ k.handler = self.plugin.create_handler(self)
+ password = k.get_password_for_storage_encryption()
+ except UserCancelled:
+ devmgr = self.plugins.device_manager
+ devmgr.unpair_xpub(k.xpub)
+ self.choose_hw_device()
+ return
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.show_error(str(e))
+ return
+ self.request_storage_encryption(
+ run_next=lambda encrypt_storage: self.on_password(
+ password,
+ encrypt_storage=encrypt_storage,
+ storage_enc_version=STO_EV_XPUB_PW,
+ encrypt_keystore=False))
+ else:
+ # prompt the user to set an arbitrary password
+ self.request_password(
+ run_next=lambda password, encrypt_storage: self.on_password(
+ password,
+ encrypt_storage=encrypt_storage,
+ storage_enc_version=STO_EV_USER_PW,
+ encrypt_keystore=encrypt_keystore),
+ force_disable_encrypt_cb=not encrypt_keystore)
+
+ def on_password(self, password, *, encrypt_storage,
+ storage_enc_version=STO_EV_USER_PW, encrypt_keystore):
+ self.storage.set_keystore_encryption(bool(password) and encrypt_keystore)
+ if encrypt_storage:
+ self.storage.set_password(password, enc_version=storage_enc_version)
+ for k in self.keystores:
+ if k.may_have_password():
+ k.update_password(None, password)
+ if self.wallet_type == 'standard':
+ self.storage.put('seed_type', self.seed_type)
+ keys = self.keystores[0].dump()
+ self.storage.put('keystore', keys)
+ self.wallet = Standard_Wallet(self.storage)
+ self.run('create_addresses')
+ elif self.wallet_type == 'multisig':
+ for i, k in enumerate(self.keystores):
+ self.storage.put('x%d/'%(i+1), k.dump())
+ self.storage.write()
+ self.wallet = Multisig_Wallet(self.storage)
+ self.run('create_addresses')
+ elif self.wallet_type == 'imported':
+ if len(self.keystores) > 0:
+ keys = self.keystores[0].dump()
+ self.storage.put('keystore', keys)
+ self.wallet = Imported_Wallet(self.storage)
+ self.wallet.storage.write()
+ self.terminate()
+
+ def show_xpub_and_add_cosigners(self, xpub):
+ self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
+
+ def choose_seed_type(self):
+ title = _('Choose Seed type')
+ message = ' '.join([
+ _("The type of addresses used by your wallet will depend on your seed."),
+ _("Segwit wallets use bech32 addresses, defined in BIP173."),
+ _("Please note that websites and other wallets may not support these addresses yet."),
+ _("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
+ ])
+ choices = [
+ ('create_standard_seed', _('Standard')),
+ ('create_segwit_seed', _('Segwit')),
+ ]
+ self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
+
+ def create_segwit_seed(self): self.create_seed('segwit')
+ def create_standard_seed(self): self.create_seed('standard')
+
+ def create_seed(self, seed_type):
+ from . import mnemonic
+ self.seed_type = seed_type
+ seed = mnemonic.Mnemonic('en').make_seed(self.seed_type)
+ self.opt_bip39 = False
+ f = lambda x: self.request_passphrase(seed, x)
+ self.show_seed_dialog(run_next=f, seed_text=seed)
+
+ def request_passphrase(self, seed, opt_passphrase):
+ if opt_passphrase:
+ f = lambda x: self.confirm_seed(seed, x)
+ self.passphrase_dialog(run_next=f)
+ else:
+ self.run('confirm_seed', seed, '')
+
+ def confirm_seed(self, seed, passphrase):
+ f = lambda x: self.confirm_passphrase(seed, passphrase)
+ self.confirm_seed_dialog(run_next=f, test=lambda x: x==seed)
+
+ def confirm_passphrase(self, seed, passphrase):
+ f = lambda x: self.run('create_keystore', seed, x)
+ if passphrase:
+ title = _('Confirm Seed Extension')
+ message = '\n'.join([
+ _('Your seed extension must be saved together with your seed.'),
+ _('Please type it here.'),
+ ])
+ self.line_dialog(run_next=f, title=title, message=message, default='', test=lambda x: x==passphrase)
+ else:
+ f('')
+
+ def create_addresses(self):
+ def task():
+ self.wallet.synchronize()
+ self.wallet.storage.write()
+ self.terminate()
+ msg = _("Electrum is generating your addresses, please wait...")
+ self.waiting_dialog(task, msg)
diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py
new file mode 100644
index 000000000..ccd57070f
--- /dev/null
+++ b/electrum/bitcoin.py
@@ -0,0 +1,782 @@
+# -*- coding: utf-8 -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import hashlib
+from typing import List
+
+from .util import bfh, bh2u, BitcoinException, print_error, assert_bytes, to_bytes, inv_dict
+from . import version
+from . import segwit_addr
+from . import constants
+from . import ecc
+from .crypto import Hash, sha256, hash_160, hmac_oneshot
+
+
+################################## transactions
+
+COINBASE_MATURITY = 100
+COIN = 100000000
+TOTAL_COIN_SUPPLY_LIMIT_IN_BTC = 21000000
+
+# supported types of transaction outputs
+TYPE_ADDRESS = 0
+TYPE_PUBKEY = 1
+TYPE_SCRIPT = 2
+
+
+def rev_hex(s):
+ return bh2u(bfh(s)[::-1])
+
+
+def int_to_hex(i: int, length: int=1) -> str:
+ """Converts int to little-endian hex string.
+ `length` is the number of bytes available
+ """
+ if not isinstance(i, int):
+ raise TypeError('{} instead of int'.format(i))
+ range_size = pow(256, length)
+ if i < -range_size/2 or i >= range_size:
+ raise OverflowError('cannot convert int {} to hex ({} bytes)'.format(i, length))
+ if i < 0:
+ # two's complement
+ i = range_size + i
+ s = hex(i)[2:].rstrip('L')
+ s = "0"*(2*length - len(s)) + s
+ return rev_hex(s)
+
+def script_num_to_hex(i: int) -> str:
+ """See CScriptNum in Bitcoin Core.
+ Encodes an integer as hex, to be used in script.
+
+ ported from https://github.com/bitcoin/bitcoin/blob/8cbc5c4be4be22aca228074f087a374a7ec38be8/src/script/script.h#L326
+ """
+ if i == 0:
+ return ''
+
+ result = bytearray()
+ neg = i < 0
+ absvalue = abs(i)
+ while absvalue > 0:
+ result.append(absvalue & 0xff)
+ absvalue >>= 8
+
+ if result[-1] & 0x80:
+ result.append(0x80 if neg else 0x00)
+ elif neg:
+ result[-1] |= 0x80
+
+ return bh2u(result)
+
+
+def var_int(i: int) -> str:
+ # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
+ if i<0xfd:
+ return int_to_hex(i)
+ elif i<=0xffff:
+ return "fd"+int_to_hex(i,2)
+ elif i<=0xffffffff:
+ return "fe"+int_to_hex(i,4)
+ else:
+ return "ff"+int_to_hex(i,8)
+
+
+def witness_push(item: str) -> str:
+ """Returns data in the form it should be present in the witness.
+ hex -> hex
+ """
+ return var_int(len(item) // 2) + item
+
+
+def op_push(i: int) -> str:
+ if i<0x4c: # OP_PUSHDATA1
+ return int_to_hex(i)
+ elif i<=0xff:
+ return '4c' + int_to_hex(i)
+ elif i<=0xffff:
+ return '4d' + int_to_hex(i,2)
+ else:
+ return '4e' + int_to_hex(i,4)
+
+
+def push_script(data: str) -> str:
+ """Returns pushed data to the script, automatically
+ choosing canonical opcodes depending on the length of the data.
+ hex -> hex
+
+ ported from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptbuilder.go#L128
+ """
+ data = bfh(data)
+ from .transaction import opcodes
+
+ data_len = len(data)
+
+ # "small integer" opcodes
+ if data_len == 0 or data_len == 1 and data[0] == 0:
+ return bh2u(bytes([opcodes.OP_0]))
+ elif data_len == 1 and data[0] <= 16:
+ return bh2u(bytes([opcodes.OP_1 - 1 + data[0]]))
+ elif data_len == 1 and data[0] == 0x81:
+ return bh2u(bytes([opcodes.OP_1NEGATE]))
+
+ return op_push(data_len) + bh2u(data)
+
+
+def add_number_to_script(i: int) -> bytes:
+ return bfh(push_script(script_num_to_hex(i)))
+
+
+hash_encode = lambda x: bh2u(x[::-1])
+hash_decode = lambda x: bfh(x)[::-1]
+hmac_sha_512 = lambda x, y: hmac_oneshot(x, y, hashlib.sha512)
+
+
+def is_new_seed(x, prefix=version.SEED_PREFIX):
+ from . import mnemonic
+ x = mnemonic.normalize_text(x)
+ s = bh2u(hmac_sha_512(b"Seed version", x.encode('utf8')))
+ return s.startswith(prefix)
+
+
+def is_old_seed(seed):
+ from . import old_mnemonic, mnemonic
+ seed = mnemonic.normalize_text(seed)
+ words = seed.split()
+ try:
+ # checks here are deliberately left weak for legacy reasons, see #3149
+ old_mnemonic.mn_decode(words)
+ uses_electrum_words = True
+ except Exception:
+ uses_electrum_words = False
+ try:
+ seed = bfh(seed)
+ is_hex = (len(seed) == 16 or len(seed) == 32)
+ except Exception:
+ is_hex = False
+ return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
+
+
+def seed_type(x):
+ if is_old_seed(x):
+ return 'old'
+ elif is_new_seed(x):
+ return 'standard'
+ elif is_new_seed(x, version.SEED_PREFIX_SW):
+ return 'segwit'
+ elif is_new_seed(x, version.SEED_PREFIX_2FA):
+ return '2fa'
+ return ''
+
+is_seed = lambda x: bool(seed_type(x))
+
+
+############ functions from pywallet #####################
+
+def hash160_to_b58_address(h160: bytes, addrtype):
+ s = bytes([addrtype])
+ s += h160
+ return base_encode(s+Hash(s)[0:4], base=58)
+
+
+def b58_address_to_hash160(addr):
+ addr = to_bytes(addr, 'ascii')
+ _bytes = base_decode(addr, 25, base=58)
+ return _bytes[0], _bytes[1:21]
+
+
+def hash160_to_p2pkh(h160, *, net=None):
+ if net is None:
+ net = constants.net
+ return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
+
+def hash160_to_p2sh(h160, *, net=None):
+ if net is None:
+ net = constants.net
+ return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
+
+def public_key_to_p2pkh(public_key: bytes) -> str:
+ return hash160_to_p2pkh(hash_160(public_key))
+
+def hash_to_segwit_addr(h, witver, *, net=None):
+ if net is None:
+ net = constants.net
+ return segwit_addr.encode(net.SEGWIT_HRP, witver, h)
+
+def public_key_to_p2wpkh(public_key):
+ return hash_to_segwit_addr(hash_160(public_key), witver=0)
+
+def script_to_p2wsh(script):
+ return hash_to_segwit_addr(sha256(bfh(script)), witver=0)
+
+def p2wpkh_nested_script(pubkey):
+ pkh = bh2u(hash_160(bfh(pubkey)))
+ return '00' + push_script(pkh)
+
+def p2wsh_nested_script(witness_script):
+ wsh = bh2u(sha256(bfh(witness_script)))
+ return '00' + push_script(wsh)
+
+def pubkey_to_address(txin_type, pubkey):
+ if txin_type == 'p2pkh':
+ return public_key_to_p2pkh(bfh(pubkey))
+ elif txin_type == 'p2wpkh':
+ return public_key_to_p2wpkh(bfh(pubkey))
+ elif txin_type == 'p2wpkh-p2sh':
+ scriptSig = p2wpkh_nested_script(pubkey)
+ return hash160_to_p2sh(hash_160(bfh(scriptSig)))
+ else:
+ raise NotImplementedError(txin_type)
+
+def redeem_script_to_address(txin_type, redeem_script):
+ if txin_type == 'p2sh':
+ return hash160_to_p2sh(hash_160(bfh(redeem_script)))
+ elif txin_type == 'p2wsh':
+ return script_to_p2wsh(redeem_script)
+ elif txin_type == 'p2wsh-p2sh':
+ scriptSig = p2wsh_nested_script(redeem_script)
+ return hash160_to_p2sh(hash_160(bfh(scriptSig)))
+ else:
+ raise NotImplementedError(txin_type)
+
+
+def script_to_address(script, *, net=None):
+ from .transaction import get_address_from_output_script
+ t, addr = get_address_from_output_script(bfh(script), net=net)
+ assert t == TYPE_ADDRESS
+ return addr
+
+def address_to_script(addr, *, net=None):
+ if net is None:
+ net = constants.net
+ witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
+ if witprog is not None:
+ if not (0 <= witver <= 16):
+ raise BitcoinException('impossible witness version: {}'.format(witver))
+ OP_n = witver + 0x50 if witver > 0 else 0
+ script = bh2u(bytes([OP_n]))
+ script += push_script(bh2u(bytes(witprog)))
+ return script
+ addrtype, hash_160 = b58_address_to_hash160(addr)
+ if addrtype == net.ADDRTYPE_P2PKH:
+ script = '76a9' # op_dup, op_hash_160
+ script += push_script(bh2u(hash_160))
+ script += '88ac' # op_equalverify, op_checksig
+ elif addrtype == net.ADDRTYPE_P2SH:
+ script = 'a9' # op_hash_160
+ script += push_script(bh2u(hash_160))
+ script += '87' # op_equal
+ else:
+ raise BitcoinException('unknown address type: {}'.format(addrtype))
+ return script
+
+def address_to_scripthash(addr):
+ script = address_to_script(addr)
+ return script_to_scripthash(script)
+
+def script_to_scripthash(script):
+ h = sha256(bytes.fromhex(script))[0:32]
+ return bh2u(bytes(reversed(h)))
+
+def public_key_to_p2pk_script(pubkey):
+ script = push_script(pubkey)
+ script += 'ac' # op_checksig
+ return script
+
+__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+assert len(__b58chars) == 58
+
+__b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'
+assert len(__b43chars) == 43
+
+
+def base_encode(v: bytes, base: int) -> str:
+ """ encode v, which is a string of bytes, to base58."""
+ assert_bytes(v)
+ if base not in (58, 43):
+ raise ValueError('not supported base: {}'.format(base))
+ chars = __b58chars
+ if base == 43:
+ chars = __b43chars
+ long_value = 0
+ for (i, c) in enumerate(v[::-1]):
+ long_value += (256**i) * c
+ result = bytearray()
+ while long_value >= base:
+ div, mod = divmod(long_value, base)
+ result.append(chars[mod])
+ long_value = div
+ result.append(chars[long_value])
+ # Bitcoin does a little leading-zero-compression:
+ # leading 0-bytes in the input become leading-1s
+ nPad = 0
+ for c in v:
+ if c == 0x00:
+ nPad += 1
+ else:
+ break
+ result.extend([chars[0]] * nPad)
+ result.reverse()
+ return result.decode('ascii')
+
+
+def base_decode(v, length, base):
+ """ decode v into a string of len bytes."""
+ # assert_bytes(v)
+ v = to_bytes(v, 'ascii')
+ if base not in (58, 43):
+ raise ValueError('not supported base: {}'.format(base))
+ chars = __b58chars
+ if base == 43:
+ chars = __b43chars
+ long_value = 0
+ for (i, c) in enumerate(v[::-1]):
+ digit = chars.find(bytes([c]))
+ if digit == -1:
+ raise ValueError('Forbidden character {} for base {}'.format(c, base))
+ long_value += digit * (base**i)
+ result = bytearray()
+ while long_value >= 256:
+ div, mod = divmod(long_value, 256)
+ result.append(mod)
+ long_value = div
+ result.append(long_value)
+ nPad = 0
+ for c in v:
+ if c == chars[0]:
+ nPad += 1
+ else:
+ break
+ result.extend(b'\x00' * nPad)
+ if length is not None and len(result) != length:
+ return None
+ result.reverse()
+ return bytes(result)
+
+
+class InvalidChecksum(Exception):
+ pass
+
+
+def EncodeBase58Check(vchIn):
+ hash = Hash(vchIn)
+ return base_encode(vchIn + hash[0:4], base=58)
+
+
+def DecodeBase58Check(psz):
+ vchRet = base_decode(psz, None, base=58)
+ key = vchRet[0:-4]
+ csum = vchRet[-4:]
+ hash = Hash(key)
+ cs32 = hash[0:4]
+ if cs32 != csum:
+ raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum)))
+ else:
+ return key
+
+
+# backwards compat
+# extended WIF for segwit (used in 3.0.x; but still used internally)
+# the keys in this dict should be a superset of what Imported Wallets can import
+WIF_SCRIPT_TYPES = {
+ 'p2pkh':0,
+ 'p2wpkh':1,
+ 'p2wpkh-p2sh':2,
+ 'p2sh':5,
+ 'p2wsh':6,
+ 'p2wsh-p2sh':7
+}
+WIF_SCRIPT_TYPES_INV = inv_dict(WIF_SCRIPT_TYPES)
+
+
+PURPOSE48_SCRIPT_TYPES = {
+ 'p2wsh-p2sh': 1, # specifically multisig
+ 'p2wsh': 2, # specifically multisig
+}
+PURPOSE48_SCRIPT_TYPES_INV = inv_dict(PURPOSE48_SCRIPT_TYPES)
+
+
+def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
+ internal_use: bool=False) -> str:
+ # we only export secrets inside curve range
+ secret = ecc.ECPrivkey.normalize_secret_bytes(secret)
+ if internal_use:
+ prefix = bytes([(WIF_SCRIPT_TYPES[txin_type] + constants.net.WIF_PREFIX) & 255])
+ else:
+ prefix = bytes([constants.net.WIF_PREFIX])
+ suffix = b'\01' if compressed else b''
+ vchIn = prefix + secret + suffix
+ base58_wif = EncodeBase58Check(vchIn)
+ if internal_use:
+ return base58_wif
+ else:
+ return '{}:{}'.format(txin_type, base58_wif)
+
+
+def deserialize_privkey(key: str) -> (str, bytes, bool):
+ if is_minikey(key):
+ return 'p2pkh', minikey_to_private_key(key), False
+
+ txin_type = None
+ if ':' in key:
+ txin_type, key = key.split(sep=':', maxsplit=1)
+ if txin_type not in WIF_SCRIPT_TYPES:
+ raise BitcoinException('unknown script type: {}'.format(txin_type))
+ try:
+ vch = DecodeBase58Check(key)
+ except BaseException:
+ neutered_privkey = str(key)[:3] + '..' + str(key)[-2:]
+ raise BitcoinException("cannot deserialize privkey {}"
+ .format(neutered_privkey))
+
+ if txin_type is None:
+ # keys exported in version 3.0.x encoded script type in first byte
+ prefix_value = vch[0] - constants.net.WIF_PREFIX
+ try:
+ txin_type = WIF_SCRIPT_TYPES_INV[prefix_value]
+ except KeyError:
+ raise BitcoinException('invalid prefix ({}) for WIF key (1)'.format(vch[0]))
+ else:
+ # all other keys must have a fixed first byte
+ if vch[0] != constants.net.WIF_PREFIX:
+ raise BitcoinException('invalid prefix ({}) for WIF key (2)'.format(vch[0]))
+
+ if len(vch) not in [33, 34]:
+ raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch)))
+ compressed = len(vch) == 34
+ secret_bytes = vch[1:33]
+ # we accept secrets outside curve range; cast into range here:
+ secret_bytes = ecc.ECPrivkey.normalize_secret_bytes(secret_bytes)
+ return txin_type, secret_bytes, compressed
+
+
+def is_compressed(sec):
+ return deserialize_privkey(sec)[2]
+
+
+def address_from_private_key(sec):
+ txin_type, privkey, compressed = deserialize_privkey(sec)
+ public_key = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
+ return pubkey_to_address(txin_type, public_key)
+
+def is_segwit_address(addr):
+ try:
+ witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr)
+ except Exception as e:
+ return False
+ return witprog is not None
+
+def is_b58_address(addr):
+ try:
+ addrtype, h = b58_address_to_hash160(addr)
+ except Exception as e:
+ return False
+ if addrtype not in [constants.net.ADDRTYPE_P2PKH, constants.net.ADDRTYPE_P2SH]:
+ return False
+ return addr == hash160_to_b58_address(h, addrtype)
+
+def is_address(addr):
+ return is_segwit_address(addr) or is_b58_address(addr)
+
+
+def is_private_key(key):
+ try:
+ k = deserialize_privkey(key)
+ return k is not False
+ except:
+ return False
+
+
+########### end pywallet functions #######################
+
+def is_minikey(text):
+ # Minikeys are typically 22 or 30 characters, but this routine
+ # permits any length of 20 or more provided the minikey is valid.
+ # A valid minikey must begin with an 'S', be in base58, and when
+ # suffixed with '?' have its SHA256 hash begin with a zero byte.
+ # They are widely used in Casascius physical bitcoins.
+ return (len(text) >= 20 and text[0] == 'S'
+ and all(ord(c) in __b58chars for c in text)
+ and sha256(text + '?')[0] == 0x00)
+
+def minikey_to_private_key(text):
+ return sha256(text)
+
+
+###################################### BIP32 ##############################
+
+BIP32_PRIME = 0x800000a0
+
+
+def protect_against_invalid_ecpoint(func):
+ def func_wrapper(*args):
+ n = args[-1]
+ while True:
+ is_prime = n & BIP32_PRIME
+ try:
+ return func(*args[:-1], n=n)
+ except ecc.InvalidECPointException:
+ print_error('bip32 protect_against_invalid_ecpoint: skipping index')
+ n += 1
+ is_prime2 = n & BIP32_PRIME
+ if is_prime != is_prime2: raise OverflowError()
+ return func_wrapper
+
+
+# Child private key derivation function (from master private key)
+# k = master private key (32 bytes)
+# c = master chain code (extra entropy for key derivation) (32 bytes)
+# n = the index of the key we want to derive. (only 32 bits will be used)
+# If n is hardened (i.e. the 32nd bit is set), the resulting private key's
+# corresponding public key can NOT be determined without the master private key.
+# However, if n is not hardened, the resulting private key's corresponding
+# public key can be determined without the master private key.
+@protect_against_invalid_ecpoint
+def CKD_priv(k, c, n):
+ if n < 0: raise ValueError('the bip32 index needs to be non-negative')
+ is_prime = n & BIP32_PRIME
+ return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)
+
+
+def _CKD_priv(k, c, s, is_prime):
+ try:
+ keypair = ecc.ECPrivkey(k)
+ except ecc.InvalidECPointException as e:
+ raise BitcoinException('Impossible xprv (not within curve order)') from e
+ cK = keypair.get_public_key_bytes(compressed=True)
+ data = bytes([0]) + k + s if is_prime else cK + s
+ I = hmac_oneshot(c, data, hashlib.sha512)
+ I_left = ecc.string_to_number(I[0:32])
+ k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER
+ if I_left >= ecc.CURVE_ORDER or k_n == 0:
+ raise ecc.InvalidECPointException()
+ k_n = ecc.number_to_string(k_n, ecc.CURVE_ORDER)
+ c_n = I[32:]
+ return k_n, c_n
+
+# Child public key derivation function (from public key only)
+# K = master public key
+# c = master chain code
+# n = index of key we want to derive
+# This function allows us to find the nth public key, as long as n is
+# not hardened. If n is hardened, we need the master private key to find it.
+@protect_against_invalid_ecpoint
+def CKD_pub(cK, c, n):
+ if n < 0: raise ValueError('the bip32 index needs to be non-negative')
+ if n & BIP32_PRIME: raise Exception()
+ return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
+
+# helper function, callable with arbitrary string.
+# note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
+def _CKD_pub(cK, c, s):
+ I = hmac_oneshot(c, cK + s, hashlib.sha512)
+ pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
+ if pubkey.is_at_infinity():
+ raise ecc.InvalidECPointException()
+ cK_n = pubkey.get_public_key_bytes(compressed=True)
+ c_n = I[32:]
+ return cK_n, c_n
+
+
+def xprv_header(xtype, *, net=None):
+ if net is None:
+ net = constants.net
+ return bfh("%08x" % net.XPRV_HEADERS[xtype])
+
+
+def xpub_header(xtype, *, net=None):
+ if net is None:
+ net = constants.net
+ return bfh("%08x" % net.XPUB_HEADERS[xtype])
+
+
+def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
+ child_number=b'\x00'*4, *, net=None):
+ if not ecc.is_secret_within_curve_range(k):
+ raise BitcoinException('Impossible xprv (not within curve order)')
+ xprv = xprv_header(xtype, net=net) \
+ + bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
+ return EncodeBase58Check(xprv)
+
+
+def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4,
+ child_number=b'\x00'*4, *, net=None):
+ xpub = xpub_header(xtype, net=net) \
+ + bytes([depth]) + fingerprint + child_number + c + cK
+ return EncodeBase58Check(xpub)
+
+
+class InvalidMasterKeyVersionBytes(BitcoinException): pass
+
+
+def deserialize_xkey(xkey, prv, *, net=None):
+ if net is None:
+ net = constants.net
+ xkey = DecodeBase58Check(xkey)
+ if len(xkey) != 78:
+ raise BitcoinException('Invalid length for extended key: {}'
+ .format(len(xkey)))
+ depth = xkey[4]
+ fingerprint = xkey[5:9]
+ child_number = xkey[9:13]
+ c = xkey[13:13+32]
+ header = int('0x' + bh2u(xkey[0:4]), 16)
+ headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS
+ if header not in headers.values():
+ raise InvalidMasterKeyVersionBytes('Invalid extended key format: {}'
+ .format(hex(header)))
+ xtype = list(headers.keys())[list(headers.values()).index(header)]
+ n = 33 if prv else 32
+ K_or_k = xkey[13+n:]
+ if prv and not ecc.is_secret_within_curve_range(K_or_k):
+ raise BitcoinException('Impossible xprv (not within curve order)')
+ return xtype, depth, fingerprint, child_number, c, K_or_k
+
+
+def deserialize_xpub(xkey, *, net=None):
+ return deserialize_xkey(xkey, False, net=net)
+
+def deserialize_xprv(xkey, *, net=None):
+ return deserialize_xkey(xkey, True, net=net)
+
+def xpub_type(x):
+ return deserialize_xpub(x)[0]
+
+
+def is_xpub(text):
+ try:
+ deserialize_xpub(text)
+ return True
+ except:
+ return False
+
+
+def is_xprv(text):
+ try:
+ deserialize_xprv(text)
+ return True
+ except:
+ return False
+
+
+def xpub_from_xprv(xprv):
+ xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
+ cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
+ return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
+
+
+def bip32_root(seed, xtype):
+ I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
+ master_k = I[0:32]
+ master_c = I[32:]
+ # create xprv first, as that will check if master_k is within curve order
+ xprv = serialize_xprv(xtype, master_c, master_k)
+ cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
+ xpub = serialize_xpub(xtype, master_c, cK)
+ return xprv, xpub
+
+
+def xpub_from_pubkey(xtype, cK):
+ if cK[0] not in (0x02, 0x03):
+ raise ValueError('Unexpected first byte: {}'.format(cK[0]))
+ return serialize_xpub(xtype, b'\x00'*32, cK)
+
+
+def bip32_derivation(s):
+ if not s.startswith('m/'):
+ raise ValueError('invalid bip32 derivation path: {}'.format(s))
+ s = s[2:]
+ for n in s.split('/'):
+ if n == '': continue
+ i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
+ yield i
+
+def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
+ """Convert bip32 path to list of uint32 integers with prime flags
+ m/0/-1/1' -> [0, 0x80000001, 0x80000001]
+
+ based on code in trezorlib
+ """
+ path = []
+ for x in n.split('/')[1:]:
+ if x == '': continue
+ prime = 0
+ if x.endswith("'"):
+ x = x.replace('\'', '')
+ prime = BIP32_PRIME
+ if x.startswith('-'):
+ prime = BIP32_PRIME
+ path.append(abs(int(x)) | prime)
+ return path
+
+def is_bip32_derivation(x):
+ try:
+ [ i for i in bip32_derivation(x)]
+ return True
+ except :
+ return False
+
+def bip32_private_derivation(xprv, branch, sequence):
+ if not sequence.startswith(branch):
+ raise ValueError('incompatible branch ({}) and sequence ({})'
+ .format(branch, sequence))
+ if branch == sequence:
+ return xprv, xpub_from_xprv(xprv)
+ xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
+ sequence = sequence[len(branch):]
+ for n in sequence.split('/'):
+ if n == '': continue
+ i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
+ parent_k = k
+ k, c = CKD_priv(k, c, i)
+ depth += 1
+ parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
+ fingerprint = hash_160(parent_cK)[0:4]
+ child_number = bfh("%08X"%i)
+ cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
+ xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
+ xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
+ return xprv, xpub
+
+
+def bip32_public_derivation(xpub, branch, sequence):
+ xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
+ if not sequence.startswith(branch):
+ raise ValueError('incompatible branch ({}) and sequence ({})'
+ .format(branch, sequence))
+ sequence = sequence[len(branch):]
+ for n in sequence.split('/'):
+ if n == '': continue
+ i = int(n)
+ parent_cK = cK
+ cK, c = CKD_pub(cK, c, i)
+ depth += 1
+ fingerprint = hash_160(parent_cK)[0:4]
+ child_number = bfh("%08X"%i)
+ return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
+
+
+def bip32_private_key(sequence, k, chain):
+ for i in sequence:
+ k, chain = CKD_priv(k, chain, i)
+ return k
diff --git a/electrum/blockchain.py b/electrum/blockchain.py
new file mode 100644
index 000000000..bbabc2a1e
--- /dev/null
+++ b/electrum/blockchain.py
@@ -0,0 +1,405 @@
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@ecdsa.org
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+import threading
+
+from . import util
+from .bitcoin import Hash, hash_encode, int_to_hex, rev_hex
+from . import constants
+from .util import bfh, bh2u
+
+MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
+
+
+class MissingHeader(Exception):
+ pass
+
+class InvalidHeader(Exception):
+ pass
+
+def serialize_header(res):
+ s = int_to_hex(res.get('version'), 4) \
+ + rev_hex(res.get('prev_block_hash')) \
+ + rev_hex(res.get('merkle_root')) \
+ + int_to_hex(int(res.get('timestamp')), 4) \
+ + int_to_hex(int(res.get('bits')), 4) \
+ + int_to_hex(int(res.get('nonce')), 4)
+ return s
+
+def deserialize_header(s, height):
+ if not s:
+ raise InvalidHeader('Invalid header: {}'.format(s))
+ if len(s) != 80:
+ raise InvalidHeader('Invalid header length: {}'.format(len(s)))
+ hex_to_int = lambda s: int('0x' + bh2u(s[::-1]), 16)
+ h = {}
+ h['version'] = hex_to_int(s[0:4])
+ h['prev_block_hash'] = hash_encode(s[4:36])
+ h['merkle_root'] = hash_encode(s[36:68])
+ h['timestamp'] = hex_to_int(s[68:72])
+ h['bits'] = hex_to_int(s[72:76])
+ h['nonce'] = hex_to_int(s[76:80])
+ h['block_height'] = height
+ return h
+
+def hash_header(header):
+ if header is None:
+ return '0' * 64
+ if header.get('prev_block_hash') is None:
+ header['prev_block_hash'] = '00'*32
+ return hash_encode(Hash(bfh(serialize_header(header))))
+
+
+blockchains = {}
+
+def read_blockchains(config):
+ blockchains[0] = Blockchain(config, 0, None)
+ fdir = os.path.join(util.get_headers_dir(config), 'forks')
+ util.make_dir(fdir)
+ l = filter(lambda x: x.startswith('fork_'), os.listdir(fdir))
+ l = sorted(l, key = lambda x: int(x.split('_')[1]))
+ for filename in l:
+ forkpoint = int(filename.split('_')[2])
+ parent_id = int(filename.split('_')[1])
+ b = Blockchain(config, forkpoint, parent_id)
+ h = b.read_header(b.forkpoint)
+ if b.parent().can_connect(h, check_height=False):
+ blockchains[b.forkpoint] = b
+ else:
+ util.print_error("cannot connect", filename)
+ return blockchains
+
+def check_header(header):
+ if type(header) is not dict:
+ return False
+ for b in blockchains.values():
+ if b.check_header(header):
+ return b
+ return False
+
+def can_connect(header):
+ for b in blockchains.values():
+ if b.can_connect(header):
+ return b
+ return False
+
+
+class Blockchain(util.PrintError):
+ """
+ Manages blockchain headers and their verification
+ """
+
+ def __init__(self, config, forkpoint, parent_id):
+ self.config = config
+ self.catch_up = None # interface catching up
+ self.forkpoint = forkpoint
+ self.checkpoints = constants.net.CHECKPOINTS
+ self.parent_id = parent_id
+ assert parent_id != forkpoint
+ self.lock = threading.RLock()
+ with self.lock:
+ self.update_size()
+
+ def with_lock(func):
+ def func_wrapper(self, *args, **kwargs):
+ with self.lock:
+ return func(self, *args, **kwargs)
+ return func_wrapper
+
+ def parent(self):
+ return blockchains[self.parent_id]
+
+ def get_max_child(self):
+ children = list(filter(lambda y: y.parent_id==self.forkpoint, blockchains.values()))
+ return max([x.forkpoint for x in children]) if children else None
+
+ def get_forkpoint(self):
+ mc = self.get_max_child()
+ return mc if mc is not None else self.forkpoint
+
+ def get_branch_size(self):
+ return self.height() - self.get_forkpoint() + 1
+
+ def get_name(self):
+ return self.get_hash(self.get_forkpoint()).lstrip('00')[0:10]
+
+ def check_header(self, header):
+ header_hash = hash_header(header)
+ height = header.get('block_height')
+ return header_hash == self.get_hash(height)
+
+ def fork(parent, header):
+ forkpoint = header.get('block_height')
+ self = Blockchain(parent.config, forkpoint, parent.forkpoint)
+ open(self.path(), 'w+').close()
+ self.save_header(header)
+ return self
+
+ def height(self):
+ return self.forkpoint + self.size() - 1
+
+ def size(self):
+ with self.lock:
+ return self._size
+
+ def update_size(self):
+ p = self.path()
+ self._size = os.path.getsize(p)//80 if os.path.exists(p) else 0
+
+ def verify_header(self, header, prev_hash, target):
+ _hash = hash_header(header)
+ if prev_hash != header.get('prev_block_hash'):
+ raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
+ if constants.net.TESTNET:
+ return
+ bits = self.target_to_bits(target)
+ if bits != header.get('bits'):
+ raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
+ if int('0x' + _hash, 16) > target:
+ raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
+
+ def verify_chunk(self, index, data):
+ num = len(data) // 80
+ prev_hash = self.get_hash(index * 2016 - 1)
+ target = self.get_target(index-1)
+ for i in range(num):
+ raw_header = data[i*80:(i+1) * 80]
+ header = deserialize_header(raw_header, index*2016 + i)
+ self.verify_header(header, prev_hash, target)
+ prev_hash = hash_header(header)
+
+ def path(self):
+ d = util.get_headers_dir(self.config)
+ filename = 'blockchain_headers' if self.parent_id is None else os.path.join('forks', 'fork_%d_%d'%(self.parent_id, self.forkpoint))
+ return os.path.join(d, filename)
+
+ @with_lock
+ def save_chunk(self, index, chunk):
+ chunk_within_checkpoint_region = index < len(self.checkpoints)
+ # chunks in checkpoint region are the responsibility of the 'main chain'
+ if chunk_within_checkpoint_region and self.parent_id is not None:
+ main_chain = blockchains[0]
+ main_chain.save_chunk(index, chunk)
+ return
+
+ delta_height = (index * 2016 - self.forkpoint)
+ delta_bytes = delta_height * 80
+ # if this chunk contains our forkpoint, only save the part after forkpoint
+ # (the part before is the responsibility of the parent)
+ if delta_bytes < 0:
+ chunk = chunk[-delta_bytes:]
+ delta_bytes = 0
+ truncate = not chunk_within_checkpoint_region
+ self.write(chunk, delta_bytes, truncate)
+ self.swap_with_parent()
+
+ @with_lock
+ def swap_with_parent(self):
+ if self.parent_id is None:
+ return
+ parent_branch_size = self.parent().height() - self.forkpoint + 1
+ if parent_branch_size >= self.size():
+ return
+ self.print_error("swap", self.forkpoint, self.parent_id)
+ parent_id = self.parent_id
+ forkpoint = self.forkpoint
+ parent = self.parent()
+ self.assert_headers_file_available(self.path())
+ with open(self.path(), 'rb') as f:
+ my_data = f.read()
+ self.assert_headers_file_available(parent.path())
+ with open(parent.path(), 'rb') as f:
+ f.seek((forkpoint - parent.forkpoint)*80)
+ parent_data = f.read(parent_branch_size*80)
+ self.write(parent_data, 0)
+ parent.write(my_data, (forkpoint - parent.forkpoint)*80)
+ # store file path
+ for b in blockchains.values():
+ b.old_path = b.path()
+ # swap parameters
+ self.parent_id = parent.parent_id; parent.parent_id = parent_id
+ self.forkpoint = parent.forkpoint; parent.forkpoint = forkpoint
+ self._size = parent._size; parent._size = parent_branch_size
+ # move files
+ for b in blockchains.values():
+ if b in [self, parent]: continue
+ if b.old_path != b.path():
+ self.print_error("renaming", b.old_path, b.path())
+ os.rename(b.old_path, b.path())
+ # update pointers
+ blockchains[self.forkpoint] = self
+ blockchains[parent.forkpoint] = parent
+
+ def assert_headers_file_available(self, path):
+ if os.path.exists(path):
+ return
+ elif not os.path.exists(util.get_headers_dir(self.config)):
+ raise FileNotFoundError('Electrum headers_dir does not exist. Was it deleted while running?')
+ else:
+ raise FileNotFoundError('Cannot find headers file but headers_dir is there. Should be at {}'.format(path))
+
+ def write(self, data, offset, truncate=True):
+ filename = self.path()
+ with self.lock:
+ self.assert_headers_file_available(filename)
+ with open(filename, 'rb+') as f:
+ if truncate and offset != self._size*80:
+ f.seek(offset)
+ f.truncate()
+ f.seek(offset)
+ f.write(data)
+ f.flush()
+ os.fsync(f.fileno())
+ self.update_size()
+
+ @with_lock
+ def save_header(self, header):
+ delta = header.get('block_height') - self.forkpoint
+ data = bfh(serialize_header(header))
+ # headers are only _appended_ to the end:
+ assert delta == self.size()
+ assert len(data) == 80
+ self.write(data, delta*80)
+ self.swap_with_parent()
+
+ def read_header(self, height):
+ assert self.parent_id != self.forkpoint
+ if height < 0:
+ return
+ if height < self.forkpoint:
+ return self.parent().read_header(height)
+ if height > self.height():
+ return
+ delta = height - self.forkpoint
+ name = self.path()
+ self.assert_headers_file_available(name)
+ with open(name, 'rb') as f:
+ f.seek(delta * 80)
+ h = f.read(80)
+ if len(h) < 80:
+ raise Exception('Expected to read a full header. This was only {} bytes'.format(len(h)))
+ if h == bytes([0])*80:
+ return None
+ return deserialize_header(h, height)
+
+ def get_hash(self, height):
+ if height == -1:
+ return '0000000000000000000000000000000000000000000000000000000000000000'
+ elif height == 0:
+ return constants.net.GENESIS
+ elif height < len(self.checkpoints) * 2016:
+ assert (height+1) % 2016 == 0, height
+ index = height // 2016
+ h, t = self.checkpoints[index]
+ return h
+ else:
+ return hash_header(self.read_header(height))
+
+ def get_target(self, index):
+ # compute target from chunk x, used in chunk x+1
+ if constants.net.TESTNET:
+ return 0
+ if index == -1:
+ return MAX_TARGET
+ if index < len(self.checkpoints):
+ h, t = self.checkpoints[index]
+ return t
+ # new target
+ first = self.read_header(index * 2016)
+ last = self.read_header(index * 2016 + 2015)
+ if not first or not last:
+ raise MissingHeader()
+ bits = last.get('bits')
+ target = self.bits_to_target(bits)
+ nActualTimespan = last.get('timestamp') - first.get('timestamp')
+ nTargetTimespan = 14 * 24 * 60 * 60
+ nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
+ nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
+ new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
+ return new_target
+
+ def bits_to_target(self, bits):
+ bitsN = (bits >> 24) & 0xff
+ if not (bitsN >= 0x03 and bitsN <= 0x1d):
+ raise Exception("First part of bits should be in [0x03, 0x1d]")
+ bitsBase = bits & 0xffffff
+ if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):
+ raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
+ return bitsBase << (8 * (bitsN-3))
+
+ def target_to_bits(self, target):
+ c = ("%064x" % target)[2:]
+ while c[:2] == '00' and len(c) > 6:
+ c = c[2:]
+ bitsN, bitsBase = len(c) // 2, int('0x' + c[:6], 16)
+ if bitsBase >= 0x800000:
+ bitsN += 1
+ bitsBase >>= 8
+ return bitsN << 24 | bitsBase
+
+ def can_connect(self, header, check_height=True):
+ if header is None:
+ return False
+ height = header['block_height']
+ if check_height and self.height() != height - 1:
+ #self.print_error("cannot connect at height", height)
+ return False
+ if height == 0:
+ return hash_header(header) == constants.net.GENESIS
+ try:
+ prev_hash = self.get_hash(height - 1)
+ except:
+ return False
+ if prev_hash != header.get('prev_block_hash'):
+ return False
+ #try:
+ # target = self.get_target(height // 2016 - 1)
+ #except MissingHeader:
+ # return False
+ #try:
+ # self.verify_header(header, prev_hash, target)
+ #except BaseException as e:
+ # return False
+ return True
+
+ def connect_chunk(self, idx, hexdata):
+ try:
+ data = bfh(hexdata)
+ #self.verify_chunk(idx, data)
+ #self.print_error("validated chunk %d" % idx)
+ self.save_chunk(idx, data)
+ return True
+ except BaseException as e:
+ self.print_error('verify_chunk %d failed'%idx, str(e))
+ return False
+
+ def get_checkpoints(self):
+ # for each chunk, store the hash of the last block and the target after the chunk
+ cp = []
+ n = self.height() // 2016
+ for index in range(n):
+ h = self.get_hash((index+1) * 2016 -1)
+ #target = self.get_target(index)
+ target=0
+ cp.append((h, target))
+ return cp
diff --git a/electrum/checkpoints.json b/electrum/checkpoints.json
new file mode 100644
index 000000000..573ca3d69
--- /dev/null
+++ b/electrum/checkpoints.json
@@ -0,0 +1,6 @@
+[
+ [
+ "e7874ac02e4da5148fe44510c7f0c7242f52b3726992553ebbc8c7f303c473ab",
+ 0
+ ]
+]
diff --git a/electrum/checkpoints_testnet.json b/electrum/checkpoints_testnet.json
new file mode 100644
index 000000000..aaa4ea28e
--- /dev/null
+++ b/electrum/checkpoints_testnet.json
@@ -0,0 +1,2662 @@
+[
+ [
+ "00000000864b744c5025331036aa4a16e9ed1cbb362908c625272150fa059b29",
+ 0
+ ],
+ [
+ "000000002e9ccffc999166ccf8d72129e1b2e9c754f6c90ad2f77cab0d9fb4c7",
+ 0
+ ],
+ [
+ "0000000009b9f0436a9c733e2c9a9d9c8fe3475d383bdc1beb7bfa995f90be70",
+ 0
+ ],
+ [
+ "000000000a9c9c79f246042b9e2819822287f2be7cd6487aecf7afab6a88bed5",
+ 0
+ ],
+ [
+ "000000003a7002e1247b0008cba36cd46f57cd7ce56ac9d9dc5644265064df09",
+ 0
+ ],
+ [
+ "00000000061e01e82afff6e7aaea4eb841b78cc0eed3af11f6706b14471fa9c8",
+ 0
+ ],
+ [
+ "000000003911e011ae2459e44d4581ac69ba703fb26e1421529bd326c538f12d",
+ 0
+ ],
+ [
+ "000000000a5984d6c73396fe40de392935f5fc2a8e48eedf38034ce0a3178a60",
+ 0
+ ],
+ [
+ "000000000786bdc642fa54c0a791d58b732ed5676516fffaeca04492be97c243",
+ 0
+ ],
+ [
+ "000000001359c49f9618f3ee69afbd1b3196f1832acc47557d42256fcc6b7f48",
+ 0
+ ],
+ [
+ "00000000270dde98d582af35dff5aed02087dad8529dc5c808c67573d6dabaf4",
+ 0
+ ],
+ [
+ "00000000425c160908c215c4adf998771a2d1c472051bc58320696f3a5eb0644",
+ 0
+ ],
+ [
+ "0000000006a5976471986377805d4a148d8822bb7f458138c83f167d197817c9",
+ 0
+ ],
+ [
+ "000000000318394ea17038ef369f3cccc79b3d7dfda957af6c8cd4a471ffa814",
+ 0
+ ],
+ [
+ "000000000ad4f9d0b8e86871478cc849f7bc42fb108ebec50e4a795afc284926",
+ 0
+ ],
+ [
+ "000000000207e63e68f2a7a4c067135883d726fd65e3620142fb9bdf50cce1f6",
+ 0
+ ],
+ [
+ "00000000003b426d2c12ee66b2eedb4dcc05d5e158685b222240d31e43687762",
+ 0
+ ],
+ [
+ "00000000017cf6ee86e3d483f9a978ded72be1fa5af37d287a71c5dfb87cdd83",
+ 0
+ ],
+ [
+ "00000000004b1d9fe16fc0c72cfa0395c98a3e460cd2affb8640e28bca295a4a",
+ 0
+ ],
+ [
+ "0000000046d191b09f7726e4f8bfaffed6c30734afbf1f95e6bddbe0b07d9e88",
+ 0
+ ],
+ [
+ "0000000082cec8200e9ea055c2991bf74560eb7e7140691ea53e7828dbdc9553",
+ 0
+ ],
+ [
+ "000000003775b96d6b362d4804afe2d9c3cf3cbb46a45c3ccc377c94e83edd23",
+ 0
+ ],
+ [
+ "00000000037835a92404acb2f18768a49d4f93685ead30aad6bb3b073f411e02",
+ 0
+ ],
+ [
+ "0000000006cf75d17706d1f62e6b08e6ba5facfde38a8920b7d808a6b6781ff2",
+ 0
+ ],
+ [
+ "0000000003dff257cdae43703fcd0ca91fda0970f5fc04258b4608fb1942a6f6",
+ 0
+ ],
+ [
+ "0000000000532d97d18867658e08c789f627535652382147e33bf8626d4131bc",
+ 0
+ ],
+ [
+ "000000000266dfb79bb11dedd0ae748505863ab3ab731269cd71a2c2fbd159b3",
+ 0
+ ],
+ [
+ "00000000349ff0119d5c0dd8ffad8bf41cd6126a88416148b81fa4dcaebc42e1",
+ 0
+ ],
+ [
+ "000000003c61939b4799eeea4335218d30de9b1071605126d719dce0f0d14810",
+ 0
+ ],
+ [
+ "000000003d9284570ed648d2b12ad24046ac8b9abcf05c4e9813ea110490cf73",
+ 0
+ ],
+ [
+ "0000000001360b66e6dc0ccfbd75356034e721ae55c3d5c71a58be5d281c252b",
+ 0
+ ],
+ [
+ "000000000c114f42504916bfb2ee26ed8307b3f7f74226c1cfe1f5302ec23d26",
+ 0
+ ],
+ [
+ "0000000007acac3fcf97b4ca81821263b704364adaa2736fce0a0722bfed4f8d",
+ 0
+ ],
+ [
+ "00000000059768ef7731d27f9c2be48c6e16d7cb56680625f08ff25ead504280",
+ 0
+ ],
+ [
+ "000000000351c8908f1f52518ce4bd251b896ca3fbccb69a2607db6624bafcfc",
+ 0
+ ],
+ [
+ "0000000068d7ccae048e212e9e2ecb4d944f583b4490df4fbf654b4915597052",
+ 0
+ ],
+ [
+ "000000000e2aaa36417187233ff55325473bd5b7a164b358da60c96d1920fd77",
+ 0
+ ],
+ [
+ "000000001eb11ef6dbe0647bc87a8d218f6e59c2b9690f17edcf0dbd39cd0308",
+ 0
+ ],
+ [
+ "00000000022e7855e24cc3fff67ce093242434a8ffa45882333a0f08a40aad9c",
+ 0
+ ],
+ [
+ "000000000210130ff4e3186258c09a8463c1e196f5c5432b4c7b6954e907bf63",
+ 0
+ ],
+ [
+ "0000000000e01372ede322bf88ee5ed8a46dd4fd8df832eca16180263fc8b1ef",
+ 0
+ ],
+ [
+ "00000000a0701896e26d5d884834b267512e0af52c92edc4bccf1c5c803d3c4f",
+ 0
+ ],
+ [
+ "00000000869fc8d9ac1588f3e5bdfd60253e9824083800b7794010e0e9c6b6fe",
+ 0
+ ],
+ [
+ "000000001d43b3165ec30736f28f0761600b092686f861db23ec38f2d92b0ec6",
+ 0
+ ],
+ [
+ "000000000ef4092da8c2056e5933de0e1530194c3ad941a9b393fbb26f98862e",
+ 0
+ ],
+ [
+ "0000000001e3fed39f70023909f962bea146b03bc8e94e5d19d7da93123f4f64",
+ 0
+ ],
+ [
+ "0000000000b4b8c877bbe3cde97649845290bb78999ecff4621b9bf2ab16aa2e",
+ 0
+ ],
+ [
+ "00000000006095ba3b4742883a0ec427a3fd685ffb65b987ea77ebfedea7da82",
+ 0
+ ],
+ [
+ "000000000168f0a76a6068a34fc042553aff4aa63b906028f28c2a4c327328e1",
+ 0
+ ],
+ [
+ "0000000000af10f3079b4989ac4ff0baaecab38220510cdae9672d6922e93919",
+ 0
+ ],
+ [
+ "0000000000312791ada0f6a4c5eaf2a1cd57cd06f5970a8ab49923817b862c35",
+ 0
+ ],
+ [
+ "000000000055f3d4f45c4d199d9c230cb2cfeb68c8e934cfd061bd616358655a",
+ 0
+ ],
+ [
+ "000000000036b6129bb5a786bfdd75cb4b932f7dcae9da469d3ba35096f1e821",
+ 0
+ ],
+ [
+ "00000000002fbccf271c13e486673251ecd7951ecc12ee73c4390e0ff09e9b59",
+ 0
+ ],
+ [
+ "0000000000314e297a81bf002fc40eb391d8883ea45ee4e782385aa0fdba6452",
+ 0
+ ],
+ [
+ "00000000d3c473819ec3b3c268f7b555df22772e407bc8f246a47cfc579ec61f",
+ 0
+ ],
+ [
+ "0000000075a438fda6bdb391263d0a2a6e8e68edd9dd8f70fe5734eab9351eb8",
+ 0
+ ],
+ [
+ "0000000017ebae0a2bec50008b4a4ea8839798cbd9ff228e76aba087d0ff1736",
+ 0
+ ],
+ [
+ "000000000800466ba31c0bbc12b125f16d05ed27788de045e25d6f093817d29c",
+ 0
+ ],
+ [
+ "00000000002163c41f2264f202e611aeb9ba6c0a3ee95cd8e5e7e571edc64edf",
+ 0
+ ],
+ [
+ "0000000000de9882d417786fce8c755cfaad17f40cda744d4badedfe5e414e31",
+ 0
+ ],
+ [
+ "00000000002af352cf41f60a5ebf033bf7e4967c0597cee706ba877b795aefb4",
+ 0
+ ],
+ [
+ "0000000000009ca0030f1dd0b09cc628f2d4d278c87b20781a1b136dc395debf",
+ 0
+ ],
+ [
+ "00000000ffd27370a76d06a0da0e3805f47e35e2cf584d73d2c5ecaa2e525642",
+ 0
+ ],
+ [
+ "00000000720da6910aa75099baa020cb8db37e1dc19cdff66152225b7609c23a",
+ 0
+ ],
+ [
+ "000000000a5c2cc704bce5e8527ce91bac7430c659624ecd86e6a1bb9b697962",
+ 0
+ ],
+ [
+ "00000000084273545134e9a06483c8fab00c2b0628056bb1967f310c74a971bc",
+ 0
+ ],
+ [
+ "0000000002f66f4da52804647b1c3e1f89d17bdb05e9cd4ebbd922007c773f21",
+ 0
+ ],
+ [
+ "00000000c46146c9d0a67a354b3f82947e52670a3bded6d8513ab34a68ae18bd",
+ 0
+ ],
+ [
+ "000000002f61c429d7dbe7bde75796086efe574998766806138710a2d6001eba",
+ 0
+ ],
+ [
+ "0000000001daf3e3e78a57df2c2d2ddd14093d10515925e75c818bec3bbd30c2",
+ 0
+ ],
+ [
+ "0000000002e133a7427a9aac6ceca969b27507c14111a45512cdf8f52a436de0",
+ 0
+ ],
+ [
+ "0000000000f7c4374d458666740de1d0e8c55229a209ced7c38e38708781487c",
+ 0
+ ],
+ [
+ "000000000035bb9ea329ba30b83eeb4ea6f57c2fe703b97f9b879f21e22643e0",
+ 0
+ ],
+ [
+ "00000000001220503e0aaee266bca85de09ce97b0091f24972d1ad1c8afe8609",
+ 0
+ ],
+ [
+ "000000000010a614c60457f8d2ae2bb826d037f52113252888fadda8ed773c9c",
+ 0
+ ],
+ [
+ "00000000585a8b882ecff8aa8434feeac4ef199ca669bd81ed473e37f0bb4528",
+ 0
+ ],
+ [
+ "000000009504ffdb5fe82ad88218fb5e75a8bc185247e30e22d23b9fd9b7f282",
+ 0
+ ],
+ [
+ "000000000ddec7d73bcd653168d82e34cf5746e006bccda8a9c031c3289b9568",
+ 0
+ ],
+ [
+ "000000000cb6620ee4e8cb8b6b4d51251e5961f7ae2e83538ab3a4fef3bcc773",
+ 0
+ ],
+ [
+ "000000000239224a0841738513c1eda712b73266ea958aa75f44a3985ebfab82",
+ 0
+ ],
+ [
+ "00000000002630c7c3586fcc19079300403c54dc293bcfdf8a9981f85a5c31bc",
+ 0
+ ],
+ [
+ "000000000028d8c34f44e51fd71f5401094a983f6566e6d08ce86ec5d1bd639c",
+ 0
+ ],
+ [
+ "00000000000dca95f1828adc3c37b4625f60aeb35a6614a4358322b7a6bc2f7d",
+ 0
+ ],
+ [
+ "00000000d72ec84fda18959ddc474d1a31a3a13b1d94695136c4810af8c01a0b",
+ 0
+ ],
+ [
+ "00000000327c29604996eb7f0a208160969ee4408a1cad277a956334f94e0f35",
+ 0
+ ],
+ [
+ "000000000e1bd41d009c1910fcfee7bf1cc1adb04b0b7a632ac36c1092f01bb7",
+ 0
+ ],
+ [
+ "000000000201a5afed48b9d095b949229e9882ef8bc96767be3097c87264dfb6",
+ 0
+ ],
+ [
+ "00000000003f28e8f3f9c80b1269bb0aa3b57501c12458550ef04fd43aca6a33",
+ 0
+ ],
+ [
+ "000000000029e09fc14e38a6a0103c8c67383f41af7d76998055682525f4ca89",
+ 0
+ ],
+ [
+ "00000000285ce297602995582ba5d32d583d618a6a92643566e25dd36cf2b7ab",
+ 0
+ ],
+ [
+ "00000000657045fa54fac52b8480dc84bd4c418940ba63679f4bd6add6a39962",
+ 0
+ ],
+ [
+ "0000000017b7bb58be05a47ff7c4ead27db750813d6bcf3f99cbcc35324cf445",
+ 0
+ ],
+ [
+ "00000000003a310e39b6df17f17450496b4f5c1593399bfa1ab8b4d39bac9b25",
+ 0
+ ],
+ [
+ "00000000000bfbc5294f003548a9636ebbcea3ba42577821266317676fbc363c",
+ 0
+ ],
+ [
+ "000000002329351dd70c24da2eea5ac19f65b6053c4611aa4eb93bcc2783c57e",
+ 0
+ ],
+ [
+ "000000004ce02f1005aa6fa4d158c6e4fce95ab053d88ae74881dd080c24e057",
+ 0
+ ],
+ [
+ "0000000000fdaaa54cdaade8cfb75245de0747c60c0307ad11be9fe154535565",
+ 0
+ ],
+ [
+ "0000000003dc49f7472f960eedb4fb2d1ccc8b0530ca6c75ed2bba9718b6f297",
+ 0
+ ],
+ [
+ "00000000014ca604d769d4b99fff03ae3ac84d1e8eb991c5dac7c3cd4d9e68ee",
+ 0
+ ],
+ [
+ "0000000000190ab8ecef3a3d5583563851672d81a4d4d952b8cf3bd503c655e5",
+ 0
+ ],
+ [
+ "00000000001204d263b607987fab11e1c19c94b7e3e674cc73cc2fb7b05fbf07",
+ 0
+ ],
+ [
+ "0000000000141e8d7f7ac359a8ae58e35ce6010c25ddd6f1881f41c0b939332e",
+ 0
+ ],
+ [
+ "00000000946344dd06ef5ddd13fb74f20c475daf911ff4e3f1dcdf64c330e274",
+ 0
+ ],
+ [
+ "00000000ec77a7892e48b85bcbaf404d16d7fc93747d7e9e3ba6195a9b6f1525",
+ 0
+ ],
+ [
+ "0000000018a305c04dea8e93e423ce9569872e0ec5af49d23a0e3872b0ad6297",
+ 0
+ ],
+ [
+ "00000000055e32c5f8a86c9a712eeb6440bbf9810ae6da12d0cea2493138a885",
+ 0
+ ],
+ [
+ "0000000001913fcbe67badbce4234e86e35a1ea867ecd69814b5f5ab039b7d4b",
+ 0
+ ],
+ [
+ "00000000002c71fe4403aee704720ceafd21f9f8c9c97a8bfbd25bb46223aa40",
+ 0
+ ],
+ [
+ "0000000000343a42da0c811836d0785c272591facd816f0e7fdcfb1109d8f9a8",
+ 0
+ ],
+ [
+ "00000000000309b182608b3eea7fafd0d72e3c79a0a3a9cda03cde3947e332e1",
+ 0
+ ],
+ [
+ "00000000000204cc04e421c3958a64d7bc024a474ce792d42ab5b48a5a6f3927",
+ 0
+ ],
+ [
+ "000000005eaa010e7255bd37e0b00780575074a74d889e17c4dbc578f917348d",
+ 0
+ ],
+ [
+ "00000000a0d425f62d9196c069286dc6635ded9d027de40070d397e45bd63e0e",
+ 0
+ ],
+ [
+ "000000003355fd37068ce2d5d2a94ef964eeb9b687f21f4a00850a3e6cc4a71f",
+ 0
+ ],
+ [
+ "000000000ca9148dabe9424cd8c96860c90d836ab25970a3e91856764e2e640c",
+ 0
+ ],
+ [
+ "0000000000bde23f829dde8edef35436be4b8978da21fd2c3a8100ef5334e3cc",
+ 0
+ ],
+ [
+ "000000000028bb26f1427fbfabeae65d55a9e59e18230713e40f0f7c9c2dee12",
+ 0
+ ],
+ [
+ "00000000002ac05422d254e597ee6b5e0f8be9b3e2f887486442d720c7766919",
+ 0
+ ],
+ [
+ "00000000000e36d0b6f187dd9601b1d1dcd987c3e0f6a081ffd039c7c5e32462",
+ 0
+ ],
+ [
+ "0000000000048d7b1f2a2a11fda34a5cfeea067ab03e482931e5a0f463f438ba",
+ 0
+ ],
+ [
+ "00000000f780ab88c8a4f4247573a749fbb087a4e3fb6a7d29926de8a9ab3462",
+ 0
+ ],
+ [
+ "000000000313bbe6a940e6a8c40ba091aa1ebbaad135bbbff3ed8ae07cf574d2",
+ 0
+ ],
+ [
+ "000000001d4ab29721aa2722482562670a0d71dc1eb73231c5dafb64756b04e8",
+ 0
+ ],
+ [
+ "0000000006588bcbdec38d19962b96cf0352cbf1b90f3379cc6787d018cdb96d",
+ 0
+ ],
+ [
+ "000000000022e79539a21ac24f9daa2cbddf2bb4a3125f88a5efc20d13ea856b",
+ 0
+ ],
+ [
+ "0000000000dd284b7fee584cc578a10fbe57e8efe6bf6ebacb23c0ac5d46cdf7",
+ 0
+ ],
+ [
+ "00000000001451143787f411c93d5506065c3fb597966f2fd7a4a5c078ee6aa2",
+ 0
+ ],
+ [
+ "00000000000ca977394af1e414dc1f9d83efa007f7226e11d3a00f59a1fdfad1",
+ 0
+ ],
+ [
+ "0000000000011f8caa80580e7a796bbce5b84e60731bf48e03c6ff5c6bba868e",
+ 0
+ ],
+ [
+ "000000000001705beb1376af1af08b437acef6befbe7d3b60c5fbaf6bb7f38c9",
+ 0
+ ],
+ [
+ "000000000000c838f1f45422d93ca9b5838368a37423efa8439ee24b2bf247a2",
+ 0
+ ],
+ [
+ "00000000000111ad857d31d07fdc8b32d17af2522c18bdaccfef449b29d17362",
+ 0
+ ],
+ [
+ "000000000000312a7718fc616b0ecfdbf6066f71ec1a4a8c43f50f02f61cc398",
+ 0
+ ],
+ [
+ "0000000000007d232b217a59b804ef67091c5720a5460c2c16bf97b97a24801e",
+ 0
+ ],
+ [
+ "000000000000177235c33695aced585685b4c500eb76e72caad02e17503900eb",
+ 0
+ ],
+ [
+ "00000000000037f5c5890da7a8e2acd2b0669ad7db648ac43140c637a1c81637",
+ 0
+ ],
+ [
+ "0000000000002123904063f223bc35135c426a4f9a0b74c1907e837b810f0321",
+ 0
+ ],
+ [
+ "0000000000000961db809da357d91a9341170fafef9f24896d8730bd05cf3f96",
+ 0
+ ],
+ [
+ "000000000d2e8fcd05eb874e98cfc3a6e239f6974950e6f50b0487513ecab760",
+ 0
+ ],
+ [
+ "00000000017e362508c8db23fae0431eaed708d9db13e48fd5d318066bf6733f",
+ 0
+ ],
+ [
+ "000000000011b2bc4fe36f90b7ba5a62f974db250bfdc285b70c71148023c7e3",
+ 0
+ ],
+ [
+ "000000000001be28570b378dd5dd2eb3aa495c229913b6757fe8900dfa3cce99",
+ 0
+ ],
+ [
+ "0000000000242bd0bb16d0a5324e0b4b5a83697dabb3b4a059084557478e50b9",
+ 0
+ ],
+ [
+ "0000000000d8ce69d18da32ed52e503d6b5ad48d970b90545f956b2d2af2edf6",
+ 0
+ ],
+ [
+ "0000000000366655bf0cb3dd0cd7801e0adbd26b5b441b77a9e3642597effb00",
+ 0
+ ],
+ [
+ "00000000000dc7aa00d4607ca8374d40d1187f1c084b620edb45fc39bc8d2db8",
+ 0
+ ],
+ [
+ "000000000003baf60d9c6e70a765cf517f66a124509191188e9547ad09edf68b",
+ 0
+ ],
+ [
+ "000000000000e0f476893b8fb4d37e855353075fde73dbc1fe181cc956349f19",
+ 0
+ ],
+ [
+ "00000000000032ed16b7de758abadf4a4fb2df7a101ff275c51f29e1555a89a5",
+ 0
+ ],
+ [
+ "0000000000000a564d03f0f2fe20f6fb5f038d931f732d817641cd7fff3b0acd",
+ 0
+ ],
+ [
+ "000000000000011aa4d0fdcea8d4ca85cd5d548e322e2b6abd17f8444be855c5",
+ 0
+ ],
+ [
+ "0000000000000610588540267a0eb544531047d4c8af0f21fca7cd3d96205cfc",
+ 0
+ ],
+ [
+ "00000000000002770dab5e14843149df8f76b8dc8458ed3ed2ed8a14a6e2e564",
+ 0
+ ],
+ [
+ "00000000000006b70ebc9f75bd32f466602cbd4b86c3c2d2379059542bb8bec6",
+ 0
+ ],
+ [
+ "00000000000000ef579af389fa7674f98a2371063fa8b218c5ca0ad94e21b896",
+ 0
+ ],
+ [
+ "000000000000021b6108dc988f9153383f9501ab9001109aa87902ddd4c8a4d1",
+ 0
+ ],
+ [
+ "000000000000022c02ff22bc0af5201f0e1a14a75879c494731e4fbf999218c8",
+ 0
+ ],
+ [
+ "000000000000032651c988edc1ccd08e82b888cbb8135e24a958ac0c0b640d5d",
+ 0
+ ],
+ [
+ "000000000000015aefdfa0790bed326c38c358c07aac0674f5b2e771258b8df3",
+ 0
+ ],
+ [
+ "00000000000000822e1534c86afef911b67d3fa20cf2b12d93d20d64005f54d7",
+ 0
+ ],
+ [
+ "00000000000000338b871276768c923b1c603fd6150bd054c2287e532e61de7f",
+ 0
+ ],
+ [
+ "00000000000002d0af52c0cae894bf836b61137ace2bd7500abd13a584c02741",
+ 0
+ ],
+ [
+ "000000006f8443a458f38d8731821c07a2fda0ecdbb1cf797f541844d468ce0c",
+ 0
+ ],
+ [
+ "0000000000b6fbd8b4e227f5514979a61d8b0b918d2adc154e585ca926386704",
+ 0
+ ],
+ [
+ "000000000f4f5e49b10278e27d9dee15b92f9d4a257138a206831e0c00188767",
+ 0
+ ],
+ [
+ "0000000002c7e9769bd8ae9906fc5682e937b5c31ab5b5b86e4d70af2c15a95c",
+ 0
+ ],
+ [
+ "0000000000f68a1db8cd387e0a2f93f45149fe1ee4a230bb386313bdd42058e8",
+ 0
+ ],
+ [
+ "0000000000f0f65c360c8f0f9853ad1142f16675dc1175d61afdbef977776b25",
+ 0
+ ],
+ [
+ "000000000004f734e634156511cbef7dfefebdf317e7488aa6c2562572d7ecb7",
+ 0
+ ],
+ [
+ "0000000000002a46a7a16787e8317dc567ae26816324c2035be0186ba54d5cb8",
+ 0
+ ],
+ [
+ "000000000001a593e6f01875b77e270163538d88452779bb557df7c2607c28e0",
+ 0
+ ],
+ [
+ "0000000000004f24cfafa10bd50a452535f64be577a6161e51c7c71542f654c4",
+ 0
+ ],
+ [
+ "00000000597cce73e84b63f08cfcb9b01f5e7621752d8c8e08fabbd6ab5c0dd5",
+ 0
+ ],
+ [
+ "000000007cad379df01247771fff471bc99faea1b86218602f45ab13efc5e9f6",
+ 0
+ ],
+ [
+ "000000000d6085aab25892be49c49d6c0a3949befdc3ddce2faa46b104e1e804",
+ 0
+ ],
+ [
+ "0000000002be5996786b42d6a229093896aea9966b1854ea261e01e84da1f420",
+ 0
+ ],
+ [
+ "00000000002684b72056e270b115d80b12b2f68eac7412355287226aecd9b5e0",
+ 0
+ ],
+ [
+ "0000000079ea27efb24366c87856a9e371c56fcbd59d09d3164a5c2fc15fcbca",
+ 0
+ ],
+ [
+ "000000001694120525dba4548ca54087544da1fbefa51c38f0208d683418825d",
+ 0
+ ],
+ [
+ "000000000693e80d372938f3553151ab9d0a9a6922182591c701df739dc9a502",
+ 0
+ ],
+ [
+ "0000000002950d9cb23c8511937811910b712f73d448e6fdc2e39e029b86848b",
+ 0
+ ],
+ [
+ "000000000091c40056c6a48f33db17764af89c01f62ae653aa5e494146164cee",
+ 0
+ ],
+ [
+ "00000000001f373c47e1a39af4e1ebcd8c88411ec49d6bd520c2781564070971",
+ 0
+ ],
+ [
+ "00000000000809ca4b2170c57958709b867095b1972d80a2ee55359fbd0940fe",
+ 0
+ ],
+ [
+ "0000000000038e7bd66fc3308447b1370dbdd0661c427c512bdbc641ff360fb2",
+ 0
+ ],
+ [
+ "000000009a3325df76e2de1fc1970cc2f241fa8a41da9ad745a0d9666d9ff51d",
+ 0
+ ],
+ [
+ "000000003176e92ff837bf43a48a995c1a321b166475f586ffb4b962e0254a4a",
+ 0
+ ],
+ [
+ "0000000001ae3292e81ca3859b75bccd5bff825cd9f496efd085160c716ed05e",
+ 0
+ ],
+ [
+ "00000000033bdac4f0d36bb912fba28bb5caa54d1b611759a10f79ff3c969cf2",
+ 0
+ ],
+ [
+ "00000000004c6db7fa0e2c9f08693abfeb128c5827b511a5c46c623a103b416b",
+ 0
+ ],
+ [
+ "00000000003d87f48bb95e9431760d0c5f4f93c77d02fce9dd1673e9f5b01029",
+ 0
+ ],
+ [
+ "00000000000e214fc3d8b97571eb75d248ca29f8e25a584c33de8488ceee72b0",
+ 0
+ ],
+ [
+ "00000000000133269b7159b828700d02de770a8cbd91f3d166e6bbc95d8e0dfc",
+ 0
+ ],
+ [
+ "000000000000cc92e2dd933a08f7fd87f84451627982fb66583587858217c059",
+ 0
+ ],
+ [
+ "00000000000030708136c20c4c8216314005b3cb5c551ded33b26cf64d2ff47d",
+ 0
+ ],
+ [
+ "00000000c472a1341d479ed02f31b699e448c035049a7092670b38f4ec6121f0",
+ 0
+ ],
+ [
+ "000000000a358834d6eed41b9b7161a338aba53828111414cdea7552ed15548a",
+ 0
+ ],
+ [
+ "000000000e13e77372daea775c8358916e57ed11835899c14e5140ed9be11089",
+ 0
+ ],
+ [
+ "00000000008252cd0931f94b2465bd4f93e4bfeec6697962c5b034cf3d12cf7c",
+ 0
+ ],
+ [
+ "00000000019812cd6cde3a43831234be71e68118be24a80161349b8b327acb5b",
+ 0
+ ],
+ [
+ "00000000005865499f301adfb59f8380743e4c3b3ab220ca4eb97dc6628df626",
+ 0
+ ],
+ [
+ "000000000015f77e1e61329560a4378eb401fa5bf0ef90b0a014a4d7857ca7a8",
+ 0
+ ],
+ [
+ "00000000e9cbcbb625e8a463ba8e7f14be46ba9538ffe93338784ccad3d992e8",
+ 0
+ ],
+ [
+ "000000000fb27169efcc2873cfaac223ebb91cc5e1e5ad7e9a312d42bedf7c42",
+ 0
+ ],
+ [
+ "000000000c9c96d62ebfbf3fa4003f1d46d175140ab084dee17e8125fa40f24a",
+ 0
+ ],
+ [
+ "000000000311e3a766b1ab2064b68a344a561eb496d595126808ffb166c71cc1",
+ 0
+ ],
+ [
+ "00000000677568c82262ac3a4ca3f909bdfb0b35145ad490fa3fbdc719d06b91",
+ 0
+ ],
+ [
+ "000000000ee77ba9ab657e51fd9140f5c9b46731d9341e98188f929c97d04746",
+ 0
+ ],
+ [
+ "0000000008a67eb9c91a6d74168f3f385270fa942ea00bdd31924d1b6ea11148",
+ 0
+ ],
+ [
+ "00000000017f93c9e0026e90d579e18c83b4a8557f0c00e9b85ab164cf4466c5",
+ 0
+ ],
+ [
+ "0000000000994efa379235c03711a8e6b29895d928b5fde96cb01c02374c0602",
+ 0
+ ],
+ [
+ "00000000b3be9f23c943d71d7c7dbdf6dd672d77a712f6c83e9796a85e4379f2",
+ 0
+ ],
+ [
+ "000000000713e1089b0b2bdcba462b740c9396f822f1c73e090713978a7f1314",
+ 0
+ ],
+ [
+ "0000000002fc44d358401a7ac9ce4ddcb17f3cbac08e40242e755e60ab2292ed",
+ 0
+ ],
+ [
+ "00000000021ef2c04fd30be7049f73b9a8353ac96a467dd5f0b9c1457be1bc5e",
+ 0
+ ],
+ [
+ "000000000023b95b440ccbbdcb914172cf675cd15d6111bd7f5a436a4925d36e",
+ 0
+ ],
+ [
+ "00000000001983521dbffd1b742a6d4b5dfda3f46579fbbdd83a2ebf9a039bec",
+ 0
+ ],
+ [
+ "0000000000044d53dbea312432e68fa90dc2148946f613216dbdeec86f6a67c1",
+ 0
+ ],
+ [
+ "00000000000107667692f12d21a55a72ff1dce828f96872e36c35bfbae475a8d",
+ 0
+ ],
+ [
+ "000000000000252d1d0c01744ec25af801ef7c57e2581c95295070b6a8a85bd5",
+ 0
+ ],
+ [
+ "000000001c1da54e16dc06158677024d9e74bff39bfaec83434ac33673fcc251",
+ 0
+ ],
+ [
+ "00000000b4d0c6ae86bfdf7ba4c205fc3e6b3b6d63836b85e30e9d8bac922301",
+ 0
+ ],
+ [
+ "000000002b16179cb022bf678bd847dd6fc1908d0df04abf0c7874981eb33ee7",
+ 0
+ ],
+ [
+ "000000000e6783554aae41856424d184dc4fa061f40676efd107e6f933a25641",
+ 0
+ ],
+ [
+ "00000000005ae4acbab519895b4b523d97a09e381c9e4b044e642f73b8c0f1b0",
+ 0
+ ],
+ [
+ "000000000010372b59c9595d947064804b75ab21868dd075a3842ab7d2df6181",
+ 0
+ ],
+ [
+ "00000000002f9f587ea304093be049d3142ac0c92f9c68928a4f82d12b929b69",
+ 0
+ ],
+ [
+ "000000000005d4cae51b3c76dc3c61bed0c265c4f228c0c4d1d3d147146c34eb",
+ 0
+ ],
+ [
+ "000000000001a5b6c0e0a0b485a490cb52ccdf9b22596656039b51545bb07be5",
+ 0
+ ],
+ [
+ "000000000000d723d0976338edf55d08edab995dd6283cbb688855f0dca6e8f5",
+ 0
+ ],
+ [
+ "00000000bfebfae90208a82c7fa06c0f61674dbf1e4f9162e370656c38d611bb",
+ 0
+ ],
+ [
+ "000000000c91cd144b2a92ab5024c87f70cc1d76a4a7f26a82a98c5aaad62850",
+ 0
+ ],
+ [
+ "00000000077c8114eb5cfb69c3924c699d0c70334360dd1daa95db0db4816953",
+ 0
+ ],
+ [
+ "000000000348a6443e091db8f68e88a10afad7c6e3e5392247902c4b4feade43",
+ 0
+ ],
+ [
+ "0000000000d63b70351e05829ad8a56336521b361b0d50eb7ea1f5b46c25b00a",
+ 0
+ ],
+ [
+ "00000000004658603163f0ede572120a1bbfce8d313aa282ae54d2ffd9fe9079",
+ 0
+ ],
+ [
+ "0000000000048063b410c793db34856f23acfb19a0ce72f5997fa572773378c8",
+ 0
+ ],
+ [
+ "00000000000228fb6e587fa593ff8b4764064bba8bfc2f43ba5b1f12af33d04a",
+ 0
+ ],
+ [
+ "00000000000082e3ddb75c0ea2a98922b1556ce10346f9bb0cedd97ccb3fdf62",
+ 0
+ ],
+ [
+ "00000000000005571b54d4886b44b81c21dfbefa554cd7c23430e5aeff6b5ae2",
+ 0
+ ],
+ [
+ "00000000306a603ca1a0d961e08e103a9f13f3615163c3373d1bd2a67cadc2a7",
+ 0
+ ],
+ [
+ "00000000195d93ba7ae19832b622de86ebdadf3c78f1751ef2b2e9b0e3a530d8",
+ 0
+ ],
+ [
+ "0000000000476d0d00cbc68bb20b4893f0e608b02a1e029b8c6c73e169c49e69",
+ 0
+ ],
+ [
+ "000000000051348044bc10fc05960c244c3ccd3b3b6c145ffd9958a1c8bc0215",
+ 0
+ ],
+ [
+ "0000000001e4df369203badca9aedc28c240d592b12d284ce0b0463fc7537c09",
+ 0
+ ],
+ [
+ "000000000091cc1ccd448b0ec9185618a84dea96f52477cfb9b9ca2b60cebe83",
+ 0
+ ],
+ [
+ "000000000024a50299c0ef0c6dec9c64336b6cf5c1a1b0013e22fd4fcee1d7d1",
+ 0
+ ],
+ [
+ "00000000000349248c1df06c3783d1270cd97ce7f605b9036fca0fdc2f0fbb96",
+ 0
+ ],
+ [
+ "000000000001afe6793e7427a3d780876d26eb7f2ded92563f991bf7302aea69",
+ 0
+ ],
+ [
+ "0000000000007148006e139e24d9fccc307661c9a0cbcd1af983487c2f0780c9",
+ 0
+ ],
+ [
+ "0000000000002734722a341984738177a3f6f264291424e4984f2128d921bf29",
+ 0
+ ],
+ [
+ "000000000109b02caaa95e49a477757a41a42daed40e92f54fa09e63f5538cd2",
+ 0
+ ],
+ [
+ "000000009a11c7ff8b8fa7fbff5a04c25906f701ab5bd67195736f9ccc839ab9",
+ 0
+ ],
+ [
+ "000000002b1d77f8e0cd60af1c62ef6d381e8905665b15a7fbc546d0c1a45e18",
+ 0
+ ],
+ [
+ "0000000002588cb017de9e2f23cea7edc5082f1b3faec890f9252d556efeac40",
+ 0
+ ],
+ [
+ "00000000008b07f177adc24a4b1a64d2dbcfbcc903ba861d493e11d6b33af7dc",
+ 0
+ ],
+ [
+ "0000000000bab8db5020aa8e052165275e8eb3e7c843533246bf6e4c8374757e",
+ 0
+ ],
+ [
+ "0000000000138488fdca8bfc327e6dbd6c72c5f1dc5868d9c0ea886665b9b56b",
+ 0
+ ],
+ [
+ "0000000000094021fc954efbf08be667fef1b817e8715d4093a561fc30264aa7",
+ 0
+ ],
+ [
+ "000000000000e8183e64072db79adfc6c09b650c4178001be3fade4050b06005",
+ 0
+ ],
+ [
+ "0000000000004c93e8661c75974cd191c68dd66999da4f70d039c0ba4a12b970",
+ 0
+ ],
+ [
+ "00000000000021c675b3ec404bb996f5e68f9eeceeac6946e5a6822987824d33",
+ 0
+ ],
+ [
+ "0000000000000ad85684d30f25d1ec34638f099df2f33b418a07307c68fe3c2d",
+ 0
+ ],
+ [
+ "000000000009c6add76ac42a1942c4ce74d25d1b8975d4e3ac8932185e785a44",
+ 0
+ ],
+ [
+ "000000001e7d828d354716881683eb6fb5caec5d91afce298e4e3bcee9574924",
+ 0
+ ],
+ [
+ "000000000a0e438ab203d8fd3e56100f2f14759f704bff6c699df0bb4e9aad64",
+ 0
+ ],
+ [
+ "000000000b7d5c2895df8bc1fdf5d31e0f663564cb5cff3b18642c44a71b6248",
+ 0
+ ],
+ [
+ "000000000193209ecd92fce00a75975446423d94a325ed525c15d5ab921da273",
+ 0
+ ],
+ [
+ "000000000020835bdc30ac67efdbc785d15186914bc14e86387f97450df46418",
+ 0
+ ],
+ [
+ "00000000000c9078321f0030214c75e170b01ec664d39bab1b1e48460a54eb63",
+ 0
+ ],
+ [
+ "00000000000ac68b63d486ade190dc9108eb3730d25e7537649fe21c30e0121f",
+ 0
+ ],
+ [
+ "000000000002a94dfc5f4b677b251a7a7647dbb99c0803df8658222227fe3e3f",
+ 0
+ ],
+ [
+ "000000000000b076bbef0e50593b1595ffb3d571e7ad95dbdf06dca8824ef7f3",
+ 0
+ ],
+ [
+ "000000000000167075c8bcd24233d25cd268271c0e8fcb6f301ee1b6f6ff0341",
+ 0
+ ],
+ [
+ "00000000013107aa587bcf12ac445330ff0325d73c5253f7e6a49ed8c50257bb",
+ 0
+ ],
+ [
+ "00000000090ff53d49c9ffd51511af8d5cba2038a8e25e3b17186b1bc941f43d",
+ 0
+ ],
+ [
+ "000000000d9e704d5607f77f8983cc56069571a3761d5bd5da55f05ec5d8e844",
+ 0
+ ],
+ [
+ "0000000002b2b4c0950fb6390f0ae860840e84eb0a82e5e8a9bc37c14bbf43b0",
+ 0
+ ],
+ [
+ "0000000000be10137a2434dce1d97850b768ce878c1c80ec905f6e9f21e65fa7",
+ 0
+ ],
+ [
+ "00000000005cd966f80183d4c048e63a5c14f649298dfd261d989d9e3c026bf4",
+ 0
+ ],
+ [
+ "00000000000e8f30e55006a4082380c4b1a372b7ad919d3a9b0a52fe5ee881d3",
+ 0
+ ],
+ [
+ "0000000000018c70a4c27bdba237ad19ebae5d3ca23f1394ccc746d73669a1c4",
+ 0
+ ],
+ [
+ "0000000000022acc8432c883953227786f7a6560aeaf0176d232c8affa5b25b4",
+ 0
+ ],
+ [
+ "0000000000001854e95b28b4efcb2cfeb08c76d8cf1fb03f2055b3fb758f3a1c",
+ 0
+ ],
+ [
+ "000000000000187080c2c39f5a3ea8be72ac4d3ec0d16b21cd34f1541bef23be",
+ 0
+ ],
+ [
+ "0000000000001593766a3c63b524f658ec7690df467cc7bbcebbdb56385500d4",
+ 0
+ ],
+ [
+ "00000000000012d6966dc51a41f2c617192169ec8418405e164ba83b9f7ecdfe",
+ 0
+ ],
+ [
+ "0000000000001d0c7d0a2605e127b00448b71e756ad96625116ab8ca18f74900",
+ 0
+ ],
+ [
+ "000000000009cb439ea49282d257595ad1f7602856c16cc26fff423f7783c792",
+ 0
+ ],
+ [
+ "0000000000889282b98336c994d7420a639221e0484b511227fd616d78dbd028",
+ 0
+ ],
+ [
+ "000000000071a4a2ad6767864bd21239c74c9912a40ca9fd3b209e21b66460d9",
+ 0
+ ],
+ [
+ "0000000000f3ed2c3c9a7c3a7291e859cecba8cf9243d23a4892e6be8ea9b70f",
+ 0
+ ],
+ [
+ "00000000006a4258ffdff8b7f6f4f685ce18c6eb1d7a1cf501ca9e02fcb7620a",
+ 0
+ ],
+ [
+ "00000000004af78f1a109d1267a9c24d69c6a4b30fea49f0efa6c8834cf394f9",
+ 0
+ ],
+ [
+ "0000000000193bf3efbb145747198470a81b2cd33c991057676742d5c22a64b2",
+ 0
+ ],
+ [
+ "000000000006b436798c7e4a8c3bdbf054a66707feee5a18ce9ca57eb95bb48a",
+ 0
+ ],
+ [
+ "0000000000001db50c7caa3a02ea4f173343f958f334a8bf3f8638add9e69b34",
+ 0
+ ],
+ [
+ "0000000000003c621629cc0bcec5968d61d2e42c6673de4d46555118ad5001d8",
+ 0
+ ],
+ [
+ "0000000000001262bef2918265f6dd4534013a4650444054fb4f5e490c5ed57b",
+ 0
+ ],
+ [
+ "0000000000000120ceee972d70cc84430006645997c7337976c673bd75cbef2b",
+ 0
+ ],
+ [
+ "00000000ba16134dc0c418a116b97ad5deccd6bf6e3daa028a8a6a80d7823faf",
+ 0
+ ],
+ [
+ "00000000a1a00d6d6fe0660e63402a5a7c7248589211594d37fd800456ce84b6",
+ 0
+ ],
+ [
+ "00000000394766cec78f962c29aaa715b66e3ad34e1f2323dba45e087cb3b395",
+ 0
+ ],
+ [
+ "0000000008b15a3020676f5e084210ecc05f646885eca1cf6a10e9ae9e3995cc",
+ 0
+ ],
+ [
+ "0000000002cf7eb98abe784f6e516670a88b9028a6faabfd099a364c2dc5c42b",
+ 0
+ ],
+ [
+ "000000000054015fec337a9ee43eea501d2292f031f5bc1f09758d20f5cd3135",
+ 0
+ ],
+ [
+ "0000000000068d24d31a9f1192d848155a2f90939627bc456c9a337135a923fa",
+ 0
+ ],
+ [
+ "000000000006262bd09358258edcc455f9ba46b7f9d6e69d0f6b9da89488a4a5",
+ 0
+ ],
+ [
+ "000000000002327bf77ae67961463ea98a78dab06c24ac7d58b1727c5f856626",
+ 0
+ ],
+ [
+ "0000000000006672235c1606fbacd7861b16b267d203b4d687708eeb1fc25e6d",
+ 0
+ ],
+ [
+ "000000000000ac0c9a39a47313a8715f125c46d6ea8be8741b99b1db4a8aae47",
+ 0
+ ],
+ [
+ "0000000000007e93f6578e7856aae0ecf6341e1312664d9e1d812ff254c37ae6",
+ 0
+ ],
+ [
+ "0000000000002a980acdb1443926875e7d4a57859b2b45ce3fa92c7716319f62",
+ 0
+ ],
+ [
+ "0000000000683bfd82c63514bc58a80daf699a6bcd040bb2a499540baf52463d",
+ 0
+ ],
+ [
+ "00000000373e6262928d7a6cac965b294aef35f90b72c85100ef91501775e06a",
+ 0
+ ],
+ [
+ "0000000000f7bc44061b65c62d4d7747138df127dd2a30f583c3ebb66a25c7a4",
+ 0
+ ],
+ [
+ "000000000212a71c38d0e13ab7c5646c949d4b7ca23afedbe351a43b7607043b",
+ 0
+ ],
+ [
+ "0000000000a836e88f76ee5dcca1e884572f32f4460a3b024280738d76e98ced",
+ 0
+ ],
+ [
+ "0000000000413f6c1b1c9841961636bb3290f2410ba0731f3522c4ff3faa2e0e",
+ 0
+ ],
+ [
+ "0000000000082336107412226110ab2a53016d4faad4deec048828507a300248",
+ 0
+ ],
+ [
+ "000000000000a91e7a3f35a23f01621dd051e314da617714991467131808d3bf",
+ 0
+ ],
+ [
+ "000000000000cd6576950f6f238227c3ba7f62405ed1bf3af4878c6dc1b04635",
+ 0
+ ],
+ [
+ "0000000000674099e9741e44da03e9531402a2607a19a65660b57470340828db",
+ 0
+ ],
+ [
+ "0000000030c4744001ae85f9e6b46ed0664449927b86b8fbf25b22b851d23671",
+ 0
+ ],
+ [
+ "00000000002f5095ad1a12eb9eedf88ce1e7268368461b6b4e10051148f436cb",
+ 0
+ ],
+ [
+ "000000000057d3e2a77eadb8b9613cb839ab02a96094dd5d0a6d1f09026c3936",
+ 0
+ ],
+ [
+ "00000000004e0a28be887d6ed037cd9102cbbda7d6c9e584ba51f2c2dce96232",
+ 0
+ ],
+ [
+ "0000000000211346d8099f7ecea72481c4cd45591f5e0d7e347725ac2162f142",
+ 0
+ ],
+ [
+ "0000000000199ae9fc06c5acee766db6033b86f76c266cadefe1461c611c2198",
+ 0
+ ],
+ [
+ "00000000004c9e5748558d4f5a75bc824171e3b958152dfd6844330f1e907f8c",
+ 0
+ ],
+ [
+ "0000000000137addf1521361dad1ee007eb9e6dd4eb8441492ebfaa3c240d556",
+ 0
+ ],
+ [
+ "000000000054d4c77bb7964e5327c35760d87b890ea336aec5ecdeb783350738",
+ 0
+ ],
+ [
+ "00000000006b7b06d04818e97a4df66164b471912f88d9cd02de4af6c8bbe74f",
+ 0
+ ],
+ [
+ "0000000000380fa9858e3e90335c061a3776a26bee1e8b6851de33ec63670782",
+ 0
+ ],
+ [
+ "00000000000842598b03fb79ce7386e9f9181a02dcf1effc8f70d3ff7368ccd5",
+ 0
+ ],
+ [
+ "000000000003d3475edecd733fc7b82432882d9c9f1350a98ef8921b87db4dec",
+ 0
+ ],
+ [
+ "00000000000000e330a8d57a38dbcc0b0a5dc7a4210f231b8082b9be5f9e4bce",
+ 0
+ ],
+ [
+ "000000000000218ff87fd50cfba2fd04203a78d2600cb2c4dcb039d803426e19",
+ 0
+ ],
+ [
+ "00000000007c96e6e3ed3146260348ac79ea7dc2ec2ae6bf8dc203400a37721d",
+ 0
+ ],
+ [
+ "000000005abaa10bf7260470c28ba32f1755b4cfd3734aad580681e39a9605a5",
+ 0
+ ],
+ [
+ "00000000005e77c226e6fffccafa56055e68f0ea0a30101e6a243ab9b3e07db0",
+ 0
+ ],
+ [
+ "0000000000e989fe27f85b89c1e852d7bc94b09033cc6c8b32fbbbd9383a9ae1",
+ 0
+ ],
+ [
+ "000000000091a1e962438583146293ef34156962445ffc5e81e4d0fe327d37ac",
+ 0
+ ],
+ [
+ "0000000000477978a6903217e2817d10e99bdfedb4f8bc396b96fd5b0b93b522",
+ 0
+ ],
+ [
+ "00000000000bfd9e5f13a9c03c48e8b58a937cf1ae2849160f1ca11f8fcced3c",
+ 0
+ ],
+ [
+ "00000000000158dd3c31b6379887b4353ef2898c03b7ce55458fcd57cb6f0639",
+ 0
+ ],
+ [
+ "00000000000029d7009eb56b9d38366005576b82a9b59fc845522a34ad36a38a",
+ 0
+ ],
+ [
+ "0000000000e6e207a82b8ad7136352204bb8e9ccfcd25885a715d3c65cbee997",
+ 0
+ ],
+ [
+ "0000000000fadc4429f50fc534ccac4db5e51a313df25034d6c5c25f7e83448c",
+ 0
+ ],
+ [
+ "000000000019c58defcfdab6c6ab9497685e61118effda4c2613bf44be19fcbd",
+ 0
+ ],
+ [
+ "000000000006cf444d846093c5045d42ddc0986ca805f261476d0fd2eb474c39",
+ 0
+ ],
+ [
+ "0000000000d0856a3d6a1e5b1ac7e388cc029bd8410b3b1489598974fe470568",
+ 0
+ ],
+ [
+ "00000000003d9aae63ed532b78082ca5386211e22410fd24ebd5318d1a4cd1da",
+ 0
+ ],
+ [
+ "00000000000345003879f86021a6d5e3fe93813246818c145947b7e225691177",
+ 0
+ ],
+ [
+ "00000000000175393730cde3e49de7af2b81ae736eee005a9f9c4a1e878c52ec",
+ 0
+ ],
+ [
+ "00000000000087a8c621c879aec2a897258632d6aa631b9a38ba4d564e08682a",
+ 0
+ ],
+ [
+ "0000000000002ea641b2975935bd9caf337b51ac9f9bb90a54f6ea6ee5d3112b",
+ 0
+ ],
+ [
+ "0000000000000c544f9b6a8cbab6d25caf949875622bf75139234850b10affe1",
+ 0
+ ],
+ [
+ "0000000000000f66fc4e37232a29f3389c493863a980d58a1d570eddd5268999",
+ 0
+ ],
+ [
+ "00000000001213fe2bbb8aacb1fc14983586e09db964151cb507956a81b35f25",
+ 0
+ ],
+ [
+ "0000000000ba82c2160602ddc1913bc4c133ad0af8848e014367c84110d00e05",
+ 0
+ ],
+ [
+ "0000000000b7a98b364b1cf9521275a915c7a1b3a0f0c052c7d8efb620ec0870",
+ 0
+ ],
+ [
+ "000000000047dc62db23540ab4aee43e54812aedb623a2a158aa3244fc784722",
+ 0
+ ],
+ [
+ "00000000005291002da10e53c3855882251a6e5a425b5e639ef9be3bd05767ca",
+ 0
+ ],
+ [
+ "00000000005ffbcbc0d9b380584bdc78050a6f0c3582b4c9c5103a150cbc71f5",
+ 0
+ ],
+ [
+ "00000000000a7a69cc06b0a68b27a8fa5d29727ec3b6db8d32d61cf7489b5ff3",
+ 0
+ ],
+ [
+ "000000000007212eb8c49758d98cefaa6098da2b877a6055be341f5f7c0ad301",
+ 0
+ ],
+ [
+ "000000000068d1099d8cf3f43f6d164f2925b1d52ede75640cc65ca020e1de1c",
+ 0
+ ],
+ [
+ "0000000008d5ddef4468a4414bd08184c2eba0ec536b85a743b1091828a6a884",
+ 0
+ ],
+ [
+ "000000000acae40db93b589783b0cde70b98552955cb3c12f08de1b417d9008d",
+ 0
+ ],
+ [
+ "000000000066a51eaa3a54036f338719da3d5779180c0bc3787b533410de90e5",
+ 0
+ ],
+ [
+ "00000000008b521677a6e897950aac69640e52efb01b7af10bba3820ecd09a89",
+ 0
+ ],
+ [
+ "00000000001823f0e399311cab0fcf57403e094feebf99b22030bafd2004da87",
+ 0
+ ],
+ [
+ "00000000000bf821c2abf5bcd00ca96439ddf5b0b593be5601145fda5338efdc",
+ 0
+ ],
+ [
+ "000000000003f4fd19b2af0141289177014ecc6dce6ea8fb50bab93d4a291095",
+ 0
+ ],
+ [
+ "00000000000011842d892a02e55ca594caddc9f3cea1979ddffefc070eda8498",
+ 0
+ ],
+ [
+ "000000000000208aa0259d20f51c0e7b8895e18a93aea79af9b3832e710ef134",
+ 0
+ ],
+ [
+ "00000000000007218f849e72dee1f7fb6fcf36f3b6745c6468187ed2ed13287f",
+ 0
+ ],
+ [
+ "00000000000f79f656cae641c2b74554c6ecd673c0c7550671c4c2af940661b3",
+ 0
+ ],
+ [
+ "0000000000199b4d178c05fd1c3154c9a4632eadc7bfc734c4522176c977ce8a",
+ 0
+ ],
+ [
+ "00000000085d0682d481635cb2e6de2e4d9884589455a86194f0b222f9acb3c6",
+ 0
+ ],
+ [
+ "00000000015972a5a6786a14b009bf582c4bbf7b9854591dd8d26f82b43ddaef",
+ 0
+ ],
+ [
+ "000000000064bf72b7bdbfcbe96dbbd0efcaf7aa94c0f92cb4e6662819468fe4",
+ 0
+ ],
+ [
+ "00000000003df36b7962bb4ad62266c462382eddc93f4bfeac464b95f7a89ee9",
+ 0
+ ],
+ [
+ "000000000006516d3a9f424eb61db5dfb85aeee29708b78c65d24827bd926263",
+ 0
+ ],
+ [
+ "000000000001c1709fe1b294712638db356e89155650f6fbecde79ec47a92af7",
+ 0
+ ],
+ [
+ "000000000000dfc23251344b593c16c28cd195abcb337519d7bc82175721a033",
+ 0
+ ],
+ [
+ "0000000000000aae2dd2bf0b8581d137fcfa3d9c4cadbe3ef3834d7cae4268c0",
+ 0
+ ],
+ [
+ "000000000000092a5baff3d9a5ae87689b2afe668e71bac3b342c7d383f0060f",
+ 0
+ ],
+ [
+ "00000000000fa906eeff7d2e126698d88b8cda01d32ea2c039c26984daaa17a3",
+ 0
+ ],
+ [
+ "00000000002d4315e5bdc2bcfdb245b914130764a50943a2b2e02ea3acf5c47b",
+ 0
+ ],
+ [
+ "0000000000fc2bc9bb83e04cbe922d64719295bfef6320027725402306bcf1a0",
+ 0
+ ],
+ [
+ "000000000142690e7c334b97612746d6db208e6153bdfa8479d86d1b575feacd",
+ 0
+ ],
+ [
+ "0000000000629a7820e8cdbbed18dcfe16c992152badc745ca73b9b34e53fb0d",
+ 0
+ ],
+ [
+ "000000000023c2e9dbf3fe03248e40f4ec3fb2dc81ac573d5a6a4f490c701877",
+ 0
+ ],
+ [
+ "000000000013658a43b6d1c4be95fa36e32d3edf80716de3a8f7e98858016adb",
+ 0
+ ],
+ [
+ "000000000007c847295d8c4b6da9d8a64b57c3a2307e64387bf8882b9d35d6de",
+ 0
+ ],
+ [
+ "0000000000032bf90b823332af80bd2ea18f411f081c7dca8f2fe79d9215526b",
+ 0
+ ],
+ [
+ "000000000000001bc0655da6f24c6952e811006897a0c6dd8b6bd94f178636c8",
+ 0
+ ],
+ [
+ "0000000000001e1d09b15393190cf686e25488db7fcbc2f1ebacc8165fe6e3a0",
+ 0
+ ],
+ [
+ "00000000000cc79ae066badb4157def4067057cefd705bf87f1d832845a7ab36",
+ 0
+ ],
+ [
+ "000000000014408398244b94b4eff6b54875802ede6df2d1d21915333a195719",
+ 0
+ ],
+ [
+ "0000000000114135a1bc757110c05162fa649b694db9569be117e34832c87257",
+ 0
+ ],
+ [
+ "00000000009b15fb2bcee1af904989ba0761e4cddc6b3ee214c0bb07dac6211f",
+ 0
+ ],
+ [
+ "000000000012be506dde2c54adf355bdb41a457b0abec436202a3be73f0b052c",
+ 0
+ ],
+ [
+ "00000000000963760ceb5fc65570650d494805e05c9d753f3ea6d44247ad3d08",
+ 0
+ ],
+ [
+ "00000000000bfec54977673f68b6fe5f088398e697d778fa7987f8bab6a70825",
+ 0
+ ],
+ [
+ "000000000000e7f428bb413c17032c0031af0d26133ba93f744a5a0c16cf7e1a",
+ 0
+ ],
+ [
+ "00000000000036bc80378323c6eaff8ab350b6d89955f602960cb7c93d2feb4c",
+ 0
+ ],
+ [
+ "00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a",
+ 0
+ ],
+ [
+ "00000000001ff8fd57798082ab5a7452ada211e1c3be38745155505601498829",
+ 0
+ ],
+ [
+ "000000000020f960b535eac585e5810ad64f158c1142f0eecd925c8058172933",
+ 0
+ ],
+ [
+ "0000000000067bd89409368d221507a160e5c45972eeb01efe210054fe8e7d85",
+ 0
+ ],
+ [
+ "00000000003521f2d5ea3232d4835ca6c6bae083ba90458f67d4cd765ce93b09",
+ 0
+ ],
+ [
+ "000000000005ab3ff3a0c484eff7b571fb78ce27d93f77a480074232e5ce0c1d",
+ 0
+ ],
+ [
+ "00000000001048c9eca7cc1cbb86946c04498052071f7e7c775bba565ada337c",
+ 0
+ ],
+ [
+ "00000000000154caacde41be616f924d7d478812148242fba85605eefec9ac61",
+ 0
+ ],
+ [
+ "000000000000c34f75bd6f338c0206a31a8d5021cc2ded51e88a6ef4fe686d10",
+ 0
+ ],
+ [
+ "0000000000001e0581d86c49a6ca14ba88639ef908abb09210b57989e06b1a1f",
+ 0
+ ],
+ [
+ "0000000000d0e6dc0bf830b50bde3e400e16ec4f772f92a55390e62d4aa73af3",
+ 0
+ ],
+ [
+ "00000000069c2501a2f32cc69af72a602ff674438ae04dd05516f72a71b9ab26",
+ 0
+ ],
+ [
+ "0000000000c926b38954550c9b8d363ff058c2eb135eebdb3e640cfa67df803d",
+ 0
+ ],
+ [
+ "000000000011e9ad9c18e9e2095c3662af5be1e918dff653758583aa45dc8197",
+ 0
+ ],
+ [
+ "0000000000f311624ff4dcdf07400d0d2fec8b16b14c1c16babc377a2d85ad21",
+ 0
+ ],
+ [
+ "00000000002e455cabfdc2a8955e8ddfe717b12efe5b80937b0c0ad6ac977fc5",
+ 0
+ ],
+ [
+ "00000000000fed8889a22339b340f599ac7908e790bfc3cfca9b78078a52d228",
+ 0
+ ],
+ [
+ "0000000000012ca4492956b3f859b00e5db14b54d422cd95c68c7150743db365",
+ 0
+ ],
+ [
+ "0000000000004c58e8f7bac59eb4a036764a4d8e0da51c0290858ab14fb72481",
+ 0
+ ],
+ [
+ "0000000000002f60bc99563ff5b4b800c176fe8bde95e8f968fd6b53d74c9cef",
+ 0
+ ],
+ [
+ "0000000000000bffd10a3fb0b5b86d8b2561f39d07f8a4c41dfa08e3e49b7db5",
+ 0
+ ],
+ [
+ "00000000000006a296be9cd8fd4e3145c146863adbe08b71831abb8a869d032c",
+ 0
+ ],
+ [
+ "0000000000000c557f496e82891039ff22e277bd604be6e2e8b95e519bee91f9",
+ 0
+ ],
+ [
+ "0000000000399b30d2111c4bf3051c1f7f2f35bba7ff290d92393341ae47df55",
+ 0
+ ],
+ [
+ "000000001f88733439e4e8d3c474504aed62037faa16f3845b4c671f69732e26",
+ 0
+ ],
+ [
+ "0000000018aa2f93d2ab76a7e2f1bf5b565b4a1b0ececb6ee46490984f6c0d4b",
+ 0
+ ],
+ [
+ "0000000005e22674fcf65ce7be896a0557205ab26d1f76d73a717f5f14a6d6ad",
+ 0
+ ],
+ [
+ "0000000000223d866b324c097973210f8fc715c9535908359d61d8e1ab2f0100",
+ 0
+ ],
+ [
+ "00000000002b321fd6452ab43849bd7a781953ec4485554e0fdc579f2a52c90a",
+ 0
+ ],
+ [
+ "0000000000173132748c51b5754b0341232325bd118455bf3c8d25164d3eb92a",
+ 0
+ ],
+ [
+ "00000000000143158cdea5fbb9453bbe1a7a900e6feba1e2193e4f5c106d9fba",
+ 0
+ ],
+ [
+ "0000000000014677751456af5630025b3d9921a4eafb4d36a06498f0c6a84c56",
+ 0
+ ],
+ [
+ "000000000000243976cf2d30ecd3cb1fd0b805fba4da92d2758f78e1c6f8ae92",
+ 0
+ ],
+ [
+ "0000000000001323db1ab3f247bcb1e92592004b43e4bed0966ed09f675cf269",
+ 0
+ ],
+ [
+ "000000000000017a410c22c4b6caf710f5ccf005d644caf276ea8626a538798d",
+ 0
+ ],
+ [
+ "0000000000170b2b1374e3a0dfdce2fbc5e302e1e0e9fb419dc057c9959902d1",
+ 0
+ ],
+ [
+ "000000000015b4fad4d929630487680cda2d3aada138c58cc08241ef6dd4ab09",
+ 0
+ ],
+ [
+ "00000000000abebab869f1620843d413a3d9e06dc7d9f5201a414d547ace1f99",
+ 0
+ ],
+ [
+ "00000000000b0bdaf05c2fe8b12ebd2372f49d8eabcfbccdadd68b5e5b7c9565",
+ 0
+ ],
+ [
+ "00000000000ca1af42ee1be2c8895d94f39dab5fcdbe0b4b4065f4be534e7294",
+ 0
+ ],
+ [
+ "000000000069d0cc8c0452bf86cff87db05232f801a162acab2d080d6e4e9ea9",
+ 0
+ ],
+ [
+ "000000000019c7f7685f5bdc3afbb5e978cb3f4f70fea7b2b410139741303b53",
+ 0
+ ],
+ [
+ "00000000000d3874ce21db78f4d1883ad9ae8b26c1d7c13f3d723ff85629d595",
+ 0
+ ],
+ [
+ "0000000000033f87c25275ff72b58630d8da90221f2c84bcbd77c8e615709f8b",
+ 0
+ ],
+ [
+ "000000000000dc72adaaae6483eb6737de7d21b3a24b2426330e80b078ceaed1",
+ 0
+ ],
+ [
+ "00000000000002fb1337228db02ac464565271f22f045c1b6ee5e449f057a829",
+ 0
+ ],
+ [
+ "00000000000001902376ff640d3088899af0819dbd15f602156a13ac2fc8e94e",
+ 0
+ ],
+ [
+ "000000000000007ee49761a1c8284a3b8acefa39e37e455be4773d648e2db794",
+ 0
+ ],
+ [
+ "00000000000005b4d495a77f57018dbc72bf47993d494349329a3c653f04ab93",
+ 0
+ ],
+ [
+ "000000000000009dcb3ae6d68828e2f5ccfd58780abb260354e74484106f81ce",
+ 0
+ ],
+ [
+ "00000000a3ceb118021fb42d39be52db951c6f852bb9a241046e972706f7329a",
+ 0
+ ],
+ [
+ "00000000574e8e1c27fa54c77b4e7cd1b79de070f0d3ad5b383206ab9777d983",
+ 0
+ ],
+ [
+ "0000000039d562f640c1743421d53e7e04c3e8ba222c339fff6f3d25b1d4a7fe",
+ 0
+ ],
+ [
+ "000000000001cb1559d55c697871e18d5c26800f77fb11587241bfbec3b15e26",
+ 0
+ ],
+ [
+ "000000000006e01a93090319756c7ca826ef655feb0cc2ef9abcc59d67de5e5b",
+ 0
+ ],
+ [
+ "000000000000a81aaf5a4c013032638a077af6aad8bc449d74daef8ad3a74419",
+ 0
+ ],
+ [
+ "00000000000087d0574963c1582f2161298e2de5e48f74566291ef9afc2be24a",
+ 0
+ ],
+ [
+ "0000000000033251e71c347cd663945fb68efe82a8c6666c0b41e93f1c46658d",
+ 0
+ ],
+ [
+ "000000000000f592857e6f0e4711b5b93fdf95f2b21a5963bde15be750a07908",
+ 0
+ ],
+ [
+ "0000000000004353c8426e18b942a5012934ddac8322b86d6ab98ed7c0ee86ed",
+ 0
+ ],
+ [
+ "00000000004f027845b699f42e7d0d30c530e99524c5f97186ce6a250a5fac42",
+ 0
+ ],
+ [
+ "000000002fc6407edc060df90785082834867331e6746a43ed34a26fbdc5df64",
+ 0
+ ],
+ [
+ "0000000000048733007c91ea3665bd4e1653b10799e3f43abee0fe830ffbb3ad",
+ 0
+ ],
+ [
+ "0000000000025a9b1c5afceba0c78c4b0320797acdc1ad50b4e040f148fbff7f",
+ 0
+ ],
+ [
+ "00000000007ca6d026d27387edc1c5570de41c61bacbcb1dad2c0f300b49e637",
+ 0
+ ],
+ [
+ "00000000000258f683a77ad509da82a4fab24188fdb4b4690e212c50794a9abb",
+ 0
+ ],
+ [
+ "0000000000015111bce7b6ac13c930484e14e31e13e43355cb4d63c8f1782440",
+ 0
+ ],
+ [
+ "000000000001ca074fdecac7749d95f28f10c83a7e13787fd865bfbe505382bc",
+ 0
+ ],
+ [
+ "0000000000001c11a6505dd44ab405fdc07ddfc015f3c1166a5d9352ab58b52c",
+ 0
+ ],
+ [
+ "0000000000000c83f7f8e1cab4efa08d6c68c4555fb6ab542e01b87edd8f56ac",
+ 0
+ ],
+ [
+ "00000000000009561d0ceba15388573d2a994aff24512ec3ed7d7881aa0997dd",
+ 0
+ ],
+ [
+ "00000000007dc7cfbbb94db1fbc076a70a1252fd595686b4d75b2ea77ed6ee9e",
+ 0
+ ],
+ [
+ "00000000000251feb68a8c90852f73aeb29ebda191038737b7edd37c9475f4ac",
+ 0
+ ],
+ [
+ "0000000000013f9a97045ea9047654e514951288911b2c3986787c27bab49106",
+ 0
+ ],
+ [
+ "0000000006e8c37735c61f22bec69f4cb7eba03172349e7012b7704652f3e83a",
+ 0
+ ],
+ [
+ "0000000001f341add5657043d8e50e53ba079fe24966a2668f904be5579c84b9",
+ 0
+ ],
+ [
+ "000000000029a6275cd477d77939424bd183c2f1308a9912f45aa7cc9ed13b56",
+ 0
+ ],
+ [
+ "00000000000a0336239e5e1faedf5bd2eedf38c9a5ba34a832356aea70aeb102",
+ 0
+ ],
+ [
+ "000000000003c1a2b25093a64eb624055f6a3a26e18b8e7ea2d9382ec7a3609a",
+ 0
+ ],
+ [
+ "000000000001bd89bf7e8740ce22adfa6e8793bd1716a647e558ed1742ee8329",
+ 0
+ ],
+ [
+ "0000000000001320421f1bb2c94000e11a621f581fc277c0e2911c3b89f680bd",
+ 0
+ ],
+ [
+ "000000000054ce90a949f5ae2d43c4ace599668c6ccbc50620f6d5705922ea7c",
+ 0
+ ],
+ [
+ "00000000200d16fea4857e6b73169cc593421a57971acdbcaf87a31d7d8d72c8",
+ 0
+ ],
+ [
+ "0000000000e75602181c88f713b91c49de291ed878be305d25b75c0ec5fbe942",
+ 0
+ ],
+ [
+ "000000000081f8169c3c3665f20351dc0fe499612ae232ec0b55858a8e5dc6e9",
+ 0
+ ],
+ [
+ "0000000000d7ad232e7593fb435d125343b8113bbdb3705ab58ac0e18c26cc79",
+ 0
+ ],
+ [
+ "0000000000076df615d887e33193ca2dc0f2fc0e70744512c95da6242e9b1a81",
+ 0
+ ],
+ [
+ "0000000000084a62093d1929843e74456686429b698a7ea9b1901c1565779f58",
+ 0
+ ],
+ [
+ "00000000000251d1da01e9de9fcaf3ca3a64bff78a5faf51a8e697dfab6b5e4b",
+ 0
+ ],
+ [
+ "000000000000609a8798996b1f1fe0b66060a628eadc380d0d369a2318c2d0ec",
+ 0
+ ],
+ [
+ "00000000000014770aeab044a022e86d888a6ede75b6474022c71aead3a1db74",
+ 0
+ ],
+ [
+ "00000000000004101d04ebc90ade5d4b911aa13c038ecf25e9887d877203ddb8",
+ 0
+ ],
+ [
+ "000000007c700410b61eb7ff1aaccbfc3a79e4e4484ad7a2b0eda4d91dc4b613",
+ 0
+ ],
+ [
+ "00000000055ff438a031413ee042fd3c0a2b69be98690542806ff123b7988024",
+ 0
+ ],
+ [
+ "000000002eca5f9f2c3b656d2550662fdee4c95da133eade51a5cae653bc69fe",
+ 0
+ ],
+ [
+ "000000000c679b76ccf0c5b943095fdee8fa466311edbea2c4a05f9430ffef3f",
+ 0
+ ],
+ [
+ "00000000007c6f494e32d5d9de58fa008a770fdc0a7b4a141be5b7c2de3ab970",
+ 0
+ ],
+ [
+ "0000000000d5dcd5a26c8ad29c1293e70401e2f90d8288469df3816b8cc6d4aa",
+ 0
+ ],
+ [
+ "00000000000d754d94f36cacbfb620710672afb1558499cabe17ca62c54a7d3a",
+ 0
+ ],
+ [
+ "000000000004096bb78fba714b130f7f1f929e2803c75a7a85619f7a2b86567f",
+ 0
+ ],
+ [
+ "0000000000020e686c38d44c35896df35f9f1b7723a82a826a5e2393c25ef68c",
+ 0
+ ],
+ [
+ "000000000000504f9af6885c0cb6484109ea205a956c8efae9557a1f5b9233da",
+ 0
+ ],
+ [
+ "0000000000000e8746e52e4320ec17e66434a3936a3825f7046fe874e92275fb",
+ 0
+ ],
+ [
+ "0000000000000f48d818a9a026270c9f733f629959bea25192596d59874b1ce2",
+ 0
+ ],
+ [
+ "00000000eaa9214cb05b241828a1cfb0c4209fb7ea64429815d61f7c1d98939e",
+ 0
+ ],
+ [
+ "000000001f7f915a6002cce4edd5cba392307f3a199a520ee8937327a9135162",
+ 0
+ ],
+ [
+ "0000000009674ee0c606d687bdcddf8e023462927e2902b3381bc4bb862a7397",
+ 0
+ ],
+ [
+ "0000000001f3f3528c083a4b11eb2f04d8bbeca92b57f05d8282909bde78bc77",
+ 0
+ ],
+ [
+ "000000000131917ac459aefb91774dbb42caeca497afc0cfd1766e0338cc7f88",
+ 0
+ ],
+ [
+ "000000000027634444081e1289354cb50034a506bb306a2ac1d8280683771c5c",
+ 0
+ ],
+ [
+ "000000000017a852acff78fbee573329d45bb8b121e9f6fc1e4f687bb3778ada",
+ 0
+ ],
+ [
+ "000000000006789e1a00eca982fb2827f680b254c4a0ecb005af4464f3585a02",
+ 0
+ ],
+ [
+ "0000000000015d2e9f54b1e9419d6b32ce68ae626cdd7f2a1954f22ca39ae0fa",
+ 0
+ ],
+ [
+ "0000000000002f7893bc169165ed9fefb434b6201103f23cc84a747a68ff8797",
+ 0
+ ],
+ [
+ "00000000000008471ccf356a18dd48aa12506ef0b6162cb8f98a8d8bb0465902",
+ 0
+ ],
+ [
+ "0000000000000596f00b9db53c4111bcde16f3781471c5307af1a996e34ec20a",
+ 0
+ ],
+ [
+ "000000000000007b5d2406f64f5f5833c063a6906552e815e603140c00bca951",
+ 0
+ ],
+ [
+ "0000000093ca5d935740a1b25f10ce092fd777c2bb521f3156619389ae78931e",
+ 0
+ ],
+ [
+ "00000000292f3a48559527341f72400a0f8a783aebcaae5bfa0e390dfaa5286b",
+ 0
+ ],
+ [
+ "000000001e852ed7ddf0108d1fce0f4f686f43c8c1b85bcb12c43e564dc7630e",
+ 0
+ ],
+ [
+ "000000000c4bea8fb1e7f3a1f3e6c6b3f71388c0ec7eef3de381853767e89f87",
+ 0
+ ],
+ [
+ "00000000029ef31a21711b55c4300efa38ace0b706091e373f48285286f2c578",
+ 0
+ ],
+ [
+ "0000000000979060786bb008f193d3917e28667bb1b28329f3adadc172e4cce7",
+ 0
+ ],
+ [
+ "000000000019030ceb98013b1627517b45b04ee055ef445813bbebaa25fa1ed3",
+ 0
+ ],
+ [
+ "00000000000adf202247bb794fc9a3c82cd8767143f1e6ed5f60940ee18b09a8",
+ 0
+ ],
+ [
+ "000000000000b19061e2481d8be6183b3d881b0d58601072d2a32729435f6af3",
+ 0
+ ],
+ [
+ "0000000000007a6d34f59b29e8d4da53e51e3414acd18527466d064945fe19fc",
+ 0
+ ],
+ [
+ "0000000000002e66ca213a2c3e9eb5fa62de29feb83880a0bd29f90fca8ad199",
+ 0
+ ],
+ [
+ "0000000000000b4ca10aa100728d0928f37db5296303db1b74ffe29e4a17b6cd",
+ 0
+ ],
+ [
+ "0000000000000143309f6b19567955743775f61f8dc6932c0b46cf5fb11c6c72",
+ 0
+ ],
+ [
+ "00000000000000b04d5409b3ac60cc18c0b9a3d58b303594635a8f75a9d2abd5",
+ 0
+ ],
+ [
+ "000000000000040a2699f62a552703a278608248c2ce823f4cd8845376e9a371",
+ 0
+ ],
+ [
+ "00000000000005cfcb850db7e83d4963994f958bae9b1de1483f5aeb3d449925",
+ 0
+ ],
+ [
+ "00000000000190f80220e70c1481153671a7c90fd856988c183ab0e3d9313df8",
+ 0
+ ],
+ [
+ "000000009374563a06178641d06776f66554c2a094b5319f0801fe35cef72ccf",
+ 0
+ ],
+ [
+ "00000000003e4e6e5e8e4a89e7de50eed104d4a49d2992ff101b6740beec7cb5",
+ 0
+ ],
+ [
+ "0000000000618cd377d14aaa441cbdb92527894f98da316eca81664f8ab5488d",
+ 0
+ ],
+ [
+ "00000000000d977ab2897885fee712f58612fce8c10ffbe9400326fe3429b77b",
+ 0
+ ],
+ [
+ "00000000000c3575b487dd0f938c5bc744fa65ca4ca3a9c981b8bda903ec110b",
+ 0
+ ],
+ [
+ "0000000000247ac689595ed8d62678bfe53e5af13c0f5455e558f5e6bb375c16",
+ 0
+ ],
+ [
+ "0000000000093d175376aa621176511f335a48f824b66d998e8082f85134a48b",
+ 0
+ ],
+ [
+ "000000000000c0c0448fe922f2c737946297d35f2c25ad7cc223e11bbe58e1f8",
+ 0
+ ],
+ [
+ "00000000000016abe4e7c10ddb658bb089b2ef3b1de3f3329097cf679eedf2b5",
+ 0
+ ],
+ [
+ "000000000000242757cea5b68c52b83dd8c2eb9257492074fc69dfa30bd4cbf4",
+ 0
+ ],
+ [
+ "00000000000006813f3dd7726a509fbe3101835db155dfd35d44aeae6aedb316",
+ 0
+ ],
+ [
+ "000000000000053cc4f39cff1c8cee1aff7e289a85dee84164d2d981afc8f17a",
+ 0
+ ],
+ [
+ "00000000000000789724805cf1d37ef689acf52c47a460507f540d5e5ca79bfa",
+ 0
+ ],
+ [
+ "00000000000003d71618bb8952887f65540270a5e54d6246b9419e08831b5e4e",
+ 0
+ ],
+ [
+ "0000000000000251a513a33eadfad67c015f6e3b291dfd0ae1cc4bb3a43006dc",
+ 0
+ ],
+ [
+ "00000000968009e3f8d6e6071e7def68298307717a9af6c2d44986deaae297d5",
+ 0
+ ],
+ [
+ "0000000062bcacb734df83bbfa3e1b9a8dfa570ecffb6c29eaaf8e9498cccd30",
+ 0
+ ],
+ [
+ "000000001d4618c0931bd3c25ee592c35341f30ff3b549a671f637b3c26ef414",
+ 0
+ ],
+ [
+ "000000000418b329df96a004f1b652ad06a7ca295f9f2e711c412d00493f5a86",
+ 0
+ ],
+ [
+ "000000000302bfb88e9027237d023c4b969e106c9a7a23a84103776de7880836",
+ 0
+ ],
+ [
+ "000000000069b9f7d9134fd93c8b7e3af8b26bbcbb5553af02fb6ed644d7fca5",
+ 0
+ ],
+ [
+ "00000000000411ec444240ee91e2777ad18b80dee854e3e838e32209e84774fa",
+ 0
+ ],
+ [
+ "0000000000007c73f322eba4dee5463305227c7e1a8139f1b7b296444f265052",
+ 0
+ ],
+ [
+ "00000000000129adf0f9c0242aedbb9d87935d67ee4ddea758c00344d4b6a29e",
+ 0
+ ],
+ [
+ "000000000000343594e671158b6e1b4b6499f6ad66e2aeabf1f6d295d3dba850",
+ 0
+ ],
+ [
+ "000000000000320f0d5c22ba22b588b97a0e02273034bcd53669b1c8c4eeda1b",
+ 0
+ ],
+ [
+ "0000000000001e8cdb2d98471a5c60bdbddbe644b9ad08e17a97b3a7dce1e332",
+ 0
+ ],
+ [
+ "0000000000000026c9994ccdd027e86f51a2e36812f754bd855a7f9b1ca56511",
+ 0
+ ],
+ [
+ "00000000000002746a820a2c08b35b8d0493c4b5d468fcc971b9c88003e84849",
+ 0
+ ],
+ [
+ "000000000002949f844e92645df73ce9c093e5aac0d962a0fa13eb076eec835c",
+ 0
+ ],
+ [
+ "00000000000156fbda67468ae2863993b98a41227c420246e4bc4e68c84df0e8",
+ 0
+ ],
+ [
+ "000000000003b43c6c807122c8dd10e2a0cffbf72946f41c97c1ab82d416f74d",
+ 0
+ ],
+ [
+ "000000000004e0635c2438b1b649007e5d424b3de846299a8db53049ebf4da0c",
+ 0
+ ],
+ [
+ "00000000000258e4b79e3cca2ab7d12b35ba77fc491572f2e794f0a10f5236d9",
+ 0
+ ],
+ [
+ "0000000000f5816875d9fece105e499b0467b8fb23ea973c48d828a235acdebd",
+ 0
+ ],
+ [
+ "000000000001353bbaec810af7a4c74b4964ae072361c0889ed6d59cf16db6fd",
+ 0
+ ],
+ [
+ "00000000000b354d8c389473670ca6bed7e3dffa069f270d35ec9dad810af141",
+ 0
+ ],
+ [
+ "000000000002fa1f39e7cd8730fa08085ba2b532146ad1ef3b400a13e835ca36",
+ 0
+ ],
+ [
+ "000000000000d2c7943eee59652a9783bff27e474a92ec206c5c6e3cdd58d0d7",
+ 0
+ ],
+ [
+ "00000000000036034181b4d9a84a97490b49fbee4262b9cfb25a7bfc9c0eec9f",
+ 0
+ ],
+ [
+ "00000000000007deb59381cce692f152fc902732d96a7e7d463bc83915b37c0a",
+ 0
+ ],
+ [
+ "00000000ea7d32833462c0f72ade0cae4766e6065caa4e510331929c56d16632",
+ 0
+ ],
+ [
+ "000000000068fce0ddd370d4c8f9129a7bc7843e75fc57666202d3b90239e269",
+ 0
+ ],
+ [
+ "0000000026b4a2212c9c9493f8bd9d5331cab6d8eda8ee017410e58a783ca069",
+ 0
+ ],
+ [
+ "0000000009535ea2dc7e83c31cd17f1db1bb78b0a678fc0610844273de143bf5",
+ 0
+ ],
+ [
+ "00000000008607cbd5baca91d5b8b82ee965aace335744a3e21578af22bee8ba",
+ 0
+ ],
+ [
+ "000000000030dcedae0f5e98c4e176f9569ce76c4d4135bb028fc3144ef381d9",
+ 0
+ ],
+ [
+ "0000000000297c3f0e3fa85731222ba934a955bf513247a72a33c74c498cadbe",
+ 0
+ ],
+ [
+ "0000000000020a0d4a1e8120cbdb486e758b58919c9df12e0edc8ca1f2795e94",
+ 0
+ ],
+ [
+ "000000000000078773afc9023182bfb6534a60158672e6bc6e8aa5052854da80",
+ 0
+ ],
+ [
+ "00000000000102ecdd67800807d9e137357805b9bbf8a439ed86bde5b19fbeb7",
+ 0
+ ],
+ [
+ "0000000000005c3d2e3c7ee737c67ab465533acb233e0df902c1525fc11c3a55",
+ 0
+ ],
+ [
+ "0000000000001a77771650cdbbceff87caa4461391ba6a4ddc9815b5b0ab47b0",
+ 0
+ ],
+ [
+ "000000000000071ec390bbd28fa2a84e52ab5b32901d0723d22646b04ae01dc3",
+ 0
+ ],
+ [
+ "00000000000005c3ec3194f710c6f26ee736d59cc935ddfa574440f39846433a",
+ 0
+ ],
+ [
+ "00000000000001cc3df6924591939269d61ead563b9eb68402a2ca01d7ff99e2",
+ 0
+ ],
+ [
+ "000000008c778b3554ceaf3a13a856acbfe46b5750fb86fd92ba30651c2852f4",
+ 0
+ ],
+ [
+ "00000000107ca31f75f8ea76073dda3c33330b2706c1ec20c3ec240e853b65c5",
+ 0
+ ],
+ [
+ "0000000006ba99b08e7f2869ce113e2ad7464891de7b4cfa96f330d706a2da46",
+ 0
+ ],
+ [
+ "000000000f31036bd51b2818f6dfb90ada9be5019abf55fb15694b181e269865",
+ 0
+ ],
+ [
+ "00000000004fcc101bc47eb7a379b9f608d5c00ac04d2d0ea165ae2937070796",
+ 0
+ ],
+ [
+ "000000000044d5ca3eda838edef0df7e69e1934047f8482822ce58ff7a18466d",
+ 0
+ ],
+ [
+ "000000000029bdfb157be6d400c4dd3370d98afdd8cd3db6f1ada8c19bbf4650",
+ 0
+ ],
+ [
+ "000000000005e9699ad8035caa4f73af781ac2040c87b8aa77459b3607209aa8",
+ 0
+ ],
+ [
+ "000000000001c0ba033f7d85beeaa167c9bde0e192240653a7ff6d9b81679842",
+ 0
+ ],
+ [
+ "0000000000000e0176111f29e800b49c7b8c7226dbbf4df715f1a4f06bcaaa49",
+ 0
+ ],
+ [
+ "00000000ac3bb2cf42192e9053f5384355228a2b3d70b4ece4d45773a5d5ddd2",
+ 0
+ ],
+ [
+ "000000000f29f7b60842b1044b2db7998e9bcbd92f8ec6fe8d159c6d582f1f1a",
+ 0
+ ],
+ [
+ "00000000352f86bc5f9760961a25de009940508bb2cd0b37f378fbc87dc97eef",
+ 0
+ ],
+ [
+ "000000000e9b3086008679ed57f59857f64c3954368ba1088117dbf88d5839cd",
+ 0
+ ],
+ [
+ "000000000015324bd8fed0e61b62bd1d6c663b862cb98ea03c494a92e4a8d0af",
+ 0
+ ],
+ [
+ "000000000020475a181b7a084b341860a72fc0c1fdfcc13a85adeb0471444b0f",
+ 0
+ ],
+ [
+ "0000000000031905c508a975707b74f24e733880382775ee0e6250666473e1d8",
+ 0
+ ],
+ [
+ "000000000000ca38b15d2ea33a6eef505a9c661540a18882f79ba9a3c575a9bd",
+ 0
+ ],
+ [
+ "000000000002739979a7a89fa279303b6606885e750b19e91ed637d7f222b392",
+ 0
+ ],
+ [
+ "00000000000091e935fc266facc2c92759d5468a39aee5be6b76b519a9bc7567",
+ 0
+ ],
+ [
+ "00000000000006e339938254208203b67c3c400f703fc29535fc646699e36e58",
+ 0
+ ],
+ [
+ "00000000000008f6f1d1150d77f93a7f1baa24b65ceb471b1825b2e92ca6997c",
+ 0
+ ],
+ [
+ "000000000000004894e1edcc5421dbcec77d47c5c50bf27b2cff3f1c242c9eb3",
+ 0
+ ],
+ [
+ "000000000000054e97fb5e1a8bd7900f7c329385895761aaa40d11b3c75b0c8e",
+ 0
+ ],
+ [
+ "0000000000000600f4bcc5a89527eede43d1d3342dc12eee1371ab534b0102dc",
+ 0
+ ],
+ [
+ "00000000d1ad5c3ef8c3bb4610b34c264e4ca1ea51c4c8bac18b215e7dc96948",
+ 0
+ ],
+ [
+ "0000000062f6a07ae11f9724b8ba9dc2b7348ffd02b59edd3cd2bf387fab9723",
+ 0
+ ],
+ [
+ "000000000014e4c97c9b09ff20203213f3336b0927fd19d214cef1f544756e39",
+ 0
+ ],
+ [
+ "0000000000d004681880e127aed3fa73255a2e75c2e5c8580cd555526614b294",
+ 0
+ ],
+ [
+ "000000000008093189bba28d40662d6964afc1c0fc9b5c1681bbe32e8bee6c0b",
+ 0
+ ],
+ [
+ "00000000002df10cf8165b2204ef4db6721c8c2119d60463b040fbc81c266bbf",
+ 0
+ ],
+ [
+ "00000000000c28c789e7cd9800b98c1dd32e2dda54048116ff47ed856a14acfb",
+ 0
+ ],
+ [
+ "000000000003e8e7755d9b8299b28c71d9f0e18909f25bc9f3eeec3464ece1dd",
+ 0
+ ],
+ [
+ "0000000000004b95a0103abe2cb97806caca76f6922d9c5df003cf4a467df822",
+ 0
+ ],
+ [
+ "0000000000005f12d2ab72bfa715860444c281265ef77e09dc2d041ce89506c0",
+ 0
+ ],
+ [
+ "00000000000016eeedb3f367daaee93334188db877fb01cd0282b990f60812b3",
+ 0
+ ],
+ [
+ "00000000000001daf3bd8306b6f6899af8aa656d87ac2aa37d493fdcb0cb3000",
+ 0
+ ],
+ [
+ "0000000000000390b86892ad0bed9b520783056961cad7362ace8049aa00471c",
+ 0
+ ],
+ [
+ "00000000000002105d01b4de7d3e3ada9c757a239151d50b5dd193e3951a23cc",
+ 0
+ ],
+ [
+ "00000000000002362fa802df308201a4b1fff2fd8a91892915a46f5d54098ff4",
+ 0
+ ],
+ [
+ "00000000000004fb8aa6c6aecb64b9d8d7e691a6cd56fad69fc5278b9e8d98cb",
+ 0
+ ],
+ [
+ "00000000000000ce3bd9752b2508ddae1ee71332e905163a3c0d7e10b8c472f7",
+ 0
+ ],
+ [
+ "00000000000002d0d8520982f15a45d4a405334c61886b6d13d95843386af647",
+ 0
+ ],
+ [
+ "00000000cafd25502ad67d5d409edfc98f5bbd3173e86e085c69658d58da5f70",
+ 0
+ ],
+ [
+ "00000000b01e0675317a29a07731ea092fa029016a40ed8bb4fc17cde50eda05",
+ 0
+ ],
+ [
+ "000000002676805396ed2883ccc8ad401aa0a974627559fbae2416ba5c54999c",
+ 0
+ ],
+ [
+ "0000000000030ab759158f3d425824228dc5c91f32db91d404bee29ee3a41878",
+ 0
+ ],
+ [
+ "00000000000da1c8040ec08e7490fb201ca1fb3571f29c0efd3351ae197d3017",
+ 0
+ ],
+ [
+ "000000000004e3cba890c16ffc7d1c019d4ab88afa39315164e1b08b8e6a9330",
+ 0
+ ],
+ [
+ "00000000000bdcfb630b43977be44529e54daa02d199014a0967deac669bd060",
+ 0
+ ],
+ [
+ "000000000007254038f9c621d6df0d9fbd90b5697e4170cd6090daaf579f3790",
+ 0
+ ],
+ [
+ "000000000002263e27ea1cec943632bf469a28b067f0bfde3b9a6b48540981b4",
+ 0
+ ],
+ [
+ "000000000000f194a8d17e683d17f222d23a9032f034d4dc4497263fd785dfa0",
+ 0
+ ],
+ [
+ "00000000000036e359b7b07044e3cd5b132a3c72501a0f3f9ccde167f5316bba",
+ 0
+ ],
+ [
+ "0000000000000b10e98a90e0fd1ffbf7d5fc5a76e8e6e960c6fb158711af6f48",
+ 0
+ ],
+ [
+ "0000000000000104e1e4303b8dae78389bb4e6c38f3eb3fe42aec6464bd5c397",
+ 0
+ ],
+ [
+ "00000000000000bde368a635921f5ad25aeb4b784651de24d624cf20c27691c7",
+ 0
+ ],
+ [
+ "0000000081a626a33cff134e7e56dc0f0a67b1735c96256774885d5d095807c0",
+ 0
+ ],
+ [
+ "0000000055d357cdf39130eb767f416101e79025515906bea528f43cb6446920",
+ 0
+ ],
+ [
+ "0000000012558b30f9c1a156fd80b02451e8dfcc7fe0350fb4adeeb84951a0a6",
+ 0
+ ],
+ [
+ "000000000001a4868924fc7cca0334ffc4dd49c07fb841c1da059a7c219bdf95",
+ 0
+ ],
+ [
+ "00000000000010086bd2bba88c71b08cfc7e24183d610a2803e6d382049d52c0",
+ 0
+ ],
+ [
+ "0000000000018c83992fe05d820b097228de93787e3f59e65cb89ad4c385e364",
+ 0
+ ],
+ [
+ "00000000000023ab80324770ff4c6802d09e5e1e7de78d2a8e64783904d47f19",
+ 0
+ ],
+ [
+ "000000000000287fa294ea557835d8c98bfe94c4d8b18d5b10f1b62d68957113",
+ 0
+ ]
+]
\ No newline at end of file
diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py
new file mode 100644
index 000000000..5a69cccfe
--- /dev/null
+++ b/electrum/coinchooser.py
@@ -0,0 +1,392 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 kyuupichan@gmail
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from collections import defaultdict, namedtuple
+from math import floor, log10
+
+from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
+from .transaction import Transaction, TxOutput
+from .util import NotEnoughFunds, PrintError
+
+
+# A simple deterministic PRNG. Used to deterministically shuffle a
+# set of coins - the same set of coins should produce the same output.
+# Although choosing UTXOs "randomly" we want it to be deterministic,
+# so if sending twice from the same UTXO set we choose the same UTXOs
+# to spend. This prevents attacks on users by malicious or stale
+# servers.
+class PRNG:
+ def __init__(self, seed):
+ self.sha = sha256(seed)
+ self.pool = bytearray()
+
+ def get_bytes(self, n):
+ while len(self.pool) < n:
+ self.pool.extend(self.sha)
+ self.sha = sha256(self.sha)
+ result, self.pool = self.pool[:n], self.pool[n:]
+ return result
+
+ def randint(self, start, end):
+ # Returns random integer in [start, end)
+ n = end - start
+ r = 0
+ p = 1
+ while p < n:
+ r = self.get_bytes(1)[0] + (r << 8)
+ p = p << 8
+ return start + (r % n)
+
+ def choice(self, seq):
+ return seq[self.randint(0, len(seq))]
+
+ def shuffle(self, x):
+ for i in reversed(range(1, len(x))):
+ # pick an element in x[:i+1] with which to exchange x[i]
+ j = self.randint(0, i+1)
+ x[i], x[j] = x[j], x[i]
+
+
+Bucket = namedtuple('Bucket',
+ ['desc',
+ 'weight', # as in BIP-141
+ 'value', # in satoshis
+ 'coins', # UTXOs
+ 'min_height', # min block height where a coin was confirmed
+ 'witness']) # whether any coin uses segwit
+
+def strip_unneeded(bkts, sufficient_funds):
+ '''Remove buckets that are unnecessary in achieving the spend amount'''
+ bkts = sorted(bkts, key = lambda bkt: bkt.value)
+ for i in range(len(bkts)):
+ if not sufficient_funds(bkts[i + 1:]):
+ return bkts[i:]
+ # Shouldn't get here
+ return bkts
+
+class CoinChooserBase(PrintError):
+
+ enable_output_value_rounding = False
+
+ def keys(self, coins):
+ raise NotImplementedError
+
+ def bucketize_coins(self, coins):
+ keys = self.keys(coins)
+ buckets = defaultdict(list)
+ for key, coin in zip(keys, coins):
+ buckets[key].append(coin)
+
+ def make_Bucket(desc, coins):
+ witness = any(Transaction.is_segwit_input(coin, guess_for_address=True) for coin in coins)
+ # note that we're guessing whether the tx uses segwit based
+ # on this single bucket
+ weight = sum(Transaction.estimated_input_weight(coin, witness)
+ for coin in coins)
+ value = sum(coin['value'] for coin in coins)
+ min_height = min(coin['height'] for coin in coins)
+ return Bucket(desc, weight, value, coins, min_height, witness)
+
+ return list(map(make_Bucket, buckets.keys(), buckets.values()))
+
+ def penalty_func(self, tx):
+ def penalty(candidate):
+ return 0
+ return penalty
+
+ def change_amounts(self, tx, count, fee_estimator, dust_threshold):
+ # Break change up if bigger than max_change
+ output_amounts = [o.value for o in tx.outputs()]
+ # Don't split change of less than 0.02 BTC
+ max_change = max(max(output_amounts) * 1.25, 0.02 * COIN)
+
+ # Use N change outputs
+ for n in range(1, count + 1):
+ # How much is left if we add this many change outputs?
+ change_amount = max(0, tx.get_fee() - fee_estimator(n))
+ if change_amount // n <= max_change:
+ break
+
+ # Get a handle on the precision of the output amounts; round our
+ # change to look similar
+ def trailing_zeroes(val):
+ s = str(val)
+ return len(s) - len(s.rstrip('0'))
+
+ zeroes = [trailing_zeroes(i) for i in output_amounts]
+ min_zeroes = min(zeroes)
+ max_zeroes = max(zeroes)
+
+ if n > 1:
+ zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1)
+ else:
+ # if there is only one change output, this will ensure that we aim
+ # to have one that is exactly as precise as the most precise output
+ zeroes = [min_zeroes]
+
+ # Calculate change; randomize it a bit if using more than 1 output
+ remaining = change_amount
+ amounts = []
+ while n > 1:
+ average = remaining / n
+ amount = self.p.randint(int(average * 0.7), int(average * 1.3))
+ precision = min(self.p.choice(zeroes), int(floor(log10(amount))))
+ amount = int(round(amount, -precision))
+ amounts.append(amount)
+ remaining -= amount
+ n -= 1
+
+ # Last change output. Round down to maximum precision but lose
+ # no more than 10**max_dp_to_round_for_privacy
+ # e.g. a max of 2 decimal places means losing 100 satoshis to fees
+ max_dp_to_round_for_privacy = 2 if self.enable_output_value_rounding else 0
+ N = pow(10, min(max_dp_to_round_for_privacy, zeroes[0]))
+ amount = (remaining // N) * N
+ amounts.append(amount)
+
+ assert sum(amounts) <= change_amount
+
+ return amounts
+
+ def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):
+ amounts = self.change_amounts(tx, len(change_addrs), fee_estimator,
+ dust_threshold)
+ assert min(amounts) >= 0
+ assert len(change_addrs) >= len(amounts)
+ # If change is above dust threshold after accounting for the
+ # size of the change output, add it to the transaction.
+ dust = sum(amount for amount in amounts if amount < dust_threshold)
+ amounts = [amount for amount in amounts if amount >= dust_threshold]
+ change = [TxOutput(TYPE_ADDRESS, addr, amount)
+ for addr, amount in zip(change_addrs, amounts)]
+ self.print_error('change:', change)
+ if dust:
+ self.print_error('not keeping dust', dust)
+ return change
+
+ def make_tx(self, coins, outputs, change_addrs, fee_estimator,
+ dust_threshold):
+ """Select unspent coins to spend to pay outputs. If the change is
+ greater than dust_threshold (after adding the change output to
+ the transaction) it is kept, otherwise none is sent and it is
+ added to the transaction fee.
+
+ Note: fee_estimator expects virtual bytes
+ """
+
+ # Deterministic randomness from coins
+ utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins]
+ self.p = PRNG(''.join(sorted(utxos)))
+
+ # Copy the outputs so when adding change we don't modify "outputs"
+ tx = Transaction.from_io([], outputs[:])
+ # Weight of the transaction with no inputs and no change
+ # Note: this will use legacy tx serialization as the need for "segwit"
+ # would be detected from inputs. The only side effect should be that the
+ # marker and flag are excluded, which is compensated in get_tx_weight()
+ base_weight = tx.estimated_weight()
+ spent_amount = tx.output_value()
+
+ def fee_estimator_w(weight):
+ return fee_estimator(Transaction.virtual_size_from_weight(weight))
+
+ def get_tx_weight(buckets):
+ total_weight = base_weight + sum(bucket.weight for bucket in buckets)
+ is_segwit_tx = any(bucket.witness for bucket in buckets)
+ if is_segwit_tx:
+ total_weight += 2 # marker and flag
+ # non-segwit inputs were previously assumed to have
+ # a witness of '' instead of '00' (hex)
+ # note that mixed legacy/segwit buckets are already ok
+ num_legacy_inputs = sum((not bucket.witness) * len(bucket.coins)
+ for bucket in buckets)
+ total_weight += num_legacy_inputs
+
+ return total_weight
+
+ def sufficient_funds(buckets):
+ '''Given a list of buckets, return True if it has enough
+ value to pay for the transaction'''
+ total_input = sum(bucket.value for bucket in buckets)
+ total_weight = get_tx_weight(buckets)
+ return total_input >= spent_amount + fee_estimator_w(total_weight)
+
+ # Collect the coins into buckets, choose a subset of the buckets
+ buckets = self.bucketize_coins(coins)
+ buckets = self.choose_buckets(buckets, sufficient_funds,
+ self.penalty_func(tx))
+
+ tx.add_inputs([coin for b in buckets for coin in b.coins])
+ tx_weight = get_tx_weight(buckets)
+
+ # change is sent back to sending address unless specified
+ if not change_addrs:
+ change_addrs = [tx.inputs()[0]['address']]
+ # note: this is not necessarily the final "first input address"
+ # because the inputs had not been sorted at this point
+ assert is_address(change_addrs[0])
+
+ # This takes a count of change outputs and returns a tx fee
+ output_weight = 4 * Transaction.estimated_output_size(change_addrs[0])
+ fee = lambda count: fee_estimator_w(tx_weight + count * output_weight)
+ change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
+ tx.add_outputs(change)
+
+ self.print_error("using %d inputs" % len(tx.inputs()))
+ self.print_error("using buckets:", [bucket.desc for bucket in buckets])
+
+ return tx
+
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
+ raise NotImplemented('To be subclassed')
+
+
+class CoinChooserRandom(CoinChooserBase):
+
+ def bucket_candidates_any(self, buckets, sufficient_funds):
+ '''Returns a list of bucket sets.'''
+ if not buckets:
+ raise NotEnoughFunds()
+
+ candidates = set()
+
+ # Add all singletons
+ for n, bucket in enumerate(buckets):
+ if sufficient_funds([bucket]):
+ candidates.add((n, ))
+
+ # And now some random ones
+ attempts = min(100, (len(buckets) - 1) * 10 + 1)
+ permutation = list(range(len(buckets)))
+ for i in range(attempts):
+ # Get a random permutation of the buckets, and
+ # incrementally combine buckets until sufficient
+ self.p.shuffle(permutation)
+ bkts = []
+ for count, index in enumerate(permutation):
+ bkts.append(buckets[index])
+ if sufficient_funds(bkts):
+ candidates.add(tuple(sorted(permutation[:count + 1])))
+ break
+ else:
+ # FIXME this assumes that the effective value of any bkt is >= 0
+ # we should make sure not to choose buckets with <= 0 eff. val.
+ raise NotEnoughFunds()
+
+ candidates = [[buckets[n] for n in c] for c in candidates]
+ return [strip_unneeded(c, sufficient_funds) for c in candidates]
+
+ def bucket_candidates_prefer_confirmed(self, buckets, sufficient_funds):
+ """Returns a list of bucket sets preferring confirmed coins.
+
+ Any bucket can be:
+ 1. "confirmed" if it only contains confirmed coins; else
+ 2. "unconfirmed" if it does not contain coins with unconfirmed parents
+ 3. other: e.g. "unconfirmed parent" or "local"
+
+ This method tries to only use buckets of type 1, and if the coins there
+ are not enough, tries to use the next type but while also selecting
+ all buckets of all previous types.
+ """
+ conf_buckets = [bkt for bkt in buckets if bkt.min_height > 0]
+ unconf_buckets = [bkt for bkt in buckets if bkt.min_height == 0]
+ other_buckets = [bkt for bkt in buckets if bkt.min_height < 0]
+
+ bucket_sets = [conf_buckets, unconf_buckets, other_buckets]
+ already_selected_buckets = []
+
+ for bkts_choose_from in bucket_sets:
+ try:
+ def sfunds(bkts):
+ return sufficient_funds(already_selected_buckets + bkts)
+
+ candidates = self.bucket_candidates_any(bkts_choose_from, sfunds)
+ break
+ except NotEnoughFunds:
+ already_selected_buckets += bkts_choose_from
+ else:
+ raise NotEnoughFunds()
+
+ candidates = [(already_selected_buckets + c) for c in candidates]
+ return [strip_unneeded(c, sufficient_funds) for c in candidates]
+
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
+ candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds)
+ penalties = [penalty_func(cand) for cand in candidates]
+ winner = candidates[penalties.index(min(penalties))]
+ self.print_error("Bucket sets:", len(buckets))
+ self.print_error("Winning penalty:", min(penalties))
+ return winner
+
+class CoinChooserPrivacy(CoinChooserRandom):
+ """Attempts to better preserve user privacy.
+ First, if any coin is spent from a user address, all coins are.
+ Compared to spending from other addresses to make up an amount, this reduces
+ information leakage about sender holdings. It also helps to
+ reduce blockchain UTXO bloat, and reduce future privacy loss that
+ would come from reusing that address' remaining UTXOs.
+ Second, it penalizes change that is quite different to the sent amount.
+ Third, it penalizes change that is too big.
+ """
+
+ def keys(self, coins):
+ return [coin['address'] for coin in coins]
+
+ def penalty_func(self, tx):
+ min_change = min(o.value for o in tx.outputs()) * 0.75
+ max_change = max(o.value for o in tx.outputs()) * 1.33
+ spent_amount = sum(o.value for o in tx.outputs())
+
+ def penalty(buckets):
+ badness = len(buckets) - 1
+ total_input = sum(bucket.value for bucket in buckets)
+ # FIXME "change" here also includes fees
+ change = float(total_input - spent_amount)
+ # Penalize change not roughly in output range
+ if change < min_change:
+ badness += (min_change - change) / (min_change + 10000)
+ elif change > max_change:
+ badness += (change - max_change) / (max_change + 10000)
+ # Penalize large change; 5 BTC excess ~= using 1 more input
+ badness += change / (COIN * 5)
+ return badness
+
+ return penalty
+
+
+COIN_CHOOSERS = {
+ 'Privacy': CoinChooserPrivacy,
+}
+
+def get_name(config):
+ kind = config.get('coin_chooser')
+ if not kind in COIN_CHOOSERS:
+ kind = 'Privacy'
+ return kind
+
+def get_coin_chooser(config):
+ klass = COIN_CHOOSERS[get_name(config)]
+ coinchooser = klass()
+ coinchooser.enable_output_value_rounding = config.get('coin_chooser_output_rounding', False)
+ return coinchooser
diff --git a/electrum/commands.py b/electrum/commands.py
new file mode 100644
index 000000000..89e7a619b
--- /dev/null
+++ b/electrum/commands.py
@@ -0,0 +1,895 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcore client
+# Copyright (C) 2011 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import sys
+import datetime
+import copy
+import argparse
+import json
+import ast
+import base64
+from functools import wraps
+from decimal import Decimal
+
+from .import util, ecc
+from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode
+from . import bitcoin
+from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
+from .i18n import _
+from .transaction import Transaction, multisig_script, TxOutput
+from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
+from .plugin import run_hook
+
+known_commands = {}
+
+
+def satoshis(amount):
+ # satoshi conversion must not be performed by the parser
+ return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
+
+
+class Command:
+ def __init__(self, func, s):
+ self.name = func.__name__
+ self.requires_network = 'n' in s
+ self.requires_wallet = 'w' in s
+ self.requires_password = 'p' in s
+ self.description = func.__doc__
+ self.help = self.description.split('.')[0] if self.description else None
+ varnames = func.__code__.co_varnames[1:func.__code__.co_argcount]
+ self.defaults = func.__defaults__
+ if self.defaults:
+ n = len(self.defaults)
+ self.params = list(varnames[:-n])
+ self.options = list(varnames[-n:])
+ else:
+ self.params = list(varnames)
+ self.options = []
+ self.defaults = []
+
+
+def command(s):
+ def decorator(func):
+ global known_commands
+ name = func.__name__
+ known_commands[name] = Command(func, s)
+ @wraps(func)
+ def func_wrapper(*args, **kwargs):
+ c = known_commands[func.__name__]
+ wallet = args[0].wallet
+ password = kwargs.get('password')
+ if c.requires_wallet and wallet is None:
+ raise Exception("wallet not loaded. Use 'electrum daemon load_wallet'")
+ if c.requires_password and password is None and wallet.has_password():
+ return {'error': 'Password required' }
+ return func(*args, **kwargs)
+ return func_wrapper
+ return decorator
+
+
+class Commands:
+
+ def __init__(self, config, wallet, network, callback = None):
+ self.config = config
+ self.wallet = wallet
+ self.network = network
+ self._callback = callback
+
+ def _run(self, method, args, password_getter):
+ # this wrapper is called from the python console
+ cmd = known_commands[method]
+ if cmd.requires_password and self.wallet.has_password():
+ password = password_getter()
+ if password is None:
+ return
+ else:
+ password = None
+
+ f = getattr(self, method)
+ if cmd.requires_password:
+ result = f(*args, **{'password':password})
+ else:
+ result = f(*args)
+
+ if self._callback:
+ self._callback()
+ return result
+
+ @command('')
+ def commands(self):
+ """List of commands"""
+ return ' '.join(sorted(known_commands.keys()))
+
+ @command('')
+ def create(self, segwit=False):
+ """Create a new wallet"""
+ raise Exception('Not a JSON-RPC command')
+
+ @command('wn')
+ def restore(self, text):
+ """Restore a wallet from text. Text can be a seed phrase, a master
+ public key, a master private key, a list of bitcore addresses
+ or bitcore private keys. If you want to be prompted for your
+ seed, type '?' or ':' (concealed) """
+ raise Exception('Not a JSON-RPC command')
+
+ @command('wp')
+ def password(self, password=None, new_password=None):
+ """Change wallet password. """
+ if self.wallet.storage.is_encrypted_with_hw_device() and new_password:
+ raise Exception("Can't change the password of a wallet encrypted with a hw device.")
+ b = self.wallet.storage.is_encrypted()
+ self.wallet.update_password(password, new_password, b)
+ self.wallet.storage.write()
+ return {'password':self.wallet.has_password()}
+
+ @command('')
+ def getconfig(self, key):
+ """Return a configuration variable. """
+ return self.config.get(key)
+
+ @classmethod
+ def _setconfig_normalize_value(cls, key, value):
+ if key not in ('rpcuser', 'rpcpassword'):
+ value = json_decode(value)
+ try:
+ value = ast.literal_eval(value)
+ except:
+ pass
+ return value
+
+ @command('')
+ def setconfig(self, key, value):
+ """Set a configuration variable. 'value' may be a string or a Python expression."""
+ value = self._setconfig_normalize_value(key, value)
+ self.config.set_key(key, value)
+ return True
+
+ @command('')
+ def make_seed(self, nbits=132, language=None, segwit=False):
+ """Create a seed"""
+ from .mnemonic import Mnemonic
+ t = 'segwit' if segwit else 'standard'
+ s = Mnemonic(language).make_seed(t, nbits)
+ return s
+
+ @command('n')
+ def getaddresshistory(self, address):
+ """Return the transaction history of any address. Note: This is a
+ walletless server query, results are not checked by SPV.
+ """
+ sh = bitcoin.address_to_scripthash(address)
+ return self.network.get_history_for_scripthash(sh)
+
+ @command('w')
+ def listunspent(self):
+ """List unspent outputs. Returns the list of unspent transaction
+ outputs in your wallet."""
+ l = copy.deepcopy(self.wallet.get_utxos())
+ for i in l:
+ v = i["value"]
+ i["value"] = str(Decimal(v)/COIN) if v is not None else None
+ return l
+
+ @command('n')
+ def getaddressunspent(self, address):
+ """Returns the UTXO list of any address. Note: This
+ is a walletless server query, results are not checked by SPV.
+ """
+ sh = bitcoin.address_to_scripthash(address)
+ return self.network.listunspent_for_scripthash(sh)
+
+ @command('')
+ def serialize(self, jsontx):
+ """Create a transaction from json inputs.
+ Inputs must have a redeemPubkey.
+ Outputs must be a list of {'address':address, 'value':satoshi_amount}.
+ """
+ keypairs = {}
+ inputs = jsontx.get('inputs')
+ outputs = jsontx.get('outputs')
+ locktime = jsontx.get('lockTime', 0)
+ for txin in inputs:
+ if txin.get('output'):
+ prevout_hash, prevout_n = txin['output'].split(':')
+ txin['prevout_n'] = int(prevout_n)
+ txin['prevout_hash'] = prevout_hash
+ sec = txin.get('privkey')
+ if sec:
+ txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
+ pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
+ keypairs[pubkey] = privkey, compressed
+ txin['type'] = txin_type
+ txin['x_pubkeys'] = [pubkey]
+ txin['signatures'] = [None]
+ txin['num_sig'] = 1
+
+ outputs = [TxOutput(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
+ tx = Transaction.from_io(inputs, outputs, locktime=locktime)
+ tx.sign(keypairs)
+ return tx.as_dict()
+
+ @command('wp')
+ def signtransaction(self, tx, privkey=None, password=None):
+ """Sign a transaction. The wallet keys will be used unless a private key is provided."""
+ tx = Transaction(tx)
+ if privkey:
+ txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)
+ pubkey_bytes = ecc.ECPrivkey(privkey2).get_public_key_bytes(compressed=compressed)
+ h160 = bitcoin.hash_160(pubkey_bytes)
+ x_pubkey = 'fd' + bh2u(b'\x00' + h160)
+ tx.sign({x_pubkey:(privkey2, compressed)})
+ else:
+ self.wallet.sign_transaction(tx, password)
+ return tx.as_dict()
+
+ @command('')
+ def deserialize(self, tx):
+ """Deserialize a serialized transaction"""
+ tx = Transaction(tx)
+ return tx.deserialize()
+
+ @command('n')
+ def broadcast(self, tx):
+ """Broadcast a transaction to the network. """
+ tx = Transaction(tx)
+ return self.network.broadcast_transaction(tx)
+
+ @command('')
+ def createmultisig(self, num, pubkeys):
+ """Create multisig address"""
+ assert isinstance(pubkeys, list), (type(num), type(pubkeys))
+ redeem_script = multisig_script(pubkeys, num)
+ address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
+ return {'address':address, 'redeemScript':redeem_script}
+
+ @command('w')
+ def freeze(self, address):
+ """Freeze address. Freeze the funds at one of your wallet\'s addresses"""
+ return self.wallet.set_frozen_state([address], True)
+
+ @command('w')
+ def unfreeze(self, address):
+ """Unfreeze address. Unfreeze the funds at one of your wallet\'s address"""
+ return self.wallet.set_frozen_state([address], False)
+
+ @command('wp')
+ def getprivatekeys(self, address, password=None):
+ """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
+ if isinstance(address, str):
+ address = address.strip()
+ if is_address(address):
+ return self.wallet.export_private_key(address, password)[0]
+ domain = address
+ return [self.wallet.export_private_key(address, password)[0] for address in domain]
+
+ @command('w')
+ def ismine(self, address):
+ """Check if address is in wallet. Return true if and only address is in wallet"""
+ return self.wallet.is_mine(address)
+
+ @command('')
+ def dumpprivkeys(self):
+ """Deprecated."""
+ return "This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '"
+
+ @command('')
+ def validateaddress(self, address):
+ """Check that an address is valid. """
+ return is_address(address)
+
+ @command('w')
+ def getpubkeys(self, address):
+ """Return the public keys for a wallet address. """
+ return self.wallet.get_public_keys(address)
+
+ @command('w')
+ def getbalance(self):
+ """Return the balance of your wallet. """
+ c, u, x = self.wallet.get_balance()
+ out = {"confirmed": str(Decimal(c)/COIN)}
+ if u:
+ out["unconfirmed"] = str(Decimal(u)/COIN)
+ if x:
+ out["unmatured"] = str(Decimal(x)/COIN)
+ return out
+
+ @command('n')
+ def getaddressbalance(self, address):
+ """Return the balance of any address. Note: This is a walletless
+ server query, results are not checked by SPV.
+ """
+ sh = bitcoin.address_to_scripthash(address)
+ out = self.network.get_balance_for_scripthash(sh)
+ out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
+ out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
+ return out
+
+ @command('n')
+ def getmerkle(self, txid, height):
+ """Get Merkle branch of a transaction included in a block. Electrum
+ uses this to verify transactions (Simple Payment Verification)."""
+ return self.network.get_merkle_for_transaction(txid, int(height))
+
+ @command('n')
+ def getservers(self):
+ """Return the list of available servers"""
+ return self.network.get_servers()
+
+ @command('')
+ def version(self):
+ """Return the version of Electrum."""
+ from .version import ELECTRUM_VERSION
+ return ELECTRUM_VERSION
+
+ @command('w')
+ def getmpk(self):
+ """Get master public key. Return your wallet\'s master public key"""
+ return self.wallet.get_master_public_key()
+
+ @command('wp')
+ def getmasterprivate(self, password=None):
+ """Get master private key. Return your wallet\'s master private key"""
+ return str(self.wallet.keystore.get_master_private_key(password))
+
+ @command('wp')
+ def getseed(self, password=None):
+ """Get seed phrase. Print the generation seed of your wallet."""
+ s = self.wallet.get_seed(password)
+ return s
+
+ @command('wp')
+ def importprivkey(self, privkey, password=None):
+ """Import a private key."""
+ if not self.wallet.can_import_privkey():
+ return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key."
+ try:
+ addr = self.wallet.import_private_key(privkey, password)
+ out = "Keypair imported: " + addr
+ except BaseException as e:
+ out = "Error: " + str(e)
+ return out
+
+ def _resolver(self, x):
+ if x is None:
+ return None
+ out = self.wallet.contacts.resolve(x)
+ if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
+ raise Exception('cannot verify alias', x)
+ return out['address']
+
+ @command('n')
+ def sweep(self, privkey, destination, fee=None, nocheck=False, imax=100):
+ """Sweep private keys. Returns a transaction that spends UTXOs from
+ privkey to a destination address. The transaction is not
+ broadcasted."""
+ from .wallet import sweep
+ tx_fee = satoshis(fee)
+ privkeys = privkey.split()
+ self.nocheck = nocheck
+ #dest = self._resolver(destination)
+ tx = sweep(privkeys, self.network, self.config, destination, tx_fee, imax)
+ return tx.as_dict() if tx else None
+
+ @command('wp')
+ def signmessage(self, address, message, password=None):
+ """Sign a message with a key. Use quotes if your message contains
+ whitespaces"""
+ sig = self.wallet.sign_message(address, message, password)
+ return base64.b64encode(sig).decode('ascii')
+
+ @command('')
+ def verifymessage(self, address, signature, message):
+ """Verify a signature."""
+ sig = base64.b64decode(signature)
+ message = util.to_bytes(message)
+ return ecc.verify_message_with_address(address, sig, message)
+
+ def _mktx(self, outputs, fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime=None):
+ self.nocheck = nocheck
+ change_addr = self._resolver(change_addr)
+ domain = None if domain is None else map(self._resolver, domain)
+ final_outputs = []
+ for address, amount in outputs:
+ address = self._resolver(address)
+ amount = satoshis(amount)
+ final_outputs.append(TxOutput(TYPE_ADDRESS, address, amount))
+
+ coins = self.wallet.get_spendable_coins(domain, self.config)
+ tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
+ if locktime != None:
+ tx.locktime = locktime
+ if rbf is None:
+ rbf = self.config.get('use_rbf', True)
+ if rbf:
+ tx.set_rbf(True)
+ if not unsigned:
+ self.wallet.sign_transaction(tx, password)
+ return tx
+
+ @command('wp')
+ def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None):
+ """Create a transaction. """
+ tx_fee = satoshis(fee)
+ domain = from_addr.split(',') if from_addr else None
+ tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)
+ return tx.as_dict()
+
+ @command('wp')
+ def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=None, password=None, locktime=None):
+ """Create a multi-output transaction. """
+ tx_fee = satoshis(fee)
+ domain = from_addr.split(',') if from_addr else None
+ tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)
+ return tx.as_dict()
+
+ @command('w')
+ def history(self, year=None, show_addresses=False, show_fiat=False):
+ """Wallet history. Returns the transaction history of your wallet."""
+ kwargs = {'show_addresses': show_addresses}
+ if year:
+ import time
+ start_date = datetime.datetime(year, 1, 1)
+ end_date = datetime.datetime(year+1, 1, 1)
+ kwargs['from_timestamp'] = time.mktime(start_date.timetuple())
+ kwargs['to_timestamp'] = time.mktime(end_date.timetuple())
+ if show_fiat:
+ from .exchange_rate import FxThread
+ fx = FxThread(self.config, None)
+ kwargs['fx'] = fx
+ return json_encode(self.wallet.get_full_history(**kwargs))
+
+ @command('w')
+ def setlabel(self, key, label):
+ """Assign a label to an item. Item may be a bitcore address or a
+ transaction ID"""
+ self.wallet.set_label(key, label)
+
+ @command('w')
+ def listcontacts(self):
+ """Show your list of contacts"""
+ return self.wallet.contacts
+
+ @command('w')
+ def getalias(self, key):
+ """Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record."""
+ return self.wallet.contacts.resolve(key)
+
+ @command('w')
+ def searchcontacts(self, query):
+ """Search through contacts, return matching entries. """
+ results = {}
+ for key, value in self.wallet.contacts.items():
+ if query.lower() in key.lower():
+ results[key] = value
+ return results
+
+ @command('w')
+ def listaddresses(self, receiving=False, change=False, labels=False, frozen=False, unused=False, funded=False, balance=False):
+ """List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results."""
+ out = []
+ for addr in self.wallet.get_addresses():
+ if frozen and not self.wallet.is_frozen(addr):
+ continue
+ if receiving and self.wallet.is_change(addr):
+ continue
+ if change and not self.wallet.is_change(addr):
+ continue
+ if unused and self.wallet.is_used(addr):
+ continue
+ if funded and self.wallet.is_empty(addr):
+ continue
+ item = addr
+ if labels or balance:
+ item = (item,)
+ if balance:
+ item += (format_satoshis(sum(self.wallet.get_addr_balance(addr))),)
+ if labels:
+ item += (repr(self.wallet.labels.get(addr, '')),)
+ out.append(item)
+ return out
+
+ @command('n')
+ def gettransaction(self, txid):
+ """Retrieve a transaction. """
+ if self.wallet and txid in self.wallet.transactions:
+ tx = self.wallet.transactions[txid]
+ else:
+ raw = self.network.get_transaction(txid)
+ if raw:
+ tx = Transaction(raw)
+ else:
+ raise Exception("Unknown transaction")
+ return tx.as_dict()
+
+ @command('')
+ def encrypt(self, pubkey, message):
+ """Encrypt a message with a public key. Use quotes if the message contains whitespaces."""
+ public_key = ecc.ECPubkey(bfh(pubkey))
+ encrypted = public_key.encrypt_message(message)
+ return encrypted
+
+ @command('wp')
+ def decrypt(self, pubkey, encrypted, password=None):
+ """Decrypt a message encrypted with a public key."""
+ return self.wallet.decrypt_message(pubkey, encrypted, password)
+
+ def _format_request(self, out):
+ pr_str = {
+ PR_UNKNOWN: 'Unknown',
+ PR_UNPAID: 'Pending',
+ PR_PAID: 'Paid',
+ PR_EXPIRED: 'Expired',
+ }
+ out['amount (BTX)'] = format_satoshis(out.get('amount'))
+ out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
+ return out
+
+ @command('w')
+ def getrequest(self, key):
+ """Return a payment request"""
+ r = self.wallet.get_payment_request(key, self.config)
+ if not r:
+ raise Exception("Request not found")
+ return self._format_request(r)
+
+ #@command('w')
+ #def ackrequest(self, serialized):
+ # """"""
+ # pass
+
+ @command('w')
+ def listrequests(self, pending=False, expired=False, paid=False):
+ """List the payment requests you made."""
+ out = self.wallet.get_sorted_requests(self.config)
+ if pending:
+ f = PR_UNPAID
+ elif expired:
+ f = PR_EXPIRED
+ elif paid:
+ f = PR_PAID
+ else:
+ f = None
+ if f is not None:
+ out = list(filter(lambda x: x.get('status')==f, out))
+ return list(map(self._format_request, out))
+
+ @command('w')
+ def createnewaddress(self):
+ """Create a new receiving address, beyond the gap limit of the wallet"""
+ return self.wallet.create_new_address(False)
+
+ @command('w')
+ def getunusedaddress(self):
+ """Returns the first unused address of the wallet, or None if all addresses are used.
+ An address is considered as used if it has received a transaction, or if it is used in a payment request."""
+ return self.wallet.get_unused_address()
+
+ @command('w')
+ def addrequest(self, amount, memo='', expiration=None, force=False):
+ """Create a payment request, using the first unused address of the wallet.
+ The address will be considered as used after this operation.
+ If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
+ addr = self.wallet.get_unused_address()
+ if addr is None:
+ if force:
+ addr = self.wallet.create_new_address(False)
+ else:
+ return False
+ amount = satoshis(amount)
+ expiration = int(expiration) if expiration else None
+ req = self.wallet.make_payment_request(addr, amount, memo, expiration)
+ self.wallet.add_payment_request(req, self.config)
+ out = self.wallet.get_payment_request(addr, self.config)
+ return self._format_request(out)
+
+ @command('w')
+ def addtransaction(self, tx):
+ """ Add a transaction to the wallet history """
+ tx = Transaction(tx)
+ if not self.wallet.add_transaction(tx.txid(), tx):
+ return False
+ self.wallet.save_transactions()
+ return tx.txid()
+
+ @command('wp')
+ def signrequest(self, address, password=None):
+ "Sign payment request with an OpenAlias"
+ alias = self.config.get('alias')
+ if not alias:
+ raise Exception('No alias in your configuration')
+ alias_addr = self.wallet.contacts.resolve(alias)['address']
+ self.wallet.sign_payment_request(address, alias, alias_addr, password)
+
+ @command('w')
+ def rmrequest(self, address):
+ """Remove a payment request"""
+ return self.wallet.remove_payment_request(address, self.config)
+
+ @command('w')
+ def clearrequests(self):
+ """Remove all payment requests"""
+ for k in list(self.wallet.receive_requests.keys()):
+ self.wallet.remove_payment_request(k, self.config)
+
+ @command('n')
+ def notify(self, address, URL):
+ """Watch an address. Every time the address changes, a http POST is sent to the URL."""
+ def callback(x):
+ import urllib.request
+ headers = {'content-type':'application/json'}
+ data = {'address':address, 'status':x.get('result')}
+ serialized_data = util.to_bytes(json.dumps(data))
+ try:
+ req = urllib.request.Request(URL, serialized_data, headers)
+ response_stream = urllib.request.urlopen(req, timeout=5)
+ util.print_error('Got Response for %s' % address)
+ except BaseException as e:
+ util.print_error(str(e))
+ self.network.subscribe_to_addresses([address], callback)
+ return True
+
+ @command('wn')
+ def is_synchronized(self):
+ """ return wallet synchronization status """
+ return self.wallet.is_up_to_date()
+
+ @command('n')
+ def getfeerate(self, fee_method=None, fee_level=None):
+ """Return current suggested fee rate (in sat/kvByte), according to config
+ settings or supplied parameters.
+ """
+ if fee_method is None:
+ dyn, mempool = None, None
+ elif fee_method.lower() == 'static':
+ dyn, mempool = False, False
+ elif fee_method.lower() == 'eta':
+ dyn, mempool = True, False
+ elif fee_method.lower() == 'mempool':
+ dyn, mempool = True, True
+ else:
+ raise Exception('Invalid fee estimation method: {}'.format(fee_method))
+ if fee_level is not None:
+ fee_level = Decimal(fee_level)
+ return self.config.fee_per_kb(dyn=dyn, mempool=mempool, fee_level=fee_level)
+
+ @command('')
+ def help(self):
+ # for the python console
+ return sorted(known_commands.keys())
+
+param_descriptions = {
+ 'privkey': 'Private key. Type \'?\' to get a prompt.',
+ 'destination': 'Bitcore address, contact or alias',
+ 'address': 'Bitcore address',
+ 'seed': 'Seed phrase',
+ 'txid': 'Transaction ID',
+ 'pos': 'Position',
+ 'height': 'Block height',
+ 'tx': 'Serialized transaction (hexadecimal)',
+ 'key': 'Variable name',
+ 'pubkey': 'Public key',
+ 'message': 'Clear text message. Use quotes if it contains spaces.',
+ 'encrypted': 'Encrypted message',
+ 'amount': 'Amount to be sent (in BTX). Type \'!\' to send the maximum available.',
+ 'requested_amount': 'Requested amount (in BTX).',
+ 'outputs': 'list of ["address", amount]',
+ 'redeem_script': 'redeem script (hexadecimal)',
+}
+
+command_options = {
+ 'password': ("-W", "Password"),
+ 'new_password':(None, "New Password"),
+ 'receiving': (None, "Show only receiving addresses"),
+ 'change': (None, "Show only change addresses"),
+ 'frozen': (None, "Show only frozen addresses"),
+ 'unused': (None, "Show only unused addresses"),
+ 'funded': (None, "Show only funded addresses"),
+ 'balance': ("-b", "Show the balances of listed addresses"),
+ 'labels': ("-l", "Show the labels of listed addresses"),
+ 'nocheck': (None, "Do not verify aliases"),
+ 'imax': (None, "Maximum number of inputs"),
+ 'fee': ("-f", "Transaction fee (in BTX)"),
+ 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."),
+ 'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"),
+ 'nbits': (None, "Number of bits of entropy"),
+ 'segwit': (None, "Create segwit seed"),
+ 'language': ("-L", "Default language for wordlist"),
+ 'privkey': (None, "Private key. Set to '?' to get a prompt."),
+ 'unsigned': ("-u", "Do not sign transaction"),
+ 'rbf': (None, "Replace-by-fee transaction"),
+ 'locktime': (None, "Set locktime block number"),
+ 'domain': ("-D", "List of addresses"),
+ 'memo': ("-m", "Description of the request"),
+ 'expiration': (None, "Time in seconds"),
+ 'timeout': (None, "Timeout in seconds"),
+ 'force': (None, "Create new address beyond gap limit, if no more addresses are available."),
+ 'pending': (None, "Show only pending requests."),
+ 'expired': (None, "Show only expired requests."),
+ 'paid': (None, "Show only paid requests."),
+ 'show_addresses': (None, "Show input and output addresses"),
+ 'show_fiat': (None, "Show fiat value of transactions"),
+ 'year': (None, "Show history for a given year"),
+ 'fee_method': (None, "Fee estimation method to use"),
+ 'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position")
+}
+
+
+# don't use floats because of rounding errors
+from .transaction import tx_from_str
+json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
+arg_types = {
+ 'num': int,
+ 'nbits': int,
+ 'imax': int,
+ 'year': int,
+ 'tx': tx_from_str,
+ 'pubkeys': json_loads,
+ 'jsontx': json_loads,
+ 'inputs': json_loads,
+ 'outputs': json_loads,
+ 'fee': lambda x: str(Decimal(x)) if x is not None else None,
+ 'amount': lambda x: str(Decimal(x)) if x != '!' else '!',
+ 'locktime': int,
+ 'fee_method': str,
+ 'fee_level': json_loads,
+}
+
+config_variables = {
+
+ 'addrequest': {
+ 'requests_dir': 'directory where a bip70 file will be written.',
+ 'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
+ 'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
+ 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcore: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
+ },
+ 'listrequests':{
+ 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcore: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
+ }
+}
+
+def set_default_subparser(self, name, args=None):
+ """see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand"""
+ subparser_found = False
+ for arg in sys.argv[1:]:
+ if arg in ['-h', '--help']: # global help if no subparser
+ break
+ else:
+ for x in self._subparsers._actions:
+ if not isinstance(x, argparse._SubParsersAction):
+ continue
+ for sp_name in x._name_parser_map.keys():
+ if sp_name in sys.argv[1:]:
+ subparser_found = True
+ if not subparser_found:
+ # insert default in first position, this implies no
+ # global options without a sub_parsers specified
+ if args is None:
+ sys.argv.insert(1, name)
+ else:
+ args.insert(0, name)
+
+argparse.ArgumentParser.set_default_subparser = set_default_subparser
+
+
+# workaround https://bugs.python.org/issue23058
+# see https://github.com/nickstenning/honcho/pull/121
+
+def subparser_call(self, parser, namespace, values, option_string=None):
+ from argparse import ArgumentError, SUPPRESS, _UNRECOGNIZED_ARGS_ATTR
+ parser_name = values[0]
+ arg_strings = values[1:]
+ # set the parser name if requested
+ if self.dest is not SUPPRESS:
+ setattr(namespace, self.dest, parser_name)
+ # select the parser
+ try:
+ parser = self._name_parser_map[parser_name]
+ except KeyError:
+ tup = parser_name, ', '.join(self._name_parser_map)
+ msg = _('unknown parser {!r} (choices: {})').format(*tup)
+ raise ArgumentError(self, msg)
+ # parse all the remaining options into the namespace
+ # store any unrecognized options on the object, so that the top
+ # level parser can decide what to do with them
+ namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+ if arg_strings:
+ vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+ getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+argparse._SubParsersAction.__call__ = subparser_call
+
+
+def add_network_options(parser):
+ parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only")
+ parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
+ parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
+ parser.add_argument("--noonion", action="store_true", dest="noonion", default=None, help="do not try to connect to onion servers")
+
+def add_global_options(parser):
+ group = parser.add_argument_group('global options')
+ # const is for when no argument is given to verbosity
+ # default is for when the flag is missing
+ group.add_argument("-v", dest="verbosity", help="Set verbosity filter", default='', const='*', nargs='?')
+ group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
+ group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum-btx_data' directory")
+ group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
+ group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
+ group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest")
+ group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet")
+
+def get_parser():
+ # create main parser
+ parser = argparse.ArgumentParser(
+ epilog="Run 'electrum help ' to see the help for a command")
+ add_global_options(parser)
+ subparsers = parser.add_subparsers(dest='cmd', metavar='')
+ # gui
+ parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
+ parser_gui.add_argument("url", nargs='?', default=None, help="bitcore URI (or bip70 file)")
+ parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio'])
+ parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
+ parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
+ parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
+ add_network_options(parser_gui)
+ add_global_options(parser_gui)
+ # daemon
+ parser_daemon = subparsers.add_parser('daemon', help="Run Daemon")
+ parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop', 'load_wallet', 'close_wallet'], nargs='?')
+ #parser_daemon.set_defaults(func=run_daemon)
+ add_network_options(parser_daemon)
+ add_global_options(parser_daemon)
+ # commands
+ for cmdname in sorted(known_commands.keys()):
+ cmd = known_commands[cmdname]
+ p = subparsers.add_parser(cmdname, help=cmd.help, description=cmd.description)
+ add_global_options(p)
+ if cmdname == 'restore':
+ p.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
+ for optname, default in zip(cmd.options, cmd.defaults):
+ a, help = command_options[optname]
+ b = '--' + optname
+ action = "store_true" if type(default) is bool else 'store'
+ args = (a, b) if a else (b,)
+ if action == 'store':
+ _type = arg_types.get(optname, str)
+ p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type)
+ else:
+ p.add_argument(*args, dest=optname, action=action, default=default, help=help)
+
+ for param in cmd.params:
+ h = param_descriptions.get(param, '')
+ _type = arg_types.get(param, str)
+ p.add_argument(param, help=h, type=_type)
+
+ cvh = config_variables.get(cmdname)
+ if cvh:
+ group = p.add_argument_group('configuration variables', '(set with setconfig/getconfig)')
+ for k, v in cvh.items():
+ group.add_argument(k, nargs='?', help=v)
+
+ # 'gui' is the default command
+ parser.set_default_subparser('gui')
+ return parser
diff --git a/electrum/constants.py b/electrum/constants.py
new file mode 100644
index 000000000..ec432f28e
--- /dev/null
+++ b/electrum/constants.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import json
+
+
+def read_json(filename, default):
+ path = os.path.join(os.path.dirname(__file__), filename)
+ try:
+ with open(path, 'r') as f:
+ r = json.loads(f.read())
+ except:
+ r = default
+ return r
+
+
+class BitcoinMainnet:
+
+ TESTNET = False
+ WIF_PREFIX = 0x80
+ ADDRTYPE_P2PKH = 3
+ ADDRTYPE_P2SH = 125
+ SEGWIT_HRP = "btx"
+ GENESIS = "604148281e5c4b7f2487e5d03cd60d8e6f69411d613f6448034508cea52e9574"
+ DEFAULT_PORTS = {'t': '50001', 's': '50002'}
+ DEFAULT_SERVERS = read_json('servers.json', {})
+ CHECKPOINTS = read_json('checkpoints.json', [])
+
+ XPRV_HEADERS = {
+ 'standard': 0x0488ade4, # xprv
+ 'p2wpkh-p2sh': 0x049d7878, # yprv
+ 'p2wsh-p2sh': 0x0295b005, # Yprv
+ 'p2wpkh': 0x04b2430c, # zprv
+ 'p2wsh': 0x02aa7a99, # Zprv
+ }
+ XPUB_HEADERS = {
+ 'standard': 0x0488b21e, # xpub
+ 'p2wpkh-p2sh': 0x049d7cb2, # ypub
+ 'p2wsh-p2sh': 0x0295b43f, # Ypub
+ 'p2wpkh': 0x04b24746, # zpub
+ 'p2wsh': 0x02aa7ed3, # Zpub
+ }
+ BIP44_COIN_TYPE = 160
+
+
+class BitcoinTestnet:
+
+ TESTNET = True
+ WIF_PREFIX = 0xef
+ ADDRTYPE_P2PKH = 111
+ ADDRTYPE_P2SH = 196
+ SEGWIT_HRP = "tb"
+ GENESIS = "02c5d66e8edb49984eb743c798bca069466ce457b7febfa3c3a01b33353b7bc6"
+ DEFAULT_PORTS = {'t': '51001', 's': '51002'}
+ DEFAULT_SERVERS = read_json('servers_testnet.json', {})
+ CHECKPOINTS = read_json('checkpoints_testnet.json', [])
+
+ XPRV_HEADERS = {
+ 'standard': 0x04358394, # tprv
+ 'p2wpkh-p2sh': 0x044a4e28, # uprv
+ 'p2wsh-p2sh': 0x024285b5, # Uprv
+ 'p2wpkh': 0x045f18bc, # vprv
+ 'p2wsh': 0x02575048, # Vprv
+ }
+ XPUB_HEADERS = {
+ 'standard': 0x043587cf, # tpub
+ 'p2wpkh-p2sh': 0x044a5262, # upub
+ 'p2wsh-p2sh': 0x024289ef, # Upub
+ 'p2wpkh': 0x045f1cf6, # vpub
+ 'p2wsh': 0x02575483, # Vpub
+ }
+ BIP44_COIN_TYPE = 1
+
+
+class BitcoinRegtest(BitcoinTestnet):
+
+ SEGWIT_HRP = "bcrt"
+ GENESIS = "604148281e5c4b7f2487e5d03cd60d8e6f69411d613f6448034508cea52e9574"
+ DEFAULT_SERVERS = read_json('servers_regtest.json', {})
+ CHECKPOINTS = []
+
+
+class BitcoinSimnet(BitcoinTestnet):
+
+ SEGWIT_HRP = "sb"
+ GENESIS = "683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6"
+ DEFAULT_SERVERS = read_json('servers_regtest.json', {})
+ CHECKPOINTS = []
+
+
+# don't import net directly, import the module instead (so that net is singleton)
+net = BitcoinMainnet
+
+def set_simnet():
+ global net
+ net = BitcoinSimnet
+
+def set_mainnet():
+ global net
+ net = BitcoinMainnet
+
+
+def set_testnet():
+ global net
+ net = BitcoinTestnet
+
+
+def set_regtest():
+ global net
+ net = BitcoinRegtest
diff --git a/electrum/contacts.py b/electrum/contacts.py
new file mode 100644
index 000000000..03b8d3ecc
--- /dev/null
+++ b/electrum/contacts.py
@@ -0,0 +1,135 @@
+# Electrum - Lightweight Bitcoin Client
+# Copyright (c) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import re
+import dns
+from dns.exception import DNSException
+import json
+import traceback
+import sys
+
+from . import bitcoin
+from . import dnssec
+from .util import export_meta, import_meta, print_error, to_string
+
+
+class Contacts(dict):
+
+ def __init__(self, storage):
+ self.storage = storage
+ d = self.storage.get('contacts', {})
+ try:
+ self.update(d)
+ except:
+ return
+ # backward compatibility
+ for k, v in self.items():
+ _type, n = v
+ if _type == 'address' and bitcoin.is_address(n):
+ self.pop(k)
+ self[n] = ('address', k)
+
+ def save(self):
+ self.storage.put('contacts', dict(self))
+
+ def import_file(self, path):
+ import_meta(path, self._validate, self.load_meta)
+
+ def load_meta(self, data):
+ self.update(data)
+ self.save()
+
+ def export_file(self, filename):
+ export_meta(self, filename)
+
+ def __setitem__(self, key, value):
+ dict.__setitem__(self, key, value)
+ self.save()
+
+ def pop(self, key):
+ if key in self.keys():
+ dict.pop(self, key)
+ self.save()
+
+ def resolve(self, k):
+ if bitcoin.is_address(k):
+ return {
+ 'address': k,
+ 'type': 'address'
+ }
+ if k in self.keys():
+ _type, addr = self[k]
+ if _type == 'address':
+ return {
+ 'address': addr,
+ 'type': 'contact'
+ }
+ out = self.resolve_openalias(k)
+ if out:
+ address, name, validated = out
+ return {
+ 'address': address,
+ 'name': name,
+ 'type': 'openalias',
+ 'validated': validated
+ }
+ raise Exception("Invalid Bitcoin address or alias", k)
+
+ def resolve_openalias(self, url):
+ # support email-style addresses, per the OA standard
+ url = url.replace('@', '.')
+ try:
+ records, validated = dnssec.query(url, dns.rdatatype.TXT)
+ except DNSException as e:
+ print_error('Error resolving openalias: ', str(e))
+ return None
+ prefix = 'btc'
+ for record in records:
+ string = to_string(record.strings[0], 'utf8')
+ if string.startswith('oa1:' + prefix):
+ address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
+ name = self.find_regex(string, r'recipient_name=([^;]+)')
+ if not name:
+ name = address
+ if not address:
+ continue
+ return address, name, validated
+
+ def find_regex(self, haystack, needle):
+ regex = re.compile(needle)
+ try:
+ return regex.search(haystack).groups()[0]
+ except AttributeError:
+ return None
+
+ def _validate(self, data):
+ for k, v in list(data.items()):
+ if k == 'contacts':
+ return self._validate(v)
+ if not bitcoin.is_address(k):
+ data.pop(k)
+ else:
+ _type, _ = v
+ if _type != 'address':
+ data.pop(k)
+ return data
+
diff --git a/electrum/crypto.py b/electrum/crypto.py
new file mode 100644
index 000000000..de8b6b5d7
--- /dev/null
+++ b/electrum/crypto.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import base64
+import os
+import hashlib
+import hmac
+
+import pyaes
+
+from .util import assert_bytes, InvalidPassword, to_bytes, to_string
+
+
+try:
+ from Cryptodome.Cipher import AES
+except:
+ AES = None
+
+
+class InvalidPadding(Exception):
+ pass
+
+
+def append_PKCS7_padding(data):
+ assert_bytes(data)
+ padlen = 16 - (len(data) % 16)
+ return data + bytes([padlen]) * padlen
+
+
+def strip_PKCS7_padding(data):
+ assert_bytes(data)
+ if len(data) % 16 != 0 or len(data) == 0:
+ raise InvalidPadding("invalid length")
+ padlen = data[-1]
+ if padlen > 16:
+ raise InvalidPadding("invalid padding byte (large)")
+ for i in data[-padlen:]:
+ if i != padlen:
+ raise InvalidPadding("invalid padding byte (inconsistent)")
+ return data[0:-padlen]
+
+
+def aes_encrypt_with_iv(key, iv, data):
+ assert_bytes(key, iv, data)
+ data = append_PKCS7_padding(data)
+ if AES:
+ e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)
+ else:
+ aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
+ aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)
+ e = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
+ return e
+
+
+def aes_decrypt_with_iv(key, iv, data):
+ assert_bytes(key, iv, data)
+ if AES:
+ cipher = AES.new(key, AES.MODE_CBC, iv)
+ data = cipher.decrypt(data)
+ else:
+ aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
+ aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)
+ data = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
+ try:
+ return strip_PKCS7_padding(data)
+ except InvalidPadding:
+ raise InvalidPassword()
+
+
+def EncodeAES(secret, s):
+ assert_bytes(s)
+ iv = bytes(os.urandom(16))
+ ct = aes_encrypt_with_iv(secret, iv, s)
+ e = iv + ct
+ return base64.b64encode(e)
+
+def DecodeAES(secret, e):
+ e = bytes(base64.b64decode(e))
+ iv, e = e[:16], e[16:]
+ s = aes_decrypt_with_iv(secret, iv, e)
+ return s
+
+def pw_encode(s, password):
+ if password:
+ secret = Hash(password)
+ return EncodeAES(secret, to_bytes(s, "utf8")).decode('utf8')
+ else:
+ return s
+
+def pw_decode(s, password):
+ if password is not None:
+ secret = Hash(password)
+ try:
+ d = to_string(DecodeAES(secret, s), "utf8")
+ except Exception:
+ raise InvalidPassword()
+ return d
+ else:
+ return s
+
+
+def sha256(x: bytes) -> bytes:
+ x = to_bytes(x, 'utf8')
+ return bytes(hashlib.sha256(x).digest())
+
+
+def Hash(x: bytes) -> bytes:
+ x = to_bytes(x, 'utf8')
+ out = bytes(sha256(sha256(x)))
+ return out
+
+
+def hash_160(x: bytes) -> bytes:
+ try:
+ md = hashlib.new('ripemd160')
+ md.update(sha256(x))
+ return md.digest()
+ except BaseException:
+ from . import ripemd
+ md = ripemd.new(sha256(x))
+ return md.digest()
+
+
+def hmac_oneshot(key: bytes, msg: bytes, digest) -> bytes:
+ if hasattr(hmac, 'digest'):
+ # requires python 3.7+; faster
+ return hmac.digest(key, msg, digest)
+ else:
+ return hmac.new(key, msg, digest).digest()
diff --git a/electrum/currencies.json b/electrum/currencies.json
new file mode 100644
index 000000000..0d4aa2023
--- /dev/null
+++ b/electrum/currencies.json
@@ -0,0 +1,36 @@
+{
+ "CoinMarketCap": [
+ "AUD",
+ "BRL",
+ "CAD",
+ "CHF",
+ "CLP",
+ "CNY",
+ "CZK",
+ "DKK",
+ "EUR",
+ "GBP",
+ "HKD",
+ "HUF",
+ "IDR",
+ "ILS",
+ "INR",
+ "JPY",
+ "KRW",
+ "MXN",
+ "MYR",
+ "NOK",
+ "NZD",
+ "PHP",
+ "PKR",
+ "PLN",
+ "RUB",
+ "SEK",
+ "SGD",
+ "THB",
+ "TRY",
+ "TWD",
+ "USD",
+ "ZAR"
+ ]
+}
\ No newline at end of file
diff --git a/electrum/daemon.py b/electrum/daemon.py
new file mode 100644
index 000000000..ccdce4a0a
--- /dev/null
+++ b/electrum/daemon.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import ast
+import os
+import time
+import traceback
+import sys
+
+# from jsonrpc import JSONRPCResponseManager
+import jsonrpclib
+from .jsonrpc import VerifyingJSONRPCServer
+
+from .version import ELECTRUM_VERSION
+from .network import Network
+from .util import json_decode, DaemonThread
+from .util import print_error, to_string
+from .wallet import Wallet
+from .storage import WalletStorage
+from .commands import known_commands, Commands
+from .simple_config import SimpleConfig
+from .exchange_rate import FxThread
+from .plugin import run_hook
+
+
+def get_lockfile(config):
+ return os.path.join(config.path, 'daemon')
+
+
+def remove_lockfile(lockfile):
+ os.unlink(lockfile)
+
+
+def get_fd_or_server(config):
+ '''Tries to create the lockfile, using O_EXCL to
+ prevent races. If it succeeds it returns the FD.
+ Otherwise try and connect to the server specified in the lockfile.
+ If this succeeds, the server is returned. Otherwise remove the
+ lockfile and try again.'''
+ lockfile = get_lockfile(config)
+ while True:
+ try:
+ return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None
+ except OSError:
+ pass
+ server = get_server(config)
+ if server is not None:
+ return None, server
+ # Couldn't connect; remove lockfile and try again.
+ remove_lockfile(lockfile)
+
+
+def get_server(config):
+ lockfile = get_lockfile(config)
+ while True:
+ create_time = None
+ try:
+ with open(lockfile) as f:
+ (host, port), create_time = ast.literal_eval(f.read())
+ rpc_user, rpc_password = get_rpc_credentials(config)
+ if rpc_password == '':
+ # authentication disabled
+ server_url = 'http://%s:%d' % (host, port)
+ else:
+ server_url = 'http://%s:%s@%s:%d' % (
+ rpc_user, rpc_password, host, port)
+ server = jsonrpclib.Server(server_url)
+ # Test daemon is running
+ server.ping()
+ return server
+ except Exception as e:
+ print_error("[get_server]", e)
+ if not create_time or create_time < time.time() - 1.0:
+ return None
+ # Sleep a bit and try again; it might have just been started
+ time.sleep(1.0)
+
+
+def get_rpc_credentials(config):
+ rpc_user = config.get('rpcuser', None)
+ rpc_password = config.get('rpcpassword', None)
+ if rpc_user is None or rpc_password is None:
+ rpc_user = 'user'
+ import ecdsa, base64
+ bits = 128
+ nbytes = bits // 8 + (bits % 8 > 0)
+ pw_int = ecdsa.util.randrange(pow(2, bits))
+ pw_b64 = base64.b64encode(
+ pw_int.to_bytes(nbytes, 'big'), b'-_')
+ rpc_password = to_string(pw_b64, 'ascii')
+ config.set_key('rpcuser', rpc_user)
+ config.set_key('rpcpassword', rpc_password, save=True)
+ elif rpc_password == '':
+ from .util import print_stderr
+ print_stderr('WARNING: RPC authentication is disabled.')
+ return rpc_user, rpc_password
+
+
+class Daemon(DaemonThread):
+
+ def __init__(self, config, fd, is_gui):
+ DaemonThread.__init__(self)
+ self.config = config
+ if config.get('offline'):
+ self.network = None
+ else:
+ self.network = Network(config)
+ self.network.start()
+ self.fx = FxThread(config, self.network)
+ if self.network:
+ self.network.add_jobs([self.fx])
+ self.gui = None
+ self.wallets = {}
+ # Setup JSONRPC server
+ self.init_server(config, fd, is_gui)
+
+ def init_server(self, config, fd, is_gui):
+ host = config.get('rpchost', '127.0.0.1')
+ port = config.get('rpcport', 0)
+
+ rpc_user, rpc_password = get_rpc_credentials(config)
+ try:
+ server = VerifyingJSONRPCServer((host, port), logRequests=False,
+ rpc_user=rpc_user, rpc_password=rpc_password)
+ except Exception as e:
+ self.print_error('Warning: cannot initialize RPC server on host', host, e)
+ self.server = None
+ os.close(fd)
+ return
+ os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8'))
+ os.close(fd)
+ self.server = server
+ server.timeout = 0.1
+ server.register_function(self.ping, 'ping')
+ if is_gui:
+ server.register_function(self.run_gui, 'gui')
+ else:
+ server.register_function(self.run_daemon, 'daemon')
+ self.cmd_runner = Commands(self.config, None, self.network)
+ for cmdname in known_commands:
+ server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
+ server.register_function(self.run_cmdline, 'run_cmdline')
+
+ def ping(self):
+ return True
+
+ def run_daemon(self, config_options):
+ config = SimpleConfig(config_options)
+ sub = config.get('subcommand')
+ assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']
+ if sub in [None, 'start']:
+ response = "Daemon already running"
+ elif sub == 'load_wallet':
+ path = config.get_wallet_path()
+ wallet = self.load_wallet(path, config.get('password'))
+ if wallet is not None:
+ self.cmd_runner.wallet = wallet
+ run_hook('load_wallet', wallet, None)
+ response = wallet is not None
+ elif sub == 'close_wallet':
+ path = config.get_wallet_path()
+ if path in self.wallets:
+ self.stop_wallet(path)
+ response = True
+ else:
+ response = False
+ elif sub == 'status':
+ if self.network:
+ p = self.network.get_parameters()
+ current_wallet = self.cmd_runner.wallet
+ current_wallet_path = current_wallet.storage.path \
+ if current_wallet else None
+ response = {
+ 'path': self.network.config.path,
+ 'server': p[0],
+ 'blockchain_height': self.network.get_local_height(),
+ 'server_height': self.network.get_server_height(),
+ 'spv_nodes': len(self.network.get_interfaces()),
+ 'connected': self.network.is_connected(),
+ 'auto_connect': p[4],
+ 'version': ELECTRUM_VERSION,
+ 'wallets': {k: w.is_up_to_date()
+ for k, w in self.wallets.items()},
+ 'current_wallet': current_wallet_path,
+ 'fee_per_kb': self.config.fee_per_kb(),
+ }
+ else:
+ response = "Daemon offline"
+ elif sub == 'stop':
+ self.stop()
+ response = "Daemon stopped"
+ return response
+
+ def run_gui(self, config_options):
+ config = SimpleConfig(config_options)
+ if self.gui:
+ #if hasattr(self.gui, 'new_window'):
+ # path = config.get_wallet_path()
+ # self.gui.new_window(path, config.get('url'))
+ # response = "ok"
+ #else:
+ # response = "error: current GUI does not support multiple windows"
+ response = "error: Electrum GUI already running"
+ else:
+ response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
+ return response
+
+ def load_wallet(self, path, password):
+ # wizard will be launched if we return
+ if path in self.wallets:
+ wallet = self.wallets[path]
+ return wallet
+ storage = WalletStorage(path, manual_upgrades=True)
+ if not storage.file_exists():
+ return
+ if storage.is_encrypted():
+ if not password:
+ return
+ storage.decrypt(password)
+ if storage.requires_split():
+ return
+ if storage.get_action():
+ return
+ wallet = Wallet(storage)
+ wallet.start_threads(self.network)
+ self.wallets[path] = wallet
+ return wallet
+
+ def add_wallet(self, wallet):
+ path = wallet.storage.path
+ self.wallets[path] = wallet
+
+ def get_wallet(self, path):
+ return self.wallets.get(path)
+
+ def stop_wallet(self, path):
+ wallet = self.wallets.pop(path)
+ wallet.stop_threads()
+
+ def run_cmdline(self, config_options):
+ password = config_options.get('password')
+ new_password = config_options.get('new_password')
+ config = SimpleConfig(config_options)
+ # FIXME this is ugly...
+ config.fee_estimates = self.network.config.fee_estimates.copy()
+ config.mempool_fees = self.network.config.mempool_fees.copy()
+ cmdname = config.get('cmd')
+ cmd = known_commands[cmdname]
+ if cmd.requires_wallet:
+ path = config.get_wallet_path()
+ wallet = self.wallets.get(path)
+ if wallet is None:
+ return {'error': 'Wallet "%s" is not loaded. Use "electrum daemon load_wallet"'%os.path.basename(path) }
+ else:
+ wallet = None
+ # arguments passed to function
+ args = map(lambda x: config.get(x), cmd.params)
+ # decode json arguments
+ args = [json_decode(i) for i in args]
+ # options
+ kwargs = {}
+ for x in cmd.options:
+ kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
+ cmd_runner = Commands(config, wallet, self.network)
+ func = getattr(cmd_runner, cmd.name)
+ result = func(*args, **kwargs)
+ return result
+
+ def run(self):
+ while self.is_running():
+ self.server.handle_request() if self.server else time.sleep(0.1)
+ for k, wallet in self.wallets.items():
+ wallet.stop_threads()
+ if self.network:
+ self.print_error("shutting down network")
+ self.network.stop()
+ self.network.join()
+ self.on_stop()
+
+ def stop(self):
+ self.print_error("stopping, removing lockfile")
+ remove_lockfile(get_lockfile(self.config))
+ DaemonThread.stop(self)
+
+ def init_gui(self, config, plugins):
+ gui_name = config.get('gui', 'qt')
+ if gui_name in ['lite', 'classic']:
+ gui_name = 'qt'
+ gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
+ self.gui = gui.ElectrumGui(config, self, plugins)
+ try:
+ self.gui.main()
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ # app will exit now
diff --git a/electrum/dnssec.py b/electrum/dnssec.py
new file mode 100644
index 000000000..6a8ac9807
--- /dev/null
+++ b/electrum/dnssec.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Check DNSSEC trust chain.
+# Todo: verify expiration dates
+#
+# Based on
+# http://backreference.org/2010/11/17/dnssec-verification-with-dig/
+# https://github.com/rthalley/dnspython/blob/master/tests/test_dnssec.py
+
+
+# import traceback
+# import sys
+import time
+import struct
+
+
+import dns.name
+import dns.query
+import dns.dnssec
+import dns.message
+import dns.resolver
+import dns.rdatatype
+import dns.rdtypes.ANY.NS
+import dns.rdtypes.ANY.CNAME
+import dns.rdtypes.ANY.DLV
+import dns.rdtypes.ANY.DNSKEY
+import dns.rdtypes.ANY.DS
+import dns.rdtypes.ANY.NSEC
+import dns.rdtypes.ANY.NSEC3
+import dns.rdtypes.ANY.NSEC3PARAM
+import dns.rdtypes.ANY.RRSIG
+import dns.rdtypes.ANY.SOA
+import dns.rdtypes.ANY.TXT
+import dns.rdtypes.IN.A
+import dns.rdtypes.IN.AAAA
+
+
+# Pure-Python version of dns.dnssec._validate_rsig
+import ecdsa
+from . import rsakey
+
+
+def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
+ from dns.dnssec import ValidationFailure, ECDSAP256SHA256, ECDSAP384SHA384
+ from dns.dnssec import _find_candidate_keys, _make_hash, _is_ecdsa, _is_rsa, _to_rdata, _make_algorithm_id
+
+ if isinstance(origin, str):
+ origin = dns.name.from_text(origin, dns.name.root)
+
+ for candidate_key in _find_candidate_keys(keys, rrsig):
+ if not candidate_key:
+ raise ValidationFailure('unknown key')
+
+ # For convenience, allow the rrset to be specified as a (name, rdataset)
+ # tuple as well as a proper rrset
+ if isinstance(rrset, tuple):
+ rrname = rrset[0]
+ rdataset = rrset[1]
+ else:
+ rrname = rrset.name
+ rdataset = rrset
+
+ if now is None:
+ now = time.time()
+ if rrsig.expiration < now:
+ raise ValidationFailure('expired')
+ if rrsig.inception > now:
+ raise ValidationFailure('not yet valid')
+
+ hash = _make_hash(rrsig.algorithm)
+
+ if _is_rsa(rrsig.algorithm):
+ keyptr = candidate_key.key
+ (bytes,) = struct.unpack('!B', keyptr[0:1])
+ keyptr = keyptr[1:]
+ if bytes == 0:
+ (bytes,) = struct.unpack('!H', keyptr[0:2])
+ keyptr = keyptr[2:]
+ rsa_e = keyptr[0:bytes]
+ rsa_n = keyptr[bytes:]
+ n = ecdsa.util.string_to_number(rsa_n)
+ e = ecdsa.util.string_to_number(rsa_e)
+ pubkey = rsakey.RSAKey(n, e)
+ sig = rrsig.signature
+
+ elif _is_ecdsa(rrsig.algorithm):
+ if rrsig.algorithm == ECDSAP256SHA256:
+ curve = ecdsa.curves.NIST256p
+ key_len = 32
+ digest_len = 32
+ elif rrsig.algorithm == ECDSAP384SHA384:
+ curve = ecdsa.curves.NIST384p
+ key_len = 48
+ digest_len = 48
+ else:
+ # shouldn't happen
+ raise ValidationFailure('unknown ECDSA curve')
+ keyptr = candidate_key.key
+ x = ecdsa.util.string_to_number(keyptr[0:key_len])
+ y = ecdsa.util.string_to_number(keyptr[key_len:key_len * 2])
+ assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
+ point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
+ verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, curve)
+ r = rrsig.signature[:key_len]
+ s = rrsig.signature[key_len:]
+ sig = ecdsa.ecdsa.Signature(ecdsa.util.string_to_number(r),
+ ecdsa.util.string_to_number(s))
+
+ else:
+ raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
+
+ hash.update(_to_rdata(rrsig, origin)[:18])
+ hash.update(rrsig.signer.to_digestable(origin))
+
+ if rrsig.labels < len(rrname) - 1:
+ suffix = rrname.split(rrsig.labels + 1)[1]
+ rrname = dns.name.from_text('*', suffix)
+ rrnamebuf = rrname.to_digestable(origin)
+ rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
+ rrsig.original_ttl)
+ rrlist = sorted(rdataset);
+ for rr in rrlist:
+ hash.update(rrnamebuf)
+ hash.update(rrfixed)
+ rrdata = rr.to_digestable(origin)
+ rrlen = struct.pack('!H', len(rrdata))
+ hash.update(rrlen)
+ hash.update(rrdata)
+
+ digest = hash.digest()
+
+ if _is_rsa(rrsig.algorithm):
+ digest = _make_algorithm_id(rrsig.algorithm) + digest
+ if pubkey.verify(bytearray(sig), bytearray(digest)):
+ return
+
+ elif _is_ecdsa(rrsig.algorithm):
+ diglong = ecdsa.util.string_to_number(digest)
+ if verifying_key.pubkey.verifies(diglong, sig):
+ return
+
+ else:
+ raise ValidationFailure('unknown algorithm %s' % rrsig.algorithm)
+
+ raise ValidationFailure('verify failure')
+
+
+# replace validate_rrsig
+dns.dnssec._validate_rrsig = python_validate_rrsig
+dns.dnssec.validate_rrsig = python_validate_rrsig
+dns.dnssec.validate = dns.dnssec._validate
+
+
+
+from .util import print_error
+
+
+# hard-coded trust anchors (root KSKs)
+trust_anchors = [
+ # KSK-2017:
+ dns.rrset.from_text('.', 1 , 'IN', 'DNSKEY', '257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU='),
+ # KSK-2010:
+ dns.rrset.from_text('.', 15202, 'IN', 'DNSKEY', '257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0='),
+]
+
+
+def check_query(ns, sub, _type, keys):
+ q = dns.message.make_query(sub, _type, want_dnssec=True)
+ response = dns.query.tcp(q, ns, timeout=5)
+ assert response.rcode() == 0, 'No answer'
+ answer = response.answer
+ assert len(answer) != 0, ('No DNS record found', sub, _type)
+ assert len(answer) != 1, ('No DNSSEC record found', sub, _type)
+ if answer[0].rdtype == dns.rdatatype.RRSIG:
+ rrsig, rrset = answer
+ elif answer[1].rdtype == dns.rdatatype.RRSIG:
+ rrset, rrsig = answer
+ else:
+ raise Exception('No signature set in record')
+ if keys is None:
+ keys = {dns.name.from_text(sub):rrset}
+ dns.dnssec.validate(rrset, rrsig, keys)
+ return rrset
+
+
+def get_and_validate(ns, url, _type):
+ # get trusted root key
+ root_rrset = None
+ for dnskey_rr in trust_anchors:
+ try:
+ # Check if there is a valid signature for the root dnskey
+ root_rrset = check_query(ns, '', dns.rdatatype.DNSKEY, {dns.name.root: dnskey_rr})
+ break
+ except dns.dnssec.ValidationFailure:
+ # It's OK as long as one key validates
+ continue
+ if not root_rrset:
+ raise dns.dnssec.ValidationFailure('None of the trust anchors found in DNS')
+ keys = {dns.name.root: root_rrset}
+ # top-down verification
+ parts = url.split('.')
+ for i in range(len(parts), 0, -1):
+ sub = '.'.join(parts[i-1:])
+ name = dns.name.from_text(sub)
+ # If server is authoritative, don't fetch DNSKEY
+ query = dns.message.make_query(sub, dns.rdatatype.NS)
+ response = dns.query.udp(query, ns, 3)
+ assert response.rcode() == dns.rcode.NOERROR, "query error"
+ rrset = response.authority[0] if len(response.authority) > 0 else response.answer[0]
+ rr = rrset[0]
+ if rr.rdtype == dns.rdatatype.SOA:
+ continue
+ # get DNSKEY (self-signed)
+ rrset = check_query(ns, sub, dns.rdatatype.DNSKEY, None)
+ # get DS (signed by parent)
+ ds_rrset = check_query(ns, sub, dns.rdatatype.DS, keys)
+ # verify that a signed DS validates DNSKEY
+ for ds in ds_rrset:
+ for dnskey in rrset:
+ htype = 'SHA256' if ds.digest_type == 2 else 'SHA1'
+ good_ds = dns.dnssec.make_ds(name, dnskey, htype)
+ if ds == good_ds:
+ break
+ else:
+ continue
+ break
+ else:
+ raise Exception("DS does not match DNSKEY")
+ # set key for next iteration
+ keys = {name: rrset}
+ # get TXT record (signed by zone)
+ rrset = check_query(ns, url, _type, keys)
+ return rrset
+
+
+def query(url, rtype):
+ # 8.8.8.8 is Google's public DNS server
+ nameservers = ['8.8.8.8']
+ ns = nameservers[0]
+ try:
+ out = get_and_validate(ns, url, rtype)
+ validated = True
+ except BaseException as e:
+ #traceback.print_exc(file=sys.stderr)
+ print_error("DNSSEC error:", str(e))
+ resolver = dns.resolver.get_default_resolver()
+ out = resolver.query(url, rtype)
+ validated = False
+ return out, validated
diff --git a/electrum/ecc.py b/electrum/ecc.py
new file mode 100644
index 000000000..b7f6c643a
--- /dev/null
+++ b/electrum/ecc.py
@@ -0,0 +1,434 @@
+# -*- coding: utf-8 -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import base64
+import hmac
+import hashlib
+from typing import Union
+
+
+import ecdsa
+from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
+from ecdsa.curves import SECP256k1
+from ecdsa.ellipticcurve import Point
+from ecdsa.util import string_to_number, number_to_string
+
+from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler
+from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
+from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1
+
+
+do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
+
+CURVE_ORDER = SECP256k1.order
+
+
+def generator():
+ return ECPubkey.from_point(generator_secp256k1)
+
+
+def point_at_infinity():
+ return ECPubkey(None)
+
+
+def sig_string_from_der_sig(der_sig, order=CURVE_ORDER):
+ r, s = ecdsa.util.sigdecode_der(der_sig, order)
+ return ecdsa.util.sigencode_string(r, s, order)
+
+
+def der_sig_from_sig_string(sig_string, order=CURVE_ORDER):
+ r, s = ecdsa.util.sigdecode_string(sig_string, order)
+ return ecdsa.util.sigencode_der_canonize(r, s, order)
+
+
+def der_sig_from_r_and_s(r, s, order=CURVE_ORDER):
+ return ecdsa.util.sigencode_der_canonize(r, s, order)
+
+
+def get_r_and_s_from_der_sig(der_sig, order=CURVE_ORDER):
+ r, s = ecdsa.util.sigdecode_der(der_sig, order)
+ return r, s
+
+
+def get_r_and_s_from_sig_string(sig_string, order=CURVE_ORDER):
+ r, s = ecdsa.util.sigdecode_string(sig_string, order)
+ return r, s
+
+
+def sig_string_from_r_and_s(r, s, order=CURVE_ORDER):
+ return ecdsa.util.sigencode_string_canonize(r, s, order)
+
+
+def point_to_ser(P, compressed=True) -> bytes:
+ if isinstance(P, tuple):
+ assert len(P) == 2, 'unexpected point: %s' % P
+ x, y = P
+ else:
+ x, y = P.x(), P.y()
+ if x is None or y is None: # infinity
+ return None
+ if compressed:
+ return bfh(('%02x' % (2+(y&1))) + ('%064x' % x))
+ return bfh('04'+('%064x' % x)+('%064x' % y))
+
+
+def get_y_coord_from_x(x, odd=True):
+ curve = curve_secp256k1
+ _p = curve.p()
+ _a = curve.a()
+ _b = curve.b()
+ for offset in range(128):
+ Mx = x + offset
+ My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
+ My = pow(My2, (_p + 1) // 4, _p)
+ if curve.contains_point(Mx, My):
+ if odd == bool(My & 1):
+ return My
+ return _p - My
+ raise Exception('ECC_YfromX: No Y found')
+
+
+def ser_to_point(ser: bytes) -> (int, int):
+ if ser[0] not in (0x02, 0x03, 0x04):
+ raise ValueError('Unexpected first byte: {}'.format(ser[0]))
+ if ser[0] == 0x04:
+ return string_to_number(ser[1:33]), string_to_number(ser[33:])
+ x = string_to_number(ser[1:])
+ return x, get_y_coord_from_x(x, ser[0] == 0x03)
+
+
+def _ser_to_python_ecdsa_point(ser: bytes) -> ecdsa.ellipticcurve.Point:
+ x, y = ser_to_point(ser)
+ try:
+ return Point(curve_secp256k1, x, y, CURVE_ORDER)
+ except:
+ raise InvalidECPointException()
+
+
+class InvalidECPointException(Exception):
+ """e.g. not on curve, or infinity"""
+
+
+class _MyVerifyingKey(ecdsa.VerifyingKey):
+ @classmethod
+ def from_signature(klass, sig, recid, h, curve): # TODO use libsecp??
+ """ See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """
+ from ecdsa import util, numbertheory
+ from . import msqr
+ curveFp = curve.curve
+ G = curve.generator
+ order = G.order()
+ # extract r,s from signature
+ r, s = util.sigdecode_string(sig, order)
+ # 1.1
+ x = r + (recid//2) * order
+ # 1.3
+ alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p()
+ beta = msqr.modular_sqrt(alpha, curveFp.p())
+ y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta
+ # 1.4 the constructor checks that nR is at infinity
+ try:
+ R = Point(curveFp, x, y, order)
+ except:
+ raise InvalidECPointException()
+ # 1.5 compute e from message:
+ e = string_to_number(h)
+ minus_e = -e % order
+ # 1.6 compute Q = r^-1 (sR - eG)
+ inv_r = numbertheory.inverse_mod(r,order)
+ try:
+ Q = inv_r * ( s * R + minus_e * G )
+ except:
+ raise InvalidECPointException()
+ return klass.from_public_point( Q, curve )
+
+
+class _MySigningKey(ecdsa.SigningKey):
+ """Enforce low S values in signatures"""
+
+ def sign_number(self, number, entropy=None, k=None):
+ r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k)
+ if s > CURVE_ORDER//2:
+ s = CURVE_ORDER - s
+ return r, s
+
+
+class _PubkeyForPointAtInfinity:
+ point = ecdsa.ellipticcurve.INFINITY
+
+
+class ECPubkey(object):
+
+ def __init__(self, b: bytes):
+ if b is not None:
+ assert_bytes(b)
+ point = _ser_to_python_ecdsa_point(b)
+ self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point)
+ else:
+ self._pubkey = _PubkeyForPointAtInfinity()
+
+ @classmethod
+ def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes):
+ assert_bytes(sig_string)
+ if len(sig_string) != 64:
+ raise Exception('Wrong encoding')
+ if recid < 0 or recid > 3:
+ raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid))
+ ecdsa_verifying_key = _MyVerifyingKey.from_signature(sig_string, recid, msg_hash, curve=SECP256k1)
+ ecdsa_point = ecdsa_verifying_key.pubkey.point
+ return ECPubkey.from_point(ecdsa_point)
+
+ @classmethod
+ def from_signature65(cls, sig: bytes, msg_hash: bytes):
+ if len(sig) != 65:
+ raise Exception("Wrong encoding")
+ nV = sig[0]
+ if nV < 27 or nV >= 35:
+ raise Exception("Bad encoding")
+ if nV >= 31:
+ compressed = True
+ nV -= 4
+ else:
+ compressed = False
+ recid = nV - 27
+ return cls.from_sig_string(sig[1:], recid, msg_hash), compressed
+
+ @classmethod
+ def from_point(cls, point):
+ _bytes = point_to_ser(point, compressed=False) # faster than compressed
+ return ECPubkey(_bytes)
+
+ def get_public_key_bytes(self, compressed=True):
+ if self.is_at_infinity(): raise Exception('point is at infinity')
+ return point_to_ser(self.point(), compressed)
+
+ def get_public_key_hex(self, compressed=True):
+ return bh2u(self.get_public_key_bytes(compressed))
+
+ def point(self) -> (int, int):
+ return self._pubkey.point.x(), self._pubkey.point.y()
+
+ def __mul__(self, other: int):
+ if not isinstance(other, int):
+ raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
+ ecdsa_point = self._pubkey.point * other
+ return self.from_point(ecdsa_point)
+
+ def __rmul__(self, other: int):
+ return self * other
+
+ def __add__(self, other):
+ if not isinstance(other, ECPubkey):
+ raise TypeError('addition not defined for ECPubkey and {}'.format(type(other)))
+ ecdsa_point = self._pubkey.point + other._pubkey.point
+ return self.from_point(ecdsa_point)
+
+ def __eq__(self, other):
+ return self._pubkey.point.x() == other._pubkey.point.x() \
+ and self._pubkey.point.y() == other._pubkey.point.y()
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def verify_message_for_address(self, sig65: bytes, message: bytes) -> None:
+ assert_bytes(message)
+ h = Hash(msg_magic(message))
+ public_key, compressed = self.from_signature65(sig65, h)
+ # check public key
+ if public_key != self:
+ raise Exception("Bad signature")
+ # check message
+ self.verify_message_hash(sig65[1:], h)
+
+ def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> None:
+ assert_bytes(sig_string)
+ if len(sig_string) != 64:
+ raise Exception('Wrong encoding')
+ ecdsa_point = self._pubkey.point
+ verifying_key = _MyVerifyingKey.from_public_point(ecdsa_point, curve=SECP256k1)
+ verifying_key.verify_digest(sig_string, msg_hash, sigdecode=ecdsa.util.sigdecode_string)
+
+ def encrypt_message(self, message: bytes, magic: bytes = b'BIE1'):
+ """
+ ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
+ """
+ assert_bytes(message)
+
+ randint = ecdsa.util.randrange(CURVE_ORDER)
+ ephemeral_exponent = number_to_string(randint, CURVE_ORDER)
+ ephemeral = ECPrivkey(ephemeral_exponent)
+ ecdh_key = (self * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
+ key = hashlib.sha512(ecdh_key).digest()
+ iv, key_e, key_m = key[0:16], key[16:32], key[32:]
+ ciphertext = aes_encrypt_with_iv(key_e, iv, message)
+ ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True)
+ encrypted = magic + ephemeral_pubkey + ciphertext
+ mac = hmac_oneshot(key_m, encrypted, hashlib.sha256)
+
+ return base64.b64encode(encrypted + mac)
+
+ @classmethod
+ def order(cls):
+ return CURVE_ORDER
+
+ def is_at_infinity(self):
+ return self == point_at_infinity()
+
+
+def msg_magic(message: bytes) -> bytes:
+ from .bitcoin import var_int
+ length = bfh(var_int(len(message)))
+ return b"\x18BitCore Signed Message:\n" + length + message
+
+
+def verify_message_with_address(address: str, sig65: bytes, message: bytes):
+ from .bitcoin import pubkey_to_address
+ assert_bytes(sig65, message)
+ try:
+ h = Hash(msg_magic(message))
+ public_key, compressed = ECPubkey.from_signature65(sig65, h)
+ # check public key using the address
+ pubkey_hex = public_key.get_public_key_hex(compressed)
+ for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
+ addr = pubkey_to_address(txin_type, pubkey_hex)
+ if address == addr:
+ break
+ else:
+ raise Exception("Bad signature")
+ # check message
+ public_key.verify_message_hash(sig65[1:], h)
+ return True
+ except Exception as e:
+ print_error("Verification error: {0}".format(e))
+ return False
+
+
+def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
+ if isinstance(secret, bytes):
+ secret = string_to_number(secret)
+ return 0 < secret < CURVE_ORDER
+
+
+class ECPrivkey(ECPubkey):
+
+ def __init__(self, privkey_bytes: bytes):
+ assert_bytes(privkey_bytes)
+ if len(privkey_bytes) != 32:
+ raise Exception('unexpected size for secret. should be 32 bytes, not {}'.format(len(privkey_bytes)))
+ secret = string_to_number(privkey_bytes)
+ if not is_secret_within_curve_range(secret):
+ raise InvalidECPointException('Invalid secret scalar (not within curve order)')
+ self.secret_scalar = secret
+
+ point = generator_secp256k1 * secret
+ super().__init__(point_to_ser(point))
+ self._privkey = ecdsa.ecdsa.Private_key(self._pubkey, secret)
+
+ @classmethod
+ def from_secret_scalar(cls, secret_scalar: int):
+ secret_bytes = number_to_string(secret_scalar, CURVE_ORDER)
+ return ECPrivkey(secret_bytes)
+
+ @classmethod
+ def from_arbitrary_size_secret(cls, privkey_bytes: bytes):
+ """This method is only for legacy reasons. Do not introduce new code that uses it.
+ Unlike the default constructor, this method does not require len(privkey_bytes) == 32,
+ and the secret does not need to be within the curve order either.
+ """
+ return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes))
+
+ @classmethod
+ def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes:
+ scalar = string_to_number(privkey_bytes) % CURVE_ORDER
+ if scalar == 0:
+ raise Exception('invalid EC private key scalar: zero')
+ privkey_32bytes = number_to_string(scalar, CURVE_ORDER)
+ return privkey_32bytes
+
+ def sign(self, data: bytes, sigencode=None, sigdecode=None) -> bytes:
+ if sigencode is None:
+ sigencode = sig_string_from_r_and_s
+ if sigdecode is None:
+ sigdecode = get_r_and_s_from_sig_string
+ private_key = _MySigningKey.from_secret_exponent(self.secret_scalar, curve=SECP256k1)
+ sig = private_key.sign_digest_deterministic(data, hashfunc=hashlib.sha256, sigencode=sigencode)
+ public_key = private_key.get_verifying_key()
+ if not public_key.verify_digest(sig, data, sigdecode=sigdecode):
+ raise Exception('Sanity check verifying our own signature failed.')
+ return sig
+
+ def sign_transaction(self, hashed_preimage: bytes) -> bytes:
+ return self.sign(hashed_preimage,
+ sigencode=der_sig_from_r_and_s,
+ sigdecode=get_r_and_s_from_der_sig)
+
+ def sign_message(self, message: bytes, is_compressed: bool) -> bytes:
+ def bruteforce_recid(sig_string):
+ for recid in range(4):
+ sig65 = construct_sig65(sig_string, recid, is_compressed)
+ try:
+ self.verify_message_for_address(sig65, message)
+ return sig65, recid
+ except Exception as e:
+ continue
+ else:
+ raise Exception("error: cannot sign message. no recid fits..")
+
+ message = to_bytes(message, 'utf8')
+ msg_hash = Hash(msg_magic(message))
+ sig_string = self.sign(msg_hash,
+ sigencode=sig_string_from_r_and_s,
+ sigdecode=get_r_and_s_from_sig_string)
+ sig65, recid = bruteforce_recid(sig_string)
+ return sig65
+
+ def decrypt_message(self, encrypted, magic=b'BIE1'):
+ encrypted = base64.b64decode(encrypted)
+ if len(encrypted) < 85:
+ raise Exception('invalid ciphertext: length')
+ magic_found = encrypted[:4]
+ ephemeral_pubkey_bytes = encrypted[4:37]
+ ciphertext = encrypted[37:-32]
+ mac = encrypted[-32:]
+ if magic_found != magic:
+ raise Exception('invalid ciphertext: invalid magic bytes')
+ try:
+ ecdsa_point = _ser_to_python_ecdsa_point(ephemeral_pubkey_bytes)
+ except AssertionError as e:
+ raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
+ if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ecdsa_point.x(), ecdsa_point.y()):
+ raise Exception('invalid ciphertext: invalid ephemeral pubkey')
+ ephemeral_pubkey = ECPubkey.from_point(ecdsa_point)
+ ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True)
+ key = hashlib.sha512(ecdh_key).digest()
+ iv, key_e, key_m = key[0:16], key[16:32], key[32:]
+ if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256):
+ raise InvalidPassword()
+ return aes_decrypt_with_iv(key_e, iv, ciphertext)
+
+
+def construct_sig65(sig_string, recid, is_compressed):
+ comp = 4 if is_compressed else 0
+ return bytes([27 + recid + comp]) + sig_string
diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py
new file mode 100644
index 000000000..10ed30096
--- /dev/null
+++ b/electrum/ecc_fast.py
@@ -0,0 +1,223 @@
+# taken (with minor modifications) from pycoin
+# https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py
+
+import os
+import sys
+import traceback
+import ctypes
+from ctypes.util import find_library
+from ctypes import (
+ byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER
+)
+
+import ecdsa
+
+from .util import print_stderr, print_error
+
+
+SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
+SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
+SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
+# /** The higher bits contain the actual data. Do not use directly. */
+SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
+SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
+SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
+
+# /** Flags to pass to secp256k1_context_create. */
+SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
+SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
+SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT)
+
+SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
+SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
+
+
+def load_library():
+ if sys.platform == 'darwin':
+ library_path = 'libsecp256k1.0.dylib'
+ elif sys.platform in ('windows', 'win32'):
+ library_path = 'libsecp256k1.dll'
+ elif 'ANDROID_DATA' in os.environ:
+ library_path = 'libsecp256k1.so'
+ else:
+ library_path = 'libsecp256k1.so.0'
+
+ secp256k1 = ctypes.cdll.LoadLibrary(library_path)
+ if not secp256k1:
+ print_stderr('[ecc] warning: libsecp256k1 library failed to load')
+ return None
+
+ try:
+ secp256k1.secp256k1_context_create.argtypes = [c_uint]
+ secp256k1.secp256k1_context_create.restype = c_void_p
+
+ secp256k1.secp256k1_context_randomize.argtypes = [c_void_p, c_char_p]
+ secp256k1.secp256k1_context_randomize.restype = c_int
+
+ secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p]
+ secp256k1.secp256k1_ec_pubkey_create.restype = c_int
+
+ secp256k1.secp256k1_ecdsa_sign.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_void_p, c_void_p]
+ secp256k1.secp256k1_ecdsa_sign.restype = c_int
+
+ secp256k1.secp256k1_ecdsa_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
+ secp256k1.secp256k1_ecdsa_verify.restype = c_int
+
+ secp256k1.secp256k1_ec_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t]
+ secp256k1.secp256k1_ec_pubkey_parse.restype = c_int
+
+ secp256k1.secp256k1_ec_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p, c_uint]
+ secp256k1.secp256k1_ec_pubkey_serialize.restype = c_int
+
+ secp256k1.secp256k1_ecdsa_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p]
+ secp256k1.secp256k1_ecdsa_signature_parse_compact.restype = c_int
+
+ secp256k1.secp256k1_ecdsa_signature_normalize.argtypes = [c_void_p, c_char_p, c_char_p]
+ secp256k1.secp256k1_ecdsa_signature_normalize.restype = c_int
+
+ secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p]
+ secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int
+
+ secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
+ secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int
+
+ secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)
+ r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
+ if r:
+ return secp256k1
+ else:
+ print_stderr('[ecc] warning: secp256k1_context_randomize failed')
+ return None
+ except (OSError, AttributeError):
+ #traceback.print_exc(file=sys.stderr)
+ print_stderr('[ecc] warning: libsecp256k1 library was found and loaded but there was an error when using it')
+ return None
+
+
+class _patched_functions:
+ prepared_to_patch = False
+ monkey_patching_active = False
+
+
+def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
+ if not _libsecp256k1:
+ return
+
+ # save original functions so that we can undo patching (needed for tests)
+ _patched_functions.orig_sign = staticmethod(ecdsa.ecdsa.Private_key.sign)
+ _patched_functions.orig_verify = staticmethod(ecdsa.ecdsa.Public_key.verifies)
+ _patched_functions.orig_mul = staticmethod(ecdsa.ellipticcurve.Point.__mul__)
+
+ curve_secp256k1 = ecdsa.ecdsa.curve_secp256k1
+ curve_order = ecdsa.curves.SECP256k1.order
+ point_at_infinity = ecdsa.ellipticcurve.INFINITY
+
+ def mul(self: ecdsa.ellipticcurve.Point, other: int):
+ if self.curve() != curve_secp256k1:
+ # this operation is not on the secp256k1 curve; use original implementation
+ return _patched_functions.orig_mul(self, other)
+ other %= curve_order
+ if self == point_at_infinity or other == 0:
+ return point_at_infinity
+ pubkey = create_string_buffer(64)
+ public_pair_bytes = b'\4' + self.x().to_bytes(32, byteorder="big") + self.y().to_bytes(32, byteorder="big")
+ r = _libsecp256k1.secp256k1_ec_pubkey_parse(
+ _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
+ if not r:
+ return False
+ r = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big"))
+ if not r:
+ return point_at_infinity
+
+ pubkey_serialized = create_string_buffer(65)
+ pubkey_size = c_size_t(65)
+ _libsecp256k1.secp256k1_ec_pubkey_serialize(
+ _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED)
+ x = int.from_bytes(pubkey_serialized[1:33], byteorder="big")
+ y = int.from_bytes(pubkey_serialized[33:], byteorder="big")
+ return ecdsa.ellipticcurve.Point(curve_secp256k1, x, y, curve_order)
+
+ def sign(self: ecdsa.ecdsa.Private_key, hash: int, random_k: int):
+ # note: random_k is ignored
+ if self.public_key.curve != curve_secp256k1:
+ # this operation is not on the secp256k1 curve; use original implementation
+ return _patched_functions.orig_sign(self, hash, random_k)
+ secret_exponent = self.secret_multiplier
+ nonce_function = None
+ sig = create_string_buffer(64)
+ sig_hash_bytes = hash.to_bytes(32, byteorder="big")
+ _libsecp256k1.secp256k1_ecdsa_sign(
+ _libsecp256k1.ctx, sig, sig_hash_bytes, secret_exponent.to_bytes(32, byteorder="big"), nonce_function, None)
+ compact_signature = create_string_buffer(64)
+ _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
+ r = int.from_bytes(compact_signature[:32], byteorder="big")
+ s = int.from_bytes(compact_signature[32:], byteorder="big")
+ return ecdsa.ecdsa.Signature(r, s)
+
+ def verify(self: ecdsa.ecdsa.Public_key, hash: int, signature: ecdsa.ecdsa.Signature):
+ if self.curve != curve_secp256k1:
+ # this operation is not on the secp256k1 curve; use original implementation
+ return _patched_functions.orig_verify(self, hash, signature)
+ sig = create_string_buffer(64)
+ input64 = signature.r.to_bytes(32, byteorder="big") + signature.s.to_bytes(32, byteorder="big")
+ r = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, input64)
+ if not r:
+ return False
+ r = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
+
+ public_pair_bytes = b'\4' + self.point.x().to_bytes(32, byteorder="big") + self.point.y().to_bytes(32, byteorder="big")
+ pubkey = create_string_buffer(64)
+ r = _libsecp256k1.secp256k1_ec_pubkey_parse(
+ _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
+ if not r:
+ return False
+
+ return 1 == _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, hash.to_bytes(32, byteorder="big"), pubkey)
+
+ # save new functions so that we can (re-)do patching
+ _patched_functions.fast_sign = sign
+ _patched_functions.fast_verify = verify
+ _patched_functions.fast_mul = mul
+
+ _patched_functions.prepared_to_patch = True
+
+
+def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
+ if not _libsecp256k1:
+ # FIXME print_error will always print as 'verbosity' is not yet initialised
+ print_error('[ecc] info: libsecp256k1 library not available, falling back to python-ecdsa. '
+ 'This means signing operations will be slower.')
+ return
+ if not _patched_functions.prepared_to_patch:
+ raise Exception("can't patch python-ecdsa without preparations")
+ ecdsa.ecdsa.Private_key.sign = _patched_functions.fast_sign
+ ecdsa.ecdsa.Public_key.verifies = _patched_functions.fast_verify
+ ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.fast_mul
+ # ecdsa.ellipticcurve.Point.__add__ = ... # TODO??
+
+ _patched_functions.monkey_patching_active = True
+
+
+def undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
+ if not _libsecp256k1:
+ return
+ if not _patched_functions.prepared_to_patch:
+ raise Exception("can't patch python-ecdsa without preparations")
+ ecdsa.ecdsa.Private_key.sign = _patched_functions.orig_sign
+ ecdsa.ecdsa.Public_key.verifies = _patched_functions.orig_verify
+ ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.orig_mul
+
+ _patched_functions.monkey_patching_active = False
+
+
+def is_using_fast_ecc():
+ return _patched_functions.monkey_patching_active
+
+
+try:
+ _libsecp256k1 = load_library()
+except:
+ _libsecp256k1 = None
+ #traceback.print_exc(file=sys.stderr)
+
+_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
diff --git a/electrum/electrum b/electrum/electrum
new file mode 120000
index 000000000..74bf81ab6
--- /dev/null
+++ b/electrum/electrum
@@ -0,0 +1 @@
+../run_electrum
\ No newline at end of file
diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py
new file mode 100644
index 000000000..9c2eb6ed9
--- /dev/null
+++ b/electrum/exchange_rate.py
@@ -0,0 +1,578 @@
+from datetime import datetime
+import inspect
+import requests
+import sys
+import os
+import json
+from threading import Thread
+import time
+import csv
+import decimal
+from decimal import Decimal
+
+from .bitcoin import COIN
+from .i18n import _
+from .util import PrintError, ThreadJob, make_dir
+
+
+# See https://en.wikipedia.org/wiki/ISO_4217
+CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
+ 'CVE': 0, 'DJF': 0, 'GNF': 0, 'IQD': 3, 'ISK': 0,
+ 'JOD': 3, 'JPY': 0, 'KMF': 0, 'KRW': 0, 'KWD': 3,
+ 'LYD': 3, 'MGA': 1, 'MRO': 1, 'OMR': 3, 'PYG': 0,
+ 'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0,
+ 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}
+
+
+class ExchangeBase(PrintError):
+
+ def __init__(self, on_quotes, on_history):
+ self.history = {}
+ self.quotes = {}
+ self.on_quotes = on_quotes
+ self.on_history = on_history
+
+ def get_json(self, site, get_string):
+ # APIs must have https
+ url = ''.join(['https://', site, get_string])
+ response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}, timeout=10)
+ return response.json()
+
+ def get_csv(self, site, get_string):
+ url = ''.join(['https://', site, get_string])
+ response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})
+ reader = csv.DictReader(response.content.decode().split('\n'))
+ return list(reader)
+
+ def name(self):
+ return self.__class__.__name__
+
+ def update_safe(self, ccy):
+ try:
+ self.print_error("getting fx quotes for", ccy)
+ self.quotes = self.get_rates(ccy)
+ self.print_error("received fx quotes")
+ except BaseException as e:
+ self.print_error("failed fx quotes:", e)
+ self.on_quotes()
+
+ def update(self, ccy):
+ t = Thread(target=self.update_safe, args=(ccy,))
+ t.setDaemon(True)
+ t.start()
+
+ def read_historical_rates(self, ccy, cache_dir):
+ filename = os.path.join(cache_dir, self.name() + '_'+ ccy)
+ if os.path.exists(filename):
+ timestamp = os.stat(filename).st_mtime
+ try:
+ with open(filename, 'r', encoding='utf-8') as f:
+ h = json.loads(f.read())
+ h['timestamp'] = timestamp
+ except:
+ h = None
+ else:
+ h = None
+ if h:
+ self.history[ccy] = h
+ self.on_history()
+ return h
+
+ def get_historical_rates_safe(self, ccy, cache_dir):
+ try:
+ self.print_error("requesting fx history for", ccy)
+ h = self.request_history(ccy)
+ self.print_error("received fx history for", ccy)
+ except BaseException as e:
+ self.print_error("failed fx history:", e)
+ return
+ filename = os.path.join(cache_dir, self.name() + '_' + ccy)
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.write(json.dumps(h))
+ h['timestamp'] = time.time()
+ self.history[ccy] = h
+ self.on_history()
+
+ def get_historical_rates(self, ccy, cache_dir):
+ if ccy not in self.history_ccys():
+ return
+ h = self.history.get(ccy)
+ if h is None:
+ h = self.read_historical_rates(ccy, cache_dir)
+ if h is None or h['timestamp'] < time.time() - 24*3600:
+ t = Thread(target=self.get_historical_rates_safe, args=(ccy, cache_dir))
+ t.setDaemon(True)
+ t.start()
+
+ def history_ccys(self):
+ return []
+
+ def historical_rate(self, ccy, d_t):
+ return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN')
+
+ def get_currencies(self):
+ rates = self.get_rates('')
+ return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a)==3])
+
+class BitcoinAverage(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
+ return dict([(r.replace("BTC", ""), Decimal(json[r]['last']))
+ for r in json if r != 'timestamp'])
+
+ def history_ccys(self):
+ return ['AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'EUR', 'GBP', 'IDR', 'ILS',
+ 'MXN', 'NOK', 'NZD', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'USD',
+ 'ZAR']
+
+ def request_history(self, ccy):
+ history = self.get_csv('apiv2.bitcoinaverage.com',
+ "/indices/global/history/BTC%s?period=alltime&format=csv" % ccy)
+ return dict([(h['DateTime'][:10], h['Average'])
+ for h in history])
+
+
+class Bitcointoyou(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('bitcointoyou.com', "/API/ticker.aspx")
+ return {'BRL': Decimal(json['ticker']['last'])}
+
+ def history_ccys(self):
+ return ['BRL']
+
+
+class BitcoinVenezuela(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.bitcoinvenezuela.com', '/')
+ rates = [(r, json['BTC'][r]) for r in json['BTC']
+ if json['BTC'][r] is not None] # Giving NULL for LTC
+ return dict(rates)
+
+ def history_ccys(self):
+ return ['ARS', 'EUR', 'USD', 'VEF']
+
+ def request_history(self, ccy):
+ return self.get_json('api.bitcoinvenezuela.com',
+ "/historical/index.php?coin=BTC")[ccy +'_BTC']
+
+
+class Bitbank(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
+ return {'JPY': Decimal(json['data']['last'])}
+
+
+class BitFlyer(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('bitflyer.jp', '/api/echo/price')
+ return {'JPY': Decimal(json['mid'])}
+
+
+class Bitmarket(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('www.bitmarket.pl', '/json/BTCPLN/ticker.json')
+ return {'PLN': Decimal(json['last'])}
+
+
+class BitPay(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('bitpay.com', '/api/rates')
+ return dict([(r['code'], Decimal(r['rate'])) for r in json])
+
+
+class Bitso(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.bitso.com', '/v2/ticker')
+ return {'MXN': Decimal(json['last'])}
+
+
+class BitStamp(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('www.bitstamp.net', '/api/ticker/')
+ return {'USD': Decimal(json['last'])}
+
+
+class Bitvalor(ExchangeBase):
+
+ def get_rates(self,ccy):
+ json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
+ return {'BRL': Decimal(json['ticker_1h']['total']['last'])}
+
+
+class BlockchainInfo(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('blockchain.info', '/ticker')
+ return dict([(r, Decimal(json[r]['15m'])) for r in json])
+
+
+class BTCChina(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('data.btcchina.com', '/data/ticker')
+ return {'CNY': Decimal(json['ticker']['last'])}
+
+
+class BTCParalelo(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('btcparalelo.com', '/api/price')
+ return {'VEF': Decimal(json['price'])}
+
+
+class Coinbase(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('coinbase.com',
+ '/api/v1/currencies/exchange_rates')
+ return dict([(r[7:].upper(), Decimal(json[r]))
+ for r in json if r.startswith('btc_to_')])
+
+
+class CoinDesk(ExchangeBase):
+
+ def get_currencies(self):
+ dicts = self.get_json('api.coindesk.com',
+ '/v1/bpi/supported-currencies.json')
+ return [d['currency'] for d in dicts]
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.coindesk.com',
+ '/v1/bpi/currentprice/%s.json' % ccy)
+ result = {ccy: Decimal(json['bpi'][ccy]['rate_float'])}
+ return result
+
+ def history_starts(self):
+ return { 'USD': '2012-11-30', 'EUR': '2013-09-01' }
+
+ def history_ccys(self):
+ return self.history_starts().keys()
+
+ def request_history(self, ccy):
+ start = self.history_starts()[ccy]
+ end = datetime.today().strftime('%Y-%m-%d')
+ # Note ?currency and ?index don't work as documented. Sigh.
+ query = ('/v1/bpi/historical/close.json?start=%s&end=%s'
+ % (start, end))
+ json = self.get_json('api.coindesk.com', query)
+ return json['bpi']
+
+
+class Coinsecure(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
+ return {'INR': Decimal(json['lastprice'] / 100.0 )}
+
+
+class Foxbit(ExchangeBase):
+
+ def get_rates(self,ccy):
+ json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
+ return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])}
+
+
+class itBit(ExchangeBase):
+
+ def get_rates(self, ccy):
+ ccys = ['USD', 'EUR', 'SGD']
+ json = self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
+ result = dict.fromkeys(ccys)
+ if ccy in ccys:
+ result[ccy] = Decimal(json['lastPrice'])
+ return result
+
+
+class Kraken(ExchangeBase):
+
+ def get_rates(self, ccy):
+ ccys = ['EUR', 'USD', 'CAD', 'GBP', 'JPY']
+ pairs = ['XBT%s' % c for c in ccys]
+ json = self.get_json('api.kraken.com',
+ '/0/public/Ticker?pair=%s' % ','.join(pairs))
+ return dict((k[-3:], Decimal(float(v['c'][0])))
+ for k, v in json['result'].items())
+
+
+class LocalBitcoins(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('localbitcoins.com',
+ '/bitcoinaverage/ticker-all-currencies/')
+ return dict([(r, Decimal(json[r]['rates']['last'])) for r in json])
+
+
+class MercadoBitcoin(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
+ return {'BRL': Decimal(json['ticker_1h']['exchanges']['MBT']['last'])}
+
+
+class NegocieCoins(ExchangeBase):
+
+ def get_rates(self,ccy):
+ json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
+ return {'BRL': Decimal(json['ticker_1h']['exchanges']['NEG']['last'])}
+
+class TheRockTrading(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('api.therocktrading.com',
+ '/v1/funds/BTCEUR/ticker')
+ return {'EUR': Decimal(json['last'])}
+
+class Unocoin(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('www.unocoin.com', 'trade?buy')
+ return {'INR': Decimal(json)}
+
+
+class WEX(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json_eur = self.get_json('wex.nz', '/api/3/ticker/btc_eur')
+ json_rub = self.get_json('wex.nz', '/api/3/ticker/btc_rur')
+ json_usd = self.get_json('wex.nz', '/api/3/ticker/btc_usd')
+ return {'EUR': Decimal(json_eur['btc_eur']['last']),
+ 'RUB': Decimal(json_rub['btc_rur']['last']),
+ 'USD': Decimal(json_usd['btc_usd']['last'])}
+
+class CoinMarketCap(ExchangeBase):
+ def get_rates(self, ccy):
+ json = self.get_json("api.coinmarketcap.com", "/v1/ticker/bitcore/?convert=" + ccy)
+ return {ccy: Decimal(json[0]["price_" + ccy.lower()])}
+
+
+class Winkdex(ExchangeBase):
+
+ def get_rates(self, ccy):
+ json = self.get_json('winkdex.com', '/api/v0/price')
+ return {'USD': Decimal(json['price'] / 100.0)}
+
+ def history_ccys(self):
+ return ['USD']
+
+ def request_history(self, ccy):
+ json = self.get_json('winkdex.com',
+ "/api/v0/series?start_time=1342915200")
+ history = json['series'][0]['results']
+ return dict([(h['timestamp'][:10], h['price'] / 100.0)
+ for h in history])
+
+
+class Zaif(ExchangeBase):
+ def get_rates(self, ccy):
+ json = self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
+ return {'JPY': Decimal(json['last_price'])}
+
+
+def dictinvert(d):
+ inv = {}
+ for k, vlist in d.items():
+ for v in vlist:
+ keys = inv.setdefault(v, [])
+ keys.append(k)
+ return inv
+
+def get_exchanges_and_currencies():
+ import os, json
+ path = os.path.join(os.path.dirname(__file__), 'currencies.json')
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.loads(f.read())
+ except:
+ pass
+ d = {}
+ is_exchange = lambda obj: (inspect.isclass(obj)
+ and issubclass(obj, ExchangeBase)
+ and obj != ExchangeBase)
+ exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange))
+ for name, klass in exchanges.items():
+ exchange = klass(None, None)
+ try:
+ d[name] = exchange.get_currencies()
+ print(name, "ok")
+ except:
+ print(name, "error")
+ continue
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(json.dumps(d, indent=4, sort_keys=True))
+ return d
+
+
+CURRENCIES = get_exchanges_and_currencies()
+
+
+def get_exchanges_by_ccy(history=True):
+ if not history:
+ return dictinvert(CURRENCIES)
+ d = {}
+ exchanges = CURRENCIES.keys()
+ for name in exchanges:
+ klass = globals()[name]
+ exchange = klass(None, None)
+ d[name] = exchange.history_ccys()
+ return dictinvert(d)
+
+
+class FxThread(ThreadJob):
+
+ def __init__(self, config, network):
+ self.config = config
+ self.network = network
+ self.ccy = self.get_currency()
+ self.history_used_spot = False
+ self.ccy_combo = None
+ self.hist_checkbox = None
+ self.cache_dir = os.path.join(config.path, 'cache')
+ self.set_exchange(self.config_exchange())
+ make_dir(self.cache_dir)
+
+ def get_currencies(self, h):
+ d = get_exchanges_by_ccy(h)
+ return sorted(d.keys())
+
+ def get_exchanges_by_ccy(self, ccy, h):
+ d = get_exchanges_by_ccy(h)
+ return d.get(ccy, [])
+
+ def ccy_amount_str(self, amount, commas):
+ prec = CCY_PRECISIONS.get(self.ccy, 2)
+ fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
+ try:
+ rounded_amount = round(amount, prec)
+ except decimal.InvalidOperation:
+ rounded_amount = amount
+ return fmt_str.format(rounded_amount)
+
+ def run(self):
+ # This runs from the plugins thread which catches exceptions
+ if self.is_enabled():
+ if self.timeout ==0 and self.show_history():
+ self.exchange.get_historical_rates(self.ccy, self.cache_dir)
+ if self.timeout <= time.time():
+ self.timeout = time.time() + 150
+ self.exchange.update(self.ccy)
+
+ def is_enabled(self):
+ return bool(self.config.get('use_exchange_rate'))
+
+ def set_enabled(self, b):
+ return self.config.set_key('use_exchange_rate', bool(b))
+
+ def get_history_config(self):
+ return bool(self.config.get('history_rates'))
+
+ def set_history_config(self, b):
+ self.config.set_key('history_rates', bool(b))
+
+ def get_history_capital_gains_config(self):
+ return bool(self.config.get('history_rates_capital_gains', False))
+
+ def set_history_capital_gains_config(self, b):
+ self.config.set_key('history_rates_capital_gains', bool(b))
+
+ def get_fiat_address_config(self):
+ return bool(self.config.get('fiat_address'))
+
+ def set_fiat_address_config(self, b):
+ self.config.set_key('fiat_address', bool(b))
+
+ def get_currency(self):
+ '''Use when dynamic fetching is needed'''
+ return self.config.get("currency", "EUR")
+
+ def config_exchange(self):
+ return self.config.get('use_exchange', 'BitcoinAverage')
+
+ def show_history(self):
+ return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
+
+ def set_currency(self, ccy):
+ self.ccy = ccy
+ self.config.set_key('currency', ccy, True)
+ self.timeout = 0 # Because self.ccy changes
+ self.on_quotes()
+
+ def set_exchange(self, name):
+ class_ = globals().get(name, BitcoinAverage)
+ self.print_error("using exchange", name)
+ if self.config_exchange() != name:
+ self.config.set_key('use_exchange', name, True)
+ self.exchange = class_(self.on_quotes, self.on_history)
+ # A new exchange means new fx quotes, initially empty. Force
+ # a quote refresh
+ self.timeout = 0
+ self.exchange.read_historical_rates(self.ccy, self.cache_dir)
+
+ def on_quotes(self):
+ if self.network:
+ self.network.trigger_callback('on_quotes')
+
+ def on_history(self):
+ if self.network:
+ self.network.trigger_callback('on_history')
+
+ def exchange_rate(self):
+ '''Returns None, or the exchange rate as a Decimal'''
+ rate = self.exchange.quotes.get(self.ccy)
+ if rate is None:
+ return Decimal('NaN')
+ return Decimal(rate)
+
+ def format_amount(self, btc_balance):
+ rate = self.exchange_rate()
+ return '' if rate.is_nan() else "%s" % self.value_str(btc_balance, rate)
+
+ def format_amount_and_units(self, btc_balance):
+ rate = self.exchange_rate()
+ return '' if rate.is_nan() else "%s %s" % (self.value_str(btc_balance, rate), self.ccy)
+
+ def get_fiat_status_text(self, btc_balance, base_unit, decimal_point):
+ rate = self.exchange_rate()
+ return _(" (No FX rate available)") if rate.is_nan() else " 1 %s~%s %s" % (base_unit,
+ self.value_str(COIN / (10**(8 - decimal_point)), rate), self.ccy)
+
+ def fiat_value(self, satoshis, rate):
+ return Decimal('NaN') if satoshis is None else Decimal(satoshis) / COIN * Decimal(rate)
+
+ def value_str(self, satoshis, rate):
+ return self.format_fiat(self.fiat_value(satoshis, rate))
+
+ def format_fiat(self, value):
+ if value.is_nan():
+ return _("No data")
+ return "%s" % (self.ccy_amount_str(value, True))
+
+ def history_rate(self, d_t):
+ if d_t is None:
+ return Decimal('NaN')
+ rate = self.exchange.historical_rate(self.ccy, d_t)
+ # Frequently there is no rate for today, until tomorrow :)
+ # Use spot quotes in that case
+ if rate == 'NaN' and (datetime.today().date() - d_t.date()).days <= 2:
+ rate = self.exchange.quotes.get(self.ccy, 'NaN')
+ self.history_used_spot = True
+ return Decimal(rate)
+
+ def historical_value_str(self, satoshis, d_t):
+ return self.format_fiat(self.historical_value(satoshis, d_t))
+
+ def historical_value(self, satoshis, d_t):
+ return self.fiat_value(satoshis, self.history_rate(d_t))
+
+ def timestamp_rate(self, timestamp):
+ from .util import timestamp_to_datetime
+ date = timestamp_to_datetime(timestamp)
+ return self.history_rate(date)
diff --git a/electrum/gui/__init__.py b/electrum/gui/__init__.py
new file mode 100644
index 000000000..9974520ac
--- /dev/null
+++ b/electrum/gui/__init__.py
@@ -0,0 +1,5 @@
+# To create a new GUI, please add its code to this directory.
+# Three objects are passed to the ElectrumGui: config, daemon and plugins
+# The Wallet object is instanciated by the GUI
+
+# Notifications about network events are sent to the GUI by using network.register_callback()
diff --git a/electrum/gui/kivy/Makefile b/electrum/gui/kivy/Makefile
new file mode 100644
index 000000000..7e87afa6b
--- /dev/null
+++ b/electrum/gui/kivy/Makefile
@@ -0,0 +1,32 @@
+PYTHON = python3
+
+# needs kivy installed or in PYTHONPATH
+
+.PHONY: theming apk clean
+
+theming:
+ $(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png
+prepare:
+ # running pre build setup
+ @cp tools/buildozer.spec ../../../buildozer.spec
+ # copy electrum to main.py
+ @cp ../../../run_electrum ../../../main.py
+ @-if [ ! -d "../../.buildozer" ];then \
+ cd ../../..; buildozer android debug;\
+ cp -f electrum/gui/kivy/tools/blacklist.txt .buildozer/android/platform/python-for-android/src/blacklist.txt;\
+ rm -rf ./.buildozer/android/platform/python-for-android/dist;\
+ fi
+apk:
+ @make prepare
+ @-cd ../../..; buildozer android debug deploy run
+ @make clean
+release:
+ @make prepare
+ @-cd ../../..; buildozer android release
+ @make clean
+clean:
+ # Cleaning up
+ # rename main.py to electrum
+ @-rm ../../../main.py
+ # remove buildozer.spec
+ @-rm ../../../buildozer.spec
diff --git a/electrum/gui/kivy/Readme.md b/electrum/gui/kivy/Readme.md
new file mode 100644
index 000000000..4c151accc
--- /dev/null
+++ b/electrum/gui/kivy/Readme.md
@@ -0,0 +1,130 @@
+# Kivy GUI
+
+The Kivy GUI is used with Electrum on Android devices. To generate an APK file, follow these instructions.
+
+## 1. Preliminaries
+
+Make sure the current user can write `/opt` (e.g. `sudo chown username: /opt`).
+
+We assume that you already got Electrum to run from source on this machine,
+hence have e.g. `git`, `python3-pip` and `python3-setuptools`.
+
+## 2. Install kivy
+
+Install kivy for python3 as described [here](https://kivy.org/docs/installation/installation-linux.html).
+So for example:
+```sh
+sudo add-apt-repository ppa:kivy-team/kivy
+sudo apt-get install python3-kivy
+```
+
+
+## 3. Install python-for-android (p4a)
+p4a is used to package Electrum, Python, SDL and a bootstrap Java app into an APK file.
+We patched p4a to add some functionality we need for Electrum. Until those changes are
+merged into p4a, you need to merge them locally (into the master branch):
+
+3.1 [kivy/python-for-android#1217](https://github.com/kivy/python-for-android/pull/1217)
+
+Something like this should work:
+
+```sh
+cd /opt
+git clone https://github.com/kivy/python-for-android
+cd python-for-android
+git remote add agilewalker https://github.com/agilewalker/python-for-android
+git remote add sombernight https://github.com/SomberNight/python-for-android
+git fetch --all
+git checkout 93759f36ba45c7bbe0456a4b3e6788622924cbac
+git cherry-pick a2fb5ecbc09c4847adbcfd03c6b1ca62b3d09b8d # openssl-fix
+git cherry-pick a0ef2007bc60ed642fbd8b61937995dbed0ddd24 # disable backups
+```
+
+## 4. Install buildozer
+4.1 Buildozer is a frontend to p4a. Luckily we don't need to patch it:
+
+```sh
+cd /opt
+git clone https://github.com/kivy/buildozer
+cd buildozer
+sudo python3 setup.py install
+```
+
+4.2 Install additional dependencies:
+```sh
+sudo apt-get install python-pip
+```
+and the ones listed
+[here](https://buildozer.readthedocs.io/en/latest/installation.html#targeting-android).
+
+You will also need
+```sh
+python3 -m pip install colorama appdirs sh jinja2
+```
+
+
+4.3 Download the [Crystax NDK](https://www.crystax.net/en/download) manually.
+Extract into `/opt/crystax-ndk-10.3.2`
+
+
+## 5. Create the UI Atlas
+In the `gui/kivy` directory of Electrum, run `make theming`.
+
+## 6. Download Electrum dependencies
+```sh
+sudo contrib/make_packages
+```
+
+## 7. Try building the APK and fail
+
+```sh
+contrib/make_apk
+```
+
+During this build attempt, buildozer downloaded some tools,
+e.g. those needed in the next step.
+
+## 8. Update the Android SDK build tools
+
+### Method 1: Using the GUI
+
+ Start the Android SDK manager in GUI mode:
+
+ ~/.buildozer/android/platform/android-sdk-20/tools/android
+
+ Check the latest SDK available and install it
+ ("Android SDK Tools" and "Android SDK Platform-tools").
+ Close the SDK manager. Repeat until there is no newer version.
+
+ Reopen the SDK manager, and install the latest build tools
+ ("Android SDK Build-tools"), 27.0.3 at the time of writing.
+
+ Install "Android Support Repository" from the SDK manager (under "Extras").
+
+### Method 2: Using the command line:
+
+ Repeat the following command until there is nothing to install:
+
+ ~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t tools,platform-tools
+
+ Install Build Tools, android API 19 and Android Support Library:
+
+ ~/.buildozer/android/platform/android-sdk-20/tools/android update sdk -u -t build-tools-27.0.3,android-19,extra-android-m2repository
+
+
+## 9. Build the APK
+
+```sh
+contrib/make_apk
+```
+
+# FAQ
+## Why do I get errors like `package me.dm7.barcodescanner.zxing does not exist` while compiling?
+Update your Android build tools to version 27 like described above.
+
+## Why do I get errors like `(use -source 7 or higher to enable multi-catch statement)` while compiling?
+Make sure that your p4a installation includes commit a3cc78a6d1a107cd3b6bd28db8b80f89e3ecddd2.
+Also make sure you have recent SDK tools and platform-tools
+
+## I changed something but I don't see any differences on the phone. What did I do wrong?
+You probably need to clear the cache: `rm -rf .buildozer/android/platform/build/{build,dists}`
diff --git a/electrum/gui/kivy/__init__.py b/electrum/gui/kivy/__init__.py
new file mode 100644
index 000000000..7b9941281
--- /dev/null
+++ b/electrum/gui/kivy/__init__.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# Kivy GUI
+
+import sys
+import os
+
+try:
+ sys.argv = ['']
+ import kivy
+except ImportError:
+ # This error ideally shouldn't be raised with pre-built packages
+ sys.exit("Error: Could not import kivy. Please install it using the" + \
+ "instructions mentioned here `http://kivy.org/#download` .")
+
+# minimum required version for kivy
+kivy.require('1.8.0')
+from kivy.logger import Logger
+
+
+
+
+class ElectrumGui:
+
+ def __init__(self, config, daemon, plugins):
+ Logger.debug('ElectrumGUI: initialising')
+ self.daemon = daemon
+ self.network = daemon.network
+ self.config = config
+ self.plugins = plugins
+
+ def main(self):
+ from .main_window import ElectrumWindow
+ self.config.open_last_wallet()
+ w = ElectrumWindow(config=self.config,
+ network=self.network,
+ plugins = self.plugins,
+ gui_object=self)
+ w.run()
diff --git a/electrum/gui/kivy/data/background.png b/electrum/gui/kivy/data/background.png
new file mode 100644
index 000000000..77f42ccdd
Binary files /dev/null and b/electrum/gui/kivy/data/background.png differ
diff --git a/electrum/gui/kivy/data/fonts/Roboto-Bold.ttf b/electrum/gui/kivy/data/fonts/Roboto-Bold.ttf
new file mode 100644
index 000000000..87d3af3e1
Binary files /dev/null and b/electrum/gui/kivy/data/fonts/Roboto-Bold.ttf differ
diff --git a/electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf b/electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf
new file mode 100644
index 000000000..c38f7c881
Binary files /dev/null and b/electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf differ
diff --git a/electrum/gui/kivy/data/fonts/Roboto-Medium.ttf b/electrum/gui/kivy/data/fonts/Roboto-Medium.ttf
new file mode 100644
index 000000000..879834198
Binary files /dev/null and b/electrum/gui/kivy/data/fonts/Roboto-Medium.ttf differ
diff --git a/electrum/gui/kivy/data/fonts/Roboto.ttf b/electrum/gui/kivy/data/fonts/Roboto.ttf
new file mode 100644
index 000000000..153c60882
Binary files /dev/null and b/electrum/gui/kivy/data/fonts/Roboto.ttf differ
diff --git a/electrum/gui/kivy/data/fonts/tron/License.txt b/electrum/gui/kivy/data/fonts/tron/License.txt
new file mode 100644
index 000000000..fbc86ed93
--- /dev/null
+++ b/electrum/gui/kivy/data/fonts/tron/License.txt
@@ -0,0 +1,4 @@
+Copyright (c) 2010-2011, Jeff Bell [www.randombell.com] | [jeffbell@randombell.com].
+This font may be distributed freely however must retain this document as well as the Readme.txt file.
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is available with a FAQ at: http://scripts.sil.org/OFL
\ No newline at end of file
diff --git a/electrum/gui/kivy/data/fonts/tron/Readme.txt b/electrum/gui/kivy/data/fonts/tron/Readme.txt
new file mode 100644
index 000000000..2e2fb69f6
--- /dev/null
+++ b/electrum/gui/kivy/data/fonts/tron/Readme.txt
@@ -0,0 +1,21 @@
+TR2N v1.3
+
+ABOUT THE FONT:
+A font based upon the poster text for TRON LEGACY.
+
+The 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.
+
+UPDATE HISTORY:
+3/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.
+
+1/22/11 - Made minor corrections to all previous letters and punctuation. Corrected issue with number 8's top filling in.
+
+ABOUT THE AUTHOR:
+Jeff 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".
+
+For more information on Jeff Bell and his work can be found online:
+
+www.randombell.com
+www.damovieman.deviantart.com
+http://www.imdb.com/name/nm3983081/
+http://www.vimeo.com/user4004969/videos
\ No newline at end of file
diff --git a/electrum/gui/kivy/data/fonts/tron/Tr2n.ttf b/electrum/gui/kivy/data/fonts/tron/Tr2n.ttf
new file mode 100644
index 000000000..8e8c0dec6
Binary files /dev/null and b/electrum/gui/kivy/data/fonts/tron/Tr2n.ttf differ
diff --git a/electrum/gui/kivy/data/glsl/default.fs b/electrum/gui/kivy/data/glsl/default.fs
new file mode 100644
index 000000000..19145d653
--- /dev/null
+++ b/electrum/gui/kivy/data/glsl/default.fs
@@ -0,0 +1,4 @@
+$HEADER$
+void main (void){
+ gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
+}
diff --git a/electrum/gui/kivy/data/glsl/default.png b/electrum/gui/kivy/data/glsl/default.png
new file mode 100644
index 000000000..a14255e4d
Binary files /dev/null and b/electrum/gui/kivy/data/glsl/default.png differ
diff --git a/electrum/gui/kivy/data/glsl/default.vs b/electrum/gui/kivy/data/glsl/default.vs
new file mode 100644
index 000000000..ac9ac4d6d
--- /dev/null
+++ b/electrum/gui/kivy/data/glsl/default.vs
@@ -0,0 +1,6 @@
+$HEADER$
+void main (void) {
+ frag_color = color * vec4(1.0, 1.0, 1.0, opacity);
+ tex_coord0 = vTexCoords0;
+ gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
+}
diff --git a/electrum/gui/kivy/data/glsl/header.fs b/electrum/gui/kivy/data/glsl/header.fs
new file mode 100644
index 000000000..e9f887ba6
--- /dev/null
+++ b/electrum/gui/kivy/data/glsl/header.fs
@@ -0,0 +1,10 @@
+#ifdef GL_ES
+ precision highp float;
+#endif
+
+/* Outputs from the vertex shader */
+varying vec4 frag_color;
+varying vec2 tex_coord0;
+
+/* uniform texture samplers */
+uniform sampler2D texture0;
diff --git a/electrum/gui/kivy/data/glsl/header.vs b/electrum/gui/kivy/data/glsl/header.vs
new file mode 100644
index 000000000..a2638bffc
--- /dev/null
+++ b/electrum/gui/kivy/data/glsl/header.vs
@@ -0,0 +1,17 @@
+#ifdef GL_ES
+ precision highp float;
+#endif
+
+/* Outputs to the fragment shader */
+varying vec4 frag_color;
+varying vec2 tex_coord0;
+
+/* vertex attributes */
+attribute vec2 vPosition;
+attribute vec2 vTexCoords0;
+
+/* uniform variables */
+uniform mat4 modelview_mat;
+uniform mat4 projection_mat;
+uniform vec4 color;
+uniform float opacity;
diff --git a/electrum/gui/kivy/data/images/defaulttheme-0.png b/electrum/gui/kivy/data/images/defaulttheme-0.png
new file mode 100644
index 000000000..8cfc82455
Binary files /dev/null and b/electrum/gui/kivy/data/images/defaulttheme-0.png differ
diff --git a/electrum/gui/kivy/data/images/defaulttheme.atlas b/electrum/gui/kivy/data/images/defaulttheme.atlas
new file mode 100644
index 000000000..26ed5d0da
--- /dev/null
+++ b/electrum/gui/kivy/data/images/defaulttheme.atlas
@@ -0,0 +1 @@
+{"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]}}
\ No newline at end of file
diff --git a/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java b/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java
new file mode 100644
index 000000000..8f4714628
--- /dev/null
+++ b/electrum/gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java
@@ -0,0 +1,48 @@
+package org.electrum.qr;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.content.Intent;
+
+import java.util.Arrays;
+
+import me.dm7.barcodescanner.zxing.ZXingScannerView;
+
+import com.google.zxing.Result;
+import com.google.zxing.BarcodeFormat;
+
+public class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
+ private ZXingScannerView mScannerView;
+ final String TAG = "org.electrum.SimpleScannerActivity";
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+ mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
+ mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
+ setContentView(mScannerView); // Set the scanner view as the content view
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
+ mScannerView.startCamera(); // Start camera on resume
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mScannerView.stopCamera(); // Stop camera on pause
+ }
+
+ @Override
+ public void handleResult(Result rawResult) {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra("text", rawResult.getText());
+ resultIntent.putExtra("format", rawResult.getBarcodeFormat().toString());
+ setResult(Activity.RESULT_OK, resultIntent);
+ this.finish();
+ }
+}
diff --git a/electrum/gui/kivy/data/logo/kivy-icon-32.png b/electrum/gui/kivy/data/logo/kivy-icon-32.png
new file mode 100644
index 000000000..455fa9763
Binary files /dev/null and b/electrum/gui/kivy/data/logo/kivy-icon-32.png differ
diff --git a/electrum/gui/kivy/data/style.kv b/electrum/gui/kivy/data/style.kv
new file mode 100644
index 000000000..1bbc60093
--- /dev/null
+++ b/electrum/gui/kivy/data/style.kv
@@ -0,0 +1,754 @@
+#:kivy 1.0
+
+:
+ canvas:
+ Color:
+ rgba: self.disabled_color if self.disabled else (self.color if not self.markup else (1, 1, 1, 1))
+ Rectangle:
+ texture: self.texture
+ size: self.texture_size
+ pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
+
+<-Button,-ToggleButton>:
+ state_image: self.background_normal if self.state == 'normal' else self.background_down
+ disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
+ canvas:
+ Color:
+ rgba: self.background_color
+ BorderImage:
+ border: self.border
+ pos: self.pos
+ size: self.size
+ source: self.disabled_image if self.disabled else self.state_image
+ Color:
+ rgba: self.disabled_color if self.disabled else self.color
+ Rectangle:
+ texture: self.texture
+ size: self.texture_size
+ pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
+
+
+ opacity: .7 if self.disabled else 1
+ rows: 1
+ canvas:
+ Color:
+ rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)
+ BorderImage:
+ border: self.parent.border if self.parent else (16, 16, 16, 16)
+ texture: root.parent._bk_img.texture if root.parent else None
+ size: self.size
+ pos: self.pos
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/bubble_btn'
+ background_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'
+ background_disabled_normal: 'atlas://data/images/defaulttheme/bubble_btn'
+ background_disabled_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'
+ border: (0, 0, 0, 0)
+
+:
+ canvas:
+ Color:
+ rgb: 1, 1, 1
+ BorderImage:
+ border: (0, 18, 0, 18) if self.orientation == 'horizontal' else (18, 0, 18, 0)
+ pos: (self.x + self.padding, self.center_y - sp(18)) if self.orientation == 'horizontal' else (self.center_x - 18, self.y + self.padding)
+ size: (self.width - self.padding * 2, sp(36)) if self.orientation == 'horizontal' else (sp(36), self.height - self.padding * 2)
+ source: 'atlas://data/images/defaulttheme/slider{}_background{}'.format(self.orientation[0], '_disabled' if self.disabled else '')
+ Rectangle:
+ 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))
+ size: (sp(32), sp(32))
+ source: 'atlas://data/images/defaulttheme/slider_cursor{}'.format('_disabled' if self.disabled else '')
+
+:
+ canvas.before:
+ PushMatrix
+ Translate:
+ xy: self.pos
+ canvas.after:
+ PopMatrix
+
+:
+ canvas:
+ Color:
+ rgba: self.color
+ Rectangle:
+ texture: self.texture
+ size: self.norm_image_size
+ pos: self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.
+
+
+ rows: 1
+ padding: 3
+ canvas:
+ Color:
+ rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)
+ BorderImage:
+ border: self.parent.border if self.parent else (16, 16, 16, 16)
+ source: (root.parent.background_disabled_image if self.disabled else root.parent.background_image) if root.parent else None
+ size: self.size
+ pos: self.pos
+
+
+ rows: 1
+
+
+ padding: '2dp', '2dp', '2dp', '2dp'
+ canvas.before:
+ BorderImage:
+ pos: self.pos
+ size: self.size
+ border: root.border
+ source: root.background_image
+
+:
+ halign: 'center'
+ valign: 'middle'
+ background_normal: 'atlas://data/images/defaulttheme/tab_btn'
+ background_disabled_normal: 'atlas://data/images/defaulttheme/tab_btn_disabled'
+ background_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'
+ background_disabled_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'
+ border: (8, 8, 8, 8)
+ font_size: '15sp'
+
+
+ allow_stretch: True
+
+:
+ canvas.before:
+ Color:
+ rgba: self.background_color
+ BorderImage:
+ border: self.border
+ pos: self.pos
+ size: self.size
+ 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)
+ Color:
+ rgba: (self.cursor_color if self.focus and not self.cursor_blink else (0, 0, 0, 0))
+ Rectangle:
+ pos: [int(x) for x in self.cursor_pos]
+ size: 1, -self.line_height
+ Color:
+ 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)
+
+:
+ but_cut: cut.__self__
+ but_copy: copy.__self__
+ but_paste: paste.__self__
+ but_selectall: selectall.__self__
+
+ size_hint: None, None
+ size: '150sp', '50sp'
+ BubbleButton:
+ id: cut
+ text: 'Cut'
+ on_release: root.do('cut')
+ BubbleButton:
+ id: copy
+ text: 'Copy'
+ on_release: root.do('copy')
+ BubbleButton:
+ id: paste
+ text: 'Paste'
+ on_release: root.do('paste')
+ BubbleButton:
+ id: selectall
+ text: 'Select All'
+ on_release: root.do('selectall')
+
+:
+ font_name: 'data/fonts/RobotoMono-Regular.ttf'
+
+
+:
+ canvas.before:
+ Color:
+ rgba: self.color_selected if self.is_selected else self.odd_color if self.odd else self.even_color
+ Rectangle:
+ pos: [self.parent.x, self.y] if self.parent else [0, 0]
+ size: [self.parent.width, self.height] if self.parent else [1, 1]
+ Color:
+ rgba: 1, 1, 1, int(not self.is_leaf)
+ Rectangle:
+ source: 'atlas://data/images/defaulttheme/tree_%s' % ('opened' if self.is_open else 'closed')
+ size: 16, 16
+ pos: self.x - 20, self.center_y - 8
+ canvas.after:
+ Color:
+ rgba: .5, .5, .5, .2
+ Line:
+ points: [self.parent.x, self.y, self.parent.right, self.y] if self.parent else []
+
+
+:
+ width: self.texture_size[0]
+ height: max(self.texture_size[1] + dp(10), dp(24))
+ text_size: self.width, None
+
+
+:
+ canvas.before:
+ StencilPush
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ StencilUse
+
+ canvas.after:
+ StencilUnUse
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ StencilPop
+
+
+:
+ on_entry_added: treeview.add_node(args[1])
+ on_entries_cleared: treeview.root.nodes = []
+ on_subentry_to_entry: not args[2].locked and treeview.add_node(args[1], args[2])
+ on_remove_subentry: args[2].nodes = []
+ BoxLayout:
+ pos: root.pos
+ size: root.size
+ size_hint: None, None
+ orientation: 'vertical'
+ BoxLayout:
+ size_hint_y: None
+ height: 30
+ orientation: 'horizontal'
+ Widget:
+ # Just for spacing
+ width: 10
+ size_hint_x: None
+ Label:
+ text: 'Name'
+ text_size: self.size
+ halign: 'left'
+ bold: True
+ Label:
+ text: 'Size'
+ text_size: self.size
+ size_hint_x: None
+ halign: 'right'
+ bold: True
+ Widget:
+ # Just for spacing
+ width: 10
+ size_hint_x: None
+ ScrollView:
+ id: scrollview
+ do_scroll_x: False
+ Scatter:
+ do_rotation: False
+ do_scale: False
+ do_translation: False
+ size: treeview.size
+ size_hint_y: None
+ TreeView:
+ id: treeview
+ hide_root: True
+ size_hint_y: None
+ width: scrollview.width
+ height: self.minimum_height
+ on_node_expand: root.controller.entry_subselect(args[1])
+ on_node_collapse: root.controller.close_subselection(args[1])
+
+:
+ layout: layout
+ FileChooserListLayout:
+ id: layout
+ controller: root
+
+[FileListEntry@FloatLayout+TreeViewNode]:
+ locked: False
+ entries: []
+ path: ctx.path
+ # FIXME: is_selected is actually a read_only treeview property. In this
+ # case, however, we're doing this because treeview only has single-selection
+ # hardcoded in it. The fix to this would be to update treeview to allow
+ # multiple selection.
+ is_selected: self.path in ctx.controller().selection
+
+ orientation: 'horizontal'
+ size_hint_y: None
+ height: '48dp' if dp(1) > 1 else '24dp'
+ # Don't allow expansion of the ../ node
+ is_leaf: not ctx.isdir or ctx.name.endswith('..' + ctx.sep) or self.locked
+ on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
+ on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
+ BoxLayout:
+ pos: root.pos
+ Label:
+ id: filename
+ text_size: self.width, None
+ halign: 'left'
+ shorten: True
+ text: ctx.name
+ Label:
+ text_size: self.width, None
+ size_hint_x: None
+ halign: 'right'
+ text: '{}'.format(ctx.get_nice_size())
+
+
+:
+ on_entry_added: stacklayout.add_widget(args[1])
+ on_entries_cleared: stacklayout.clear_widgets()
+ ScrollView:
+ id: scrollview
+ pos: root.pos
+ size: root.size
+ size_hint: None, None
+ do_scroll_x: False
+ Scatter:
+ do_rotation: False
+ do_scale: False
+ do_translation: False
+ size_hint_y: None
+ height: stacklayout.height
+ StackLayout:
+ id: stacklayout
+ width: scrollview.width
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: '10dp'
+ padding: '10dp'
+
+:
+ layout: layout
+ FileChooserIconLayout:
+ id: layout
+ controller: root
+
+[FileIconEntry@Widget]:
+ locked: False
+ path: ctx.path
+ selected: self.path in ctx.controller().selection
+ size_hint: None, None
+
+ on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])
+ on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])
+ size: '100dp', '100dp'
+
+ canvas:
+ Color:
+ rgba: 1, 1, 1, 1 if self.selected else 0
+ BorderImage:
+ border: 8, 8, 8, 8
+ pos: root.pos
+ size: root.size
+ source: 'atlas://data/images/defaulttheme/filechooser_selected'
+
+ Image:
+ size: '48dp', '48dp'
+ source: 'atlas://data/images/defaulttheme/filechooser_%s' % ('folder' if ctx.isdir else 'file')
+ pos: root.x + dp(24), root.y + dp(40)
+ Label:
+ text: ctx.name
+ text_size: (root.width, self.height)
+ halign: 'center'
+ shorten: True
+ size: '100dp', '16dp'
+ pos: root.x, root.y + dp(16)
+
+ Label:
+ text: '{}'.format(ctx.get_nice_size())
+ font_size: '11sp'
+ color: .8, .8, .8, 1
+ size: '100dp', '16sp'
+ pos: root.pos
+ halign: 'center'
+
+:
+ pos_hint: {'x': 0, 'y': 0}
+ canvas:
+ Color:
+ rgba: 0, 0, 0, .8
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ Label:
+ pos_hint: {'x': .2, 'y': .6}
+ size_hint: .6, .2
+ text: 'Opening %s' % root.path
+ FloatLayout:
+ pos_hint: {'x': .2, 'y': .4}
+ size_hint: .6, .2
+ ProgressBar:
+ id: pb
+ pos_hint: {'x': 0, 'center_y': .5}
+ max: root.total
+ value: root.index
+ Label:
+ pos_hint: {'x': 0}
+ text: '%d / %d' % (root.index, root.total)
+ size_hint_y: None
+ height: self.texture_size[1]
+ y: pb.center_y - self.height - 8
+ font_size: '13sp'
+ color: (.8, .8, .8, .8)
+
+ AnchorLayout:
+ pos_hint: {'x': .2, 'y': .2}
+ size_hint: .6, .2
+
+ Button:
+ text: 'Cancel'
+ size_hint: None, None
+ size: 150, 44
+ on_release: root.cancel()
+
+
+
+# Switch widget
+:
+ active_norm_pos: max(0., min(1., (int(self.active) + self.touch_distance / sp(41))))
+ canvas:
+ Color:
+ rgb: 1, 1, 1
+ Rectangle:
+ source: 'atlas://data/images/defaulttheme/switch-background{}'.format('_disabled' if self.disabled else '')
+ size: sp(83), sp(32)
+ pos: int(self.center_x - sp(41)), int(self.center_y - sp(16))
+ Rectangle:
+ source: 'atlas://data/images/defaulttheme/switch-button{}'.format('_disabled' if self.disabled else '')
+ size: sp(43), sp(32)
+ pos: int(self.center_x - sp(41) + self.active_norm_pos * sp(41)), int(self.center_y - sp(16))
+
+
+# ModalView widget
+:
+ canvas:
+ Color:
+ rgba: root.background_color[:3] + [root.background_color[-1] * self._anim_alpha]
+ Rectangle:
+ size: self._window.size if self._window else (0, 0)
+
+ Color:
+ rgb: 1, 1, 1
+ BorderImage:
+ source: root.background
+ border: root.border
+ pos: self.pos
+ size: self.size
+
+
+# Popup widget
+:
+ _container: container
+ GridLayout:
+ padding: '12dp'
+ cols: 1
+ size_hint: None, None
+ pos: root.pos
+ size: root.size
+
+ Label:
+ text: root.title
+ color: root.title_color
+ size_hint_y: None
+ height: self.texture_size[1] + dp(16)
+ text_size: self.width - dp(16), None
+ font_size: root.title_size
+ font_name: root.title_font
+ halign: root.title_align
+
+ Widget:
+ size_hint_y: None
+ height: dp(4)
+ canvas:
+ Color:
+ rgba: root.separator_color
+ Rectangle:
+ pos: self.x, self.y + root.separator_height / 2.
+ size: self.width, root.separator_height
+
+ BoxLayout:
+ id: container
+
+# =============================================================================
+# Spinner widget
+# =============================================================================
+
+:
+ size_hint_y: None
+ height: '48dp'
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/spinner'
+ background_disabled_normal: 'atlas://data/images/defaulttheme/spinner_disabled'
+ background_down: 'atlas://data/images/defaulttheme/spinner_pressed'
+
+# =============================================================================
+# ActionBar widget
+# =============================================================================
+
+:
+ height: '48dp'
+ size_hint_y: None
+ spacing: '4dp'
+ canvas:
+ Color:
+ rgba: self.background_color
+ BorderImage:
+ border: root.border
+ pos: self.pos
+ size: self.size
+ source: self.background_image
+
+:
+ orientation: 'horizontal'
+ canvas:
+ Color:
+ rgba: self.background_color
+ BorderImage:
+ pos: self.pos
+ size: self.size
+ source: self.background_image
+
+:
+ size_hint_x: None
+ minimum_width: '2sp'
+ width: self.minimum_width
+ canvas:
+ Rectangle:
+ pos: self.x, self.y + sp(4)
+ size: self.width, self.height - sp(8)
+ source: self.background_image
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/' + ('action_bar' if self.inside_group else 'action_item')
+ background_down: 'atlas://data/images/defaulttheme/action_item_down'
+ size_hint_x: None if not root.inside_group else 1
+ 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]
+ color: self.color[:3] + [0 if (root.icon and not root.inside_group) else 1]
+
+ Image:
+ allow_stretch: True
+ opacity: 1 if (root.icon and not root.inside_group) else 0
+ source: root.icon
+ mipmap: root.mipmap
+ pos: root.x + dp(4), root.y + dp(4)
+ size: root.width - dp(8), root.height - sp(8)
+
+:
+ size_hint_x: None if not root.inside_group else 1
+ width: self.texture_size[0] + dp(32)
+
+:
+ size_hint_x: None
+ width: self.texture_size[0] + dp(32)
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/action_bar' if self.inside_group else 'atlas://data/images/defaulttheme/action_item'
+
+:
+ temp_width: 0
+ temp_height: 0
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/action_item'
+ background_down: 'atlas://data/images/defaulttheme/action_item_down'
+
+:
+ size_hint_x: 1
+ minimum_width: layout.minimum_width + min(sp(100), title.width)
+ important: True
+ GridLayout:
+ id: layout
+ rows: 1
+ pos: root.pos
+ size_hint_x: None
+ width: self.minimum_width
+ ActionPreviousButton:
+ on_press: root.dispatch('on_press')
+ on_release: root.dispatch('on_release')
+ size_hint_x: None
+ width: prevlayout.width
+ GridLayout:
+ id: prevlayout
+ rows: 1
+ width: self.minimum_width
+ height: self.parent.height
+ pos: self.parent.pos
+ ActionPreviousImage:
+ id: prev_icon_image
+ source: root.previous_image
+ opacity: 1 if root.with_previous else 0
+ allow_stretch: True
+ size_hint_x: None
+ temp_width: root.previous_image_width or dp(prev_icon_image.texture_size[0])
+ temp_height: root.previous_image_height or dp(prev_icon_image.texture_size[1])
+ width:
+ (self.temp_width if self.temp_height <= self.height else \
+ self.temp_width * (self.height / self.temp_height)) \
+ if self.texture else dp(8)
+ mipmap: root.mipmap
+ ActionPreviousImage:
+ id: app_icon_image
+ source: root.app_icon
+ allow_stretch: True
+ size_hint_x: None
+ temp_width: root.app_icon_width or dp(app_icon_image.texture_size[0])
+ temp_height: root.app_icon_height or dp(app_icon_image.texture_size[1])
+ width:
+ (self.temp_width if self.temp_height <= self.height else \
+ self.temp_width * (self.height / self.temp_height)) \
+ if self.texture else dp(8)
+ mipmap: root.mipmap
+ Widget:
+ size_hint_x: None
+ width: '5sp'
+ Label:
+ id: title
+ text: root.title
+ text_size: self.size
+ color: root.color
+ shorten: True
+ shorten_from: 'right'
+ halign: 'left'
+ valign: 'middle'
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/action_group'
+ background_down: 'atlas://data/images/defaulttheme/action_group_down'
+ background_disabled_normal: 'atlas://data/images/defaulttheme/action_group_disabled'
+ border: 30, 30, 3, 3
+ ActionSeparator:
+ pos: root.pos
+ size: root.separator_width, root.height
+ opacity: 1 if root.use_separator else 0
+ background_image: root.separator_image if root.use_separator else 'action_view'
+
+:
+ border: 3, 3, 3, 3
+ background_normal: 'atlas://data/images/defaulttheme/action_item'
+ background_down: 'atlas://data/images/defaulttheme/action_item_down'
+ background_disabled_normal: 'atlas://data/images/defaulttheme/button_disabled'
+ size_hint_x: None
+ minimum_width: '48sp'
+ width: self.texture_size[0] if self.texture else self.minimum_width
+ canvas.after:
+ Color:
+ rgb: 1, 1, 1
+ Rectangle:
+ pos: root.center_x - sp(16), root.center_y - sp(16)
+ size: sp(32), sp(32)
+ source: root.overflow_image
+
+:
+ auto_width: False
+
+
+# =============================================================================
+# Accordion widget
+# =============================================================================
+
+[AccordionItemTitle@Label]:
+ text: ctx.title
+ normal_background: ctx.item.background_normal if ctx.item.collapse else ctx.item.background_selected
+ disabled_background: ctx.item.background_disabled_normal if ctx.item.collapse else ctx.item.background_disabled_selected
+ canvas.before:
+ Color:
+ rgba: self.disabled_color if self.disabled else self.color
+ BorderImage:
+ source: self.disabled_background if self.disabled else self.normal_background
+ pos: self.pos
+ size: self.size
+ PushMatrix
+ Translate:
+ xy: self.center_x, self.center_y
+ Rotate:
+ angle: 90 if ctx.item.orientation == 'horizontal' else 0
+ axis: 0, 0, 1
+ Translate:
+ xy: -self.center_x, -self.center_y
+ canvas.after:
+ PopMatrix
+
+
+:
+ container: container
+ container_title: container_title
+
+ BoxLayout:
+ orientation: root.orientation
+ pos: root.pos
+ BoxLayout:
+ size_hint_x: None if root.orientation == 'horizontal' else 1
+ size_hint_y: None if root.orientation == 'vertical' else 1
+ width: root.min_space if root.orientation == 'horizontal' else 100
+ height: root.min_space if root.orientation == 'vertical' else 100
+ id: container_title
+
+ StencilView:
+ id: sv
+
+ BoxLayout:
+ id: container
+ pos: sv.pos
+ size: root.content_size
+
+
+:
+ _handle_y_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]
+ _handle_y_size: min(self.bar_width, self.width), self.height * self.vbar[1]
+ _handle_x_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)
+ _handle_x_size: self.width * self.hbar[1], min(self.bar_width, self.height)
+ canvas.after:
+ Color:
+ rgba: self._bar_color if (self.do_scroll_y and self.viewport_size[1] > self.height) else [0, 0, 0, 0]
+ Rectangle:
+ pos: root._handle_y_pos or (0, 0)
+ size: root._handle_y_size or (0, 0)
+ Color:
+ rgba: self._bar_color if (self.do_scroll_x and self.viewport_size[0] > self.width) else [0, 0, 0, 0]
+ Rectangle:
+ pos: root._handle_x_pos or (0, 0)
+ size: root._handle_x_size or (0, 0)
+
+
+:
+ _checkbox_state_image:
+ self.background_checkbox_down \
+ if self.active else self.background_checkbox_normal
+ _checkbox_disabled_image:
+ self.background_checkbox_disabled_down \
+ if self.active else self.background_checkbox_disabled_normal
+ _radio_state_image:
+ self.background_radio_down \
+ if self.active else self.background_radio_normal
+ _radio_disabled_image:
+ self.background_radio_disabled_down \
+ if self.active else self.background_radio_disabled_normal
+ _checkbox_image:
+ self._checkbox_disabled_image \
+ if self.disabled else self._checkbox_state_image
+ _radio_image:
+ self._radio_disabled_image \
+ if self.disabled else self._radio_state_image
+ canvas:
+ Color:
+ rgb: 1, 1, 1
+ Rectangle:
+ source: self._radio_image if self.group else self._checkbox_image
+ size: sp(32), sp(32)
+ pos: int(self.center_x - sp(16)), int(self.center_y - sp(16))
+
+# =============================================================================
+# Screen Manager
+# =============================================================================
+
+:
+ canvas.before:
+ StencilPush
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ StencilUse
+ canvas.after:
+ StencilUnUse
+ Rectangle:
+ pos: self.pos
+ size: self.size
+ StencilPop
diff --git a/electrum/gui/kivy/i18n.py b/electrum/gui/kivy/i18n.py
new file mode 100644
index 000000000..733249d3e
--- /dev/null
+++ b/electrum/gui/kivy/i18n.py
@@ -0,0 +1,46 @@
+import gettext
+
+
+class _(str):
+
+ observers = set()
+ lang = None
+
+ def __new__(cls, s):
+ if _.lang is None:
+ _.switch_lang('en')
+ t = _.translate(s)
+ o = super(_, cls).__new__(cls, t)
+ o.source_text = s
+ return o
+
+ @staticmethod
+ def translate(s, *args, **kwargs):
+ return _.lang(s)
+
+ @staticmethod
+ def bind(label):
+ try:
+ _.observers.add(label)
+ except:
+ pass
+ # garbage collection
+ new = set()
+ for label in _.observers:
+ try:
+ new.add(label)
+ except:
+ pass
+ _.observers = new
+
+ @staticmethod
+ def switch_lang(lang):
+ # get the right locales directory, and instanciate a gettext
+ from electrum.i18n import LOCALE_DIR
+ locales = gettext.translation('electrum', LOCALE_DIR, languages=[lang], fallback=True)
+ _.lang = locales.gettext
+ for label in _.observers:
+ try:
+ label.text = _(label.text.source_text)
+ except:
+ pass
diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv
new file mode 100644
index 000000000..b4f05188e
--- /dev/null
+++ b/electrum/gui/kivy/main.kv
@@ -0,0 +1,464 @@
+#:import Clock kivy.clock.Clock
+#:import Window kivy.core.window.Window
+#:import Factory kivy.factory.Factory
+#:import _ electrum.gui.kivy.i18n._
+
+
+###########################
+# Global Defaults
+###########################
+
+
+ markup: True
+ font_name: 'Roboto'
+ font_size: '16sp'
+ bound: False
+ on_text: if isinstance(self.text, _) and not self.bound: self.bound=True; _.bind(self)
+
+
+ on_focus: app._focused_widget = root
+ font_size: '18sp'
+
+
+ on_parent: self.MIN_STATE_TIME = 0.1
+
+
+ font_size: '12sp'
+
+:
+ canvas.before:
+ Color:
+ rgba: 0.1, 0.1, 0.1, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+:
+ canvas.before:
+ Color:
+ rgba: 0.1, 0.1, 0.1, 1
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+
+# Custom Global Widgets
+
+
+ size_hint_y: None
+ text_size: self.width, None
+ height: self.texture_size[1]
+
+:
+ rows: 1
+ size_hint: 1, None
+ height: self.minimum_height
+
+
+
+:
+ icon: ''
+ AnchorLayout:
+ pos: self.parent.pos
+ size: self.parent.size
+ orientation: 'lr-tb'
+ Image:
+ source: self.parent.parent.icon
+ size_hint_x: None
+ size: '30dp', '30dp'
+
+
+
+#########################
+# Dialogs
+#########################
+
+ text: ''
+ value: ''
+ size_hint_y: None
+ height: max(lbl1.height, lbl2.height)
+ TopLabel
+ id: lbl1
+ text: root.text
+ pos_hint: {'top':1}
+ TopLabel
+ id: lbl2
+ text: root.value
+
+
+ address: ''
+ value: ''
+ size_hint_y: None
+ height: max(lbl1.height, lbl2.height)
+ TopLabel
+ id: lbl1
+ text: '[ref=%s]%s[/ref]'%(root.address, root.address)
+ font_size: '6pt'
+ shorten: True
+ size_hint_x: 0.65
+ on_ref_press:
+ app._clipboard.copy(root.address)
+ app.show_info(_('Address copied to clipboard') + ' ' + root.address)
+ TopLabel
+ id: lbl2
+ text: root.value
+ font_size: '6pt'
+ size_hint_x: 0.35
+ halign: 'right'
+
+
+
+ viewclass: 'OutputItem'
+ size_hint: 1, None
+ height: min(output_list_layout.minimum_height, dp(144))
+ scroll_type: ['bars', 'content']
+ bar_width: dp(15)
+ RecycleBoxLayout:
+ orientation: 'vertical'
+ default_size: None, pt(6)
+ default_size_hint: 1, None
+ size_hint: 1, None
+ height: self.minimum_height
+ id: output_list_layout
+ spacing: '10dp'
+ padding: '10dp'
+ canvas.before:
+ Color:
+ rgb: .3, .3, .3
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+
+ font_size: '6pt'
+ name: ''
+ data: ''
+ text: self.data
+ touched: False
+ padding: '10dp', '10dp'
+ on_touch_down:
+ touch = args[1]
+ if self.collide_point(*touch.pos): app.on_ref_label(self, touch)
+ else: self.touched = False
+ canvas.before:
+ Color:
+ rgb: .3, .3, .3
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+
+ data: ''
+ text: ' '.join(map(''.join, zip(*[iter(self.data)]*4))) if self.data else ''
+
+
+ size_hint: None, None
+ width: '270dp' if root.fs else min(self.width, dp(270))
+ height: self.width if self.fs else (lbl.texture_size[1] + dp(27))
+ BoxLayout:
+ padding: '5dp' if root.fs else 0
+ Widget:
+ size_hint: None, 1
+ width: '4dp' if root.fs else '2dp'
+ Image:
+ id: img
+ source: root.icon
+ mipmap: True
+ size_hint: None, 1
+ width: (root.width - dp(20)) if root.fs else (0 if not root.icon else '32dp')
+ Widget:
+ size_hint_x: None
+ width: '5dp'
+ Label:
+ id: lbl
+ markup: True
+ font_size: '12sp'
+ text: root.message
+ text_size: self.width, None
+ valign: 'middle'
+ size_hint: 1, 1
+ width: 0 if root.fs else (root.width - img.width)
+
+
+
+ item_height: dp(42)
+ foreground_color: .843, .914, .972, 1
+ cols: 1
+ padding: '12dp', 0
+ canvas.before:
+ Color:
+ rgba: 0.192, .498, 0.745, 1
+ BorderImage:
+ source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
+ size: self.size
+ pos: self.pos
+
+
+
+ item_height: dp(42)
+ item_width: dp(60)
+ foreground_color: .843, .914, .972, 1
+ cols: 1
+ canvas.before:
+ Color:
+ rgba: 0.192, .498, 0.745, 1
+ BorderImage:
+ source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
+ size: self.size
+ pos: self.pos
+
+
+ item_height: dp(42)
+ foreground_color: .843, .914, .972, 1
+ cols: 1
+ padding: '12dp', 0
+ canvas.before:
+ Color:
+ rgba: 0.192, .498, 0.745, 1
+ BorderImage:
+ source: 'atlas://electrum/gui/kivy/theming/light/card_bottom'
+ size: self.size
+ pos: self.pos
+
+
+ size_hint: 1, None
+ height: dp(1)
+ color: .909, .909, .909, 1
+ canvas:
+ Color:
+ rgba: root.color if root.color else (0, 0, 0, 0)
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+
+ size_hint: 1, None
+ height: '65dp'
+ group: 'requests'
+ padding: dp(12)
+ spacing: dp(5)
+ screen: None
+ on_release:
+ self.screen.show_menu(args[0]) if self.state == 'down' else self.screen.hide_menu()
+ canvas.before:
+ Color:
+ rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.15, 0.15, 0.17, 1)
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+:
+ background_color: 1, .585, .878, 0
+ halign: 'left'
+ text_size: (self.width-10, None)
+ size_hint: 0.5, None
+ default_text: ''
+ text: self.default_text
+ padding: '5dp', '5dp'
+ height: '40dp'
+ text_color: self.foreground_color
+ disabled_color: 1, 1, 1, 1
+ foreground_color: 1, 1, 1, 1
+ canvas.before:
+ Color:
+ rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+:
+ background_color: 1, .585, .878, 0
+ halign: 'center'
+ text_size: (self.width, None)
+ shorten: True
+ size_hint: 0.5, None
+ default_text: ''
+ text: self.default_text
+ padding: '5dp', '5dp'
+ height: '40dp'
+ text_color: self.foreground_color
+ disabled_color: 1, 1, 1, 1
+ foreground_color: 1, 1, 1, 1
+ canvas.before:
+ Color:
+ rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+:
+ size_hint: 1, None
+ height: '60dp'
+ font_size: '30dp'
+ on_release:
+ self.parent.update_amount(self.text)
+
+
+
+ padding: 0, 0, 0, 0
+
+:
+ on_parent:
+ if self.parent: self.parent.bar_width = 0
+ if self.parent: self.parent.scroll_x = 0.5
+
+
+
+ carousel: carousel
+ do_default_tab: False
+ Carousel:
+ scroll_timeout: 250
+ scroll_distance: '100dp'
+ anim_type: 'out_quart'
+ min_move: .05
+ anim_move_duration: .1
+ anim_cancel_duration: .54
+ on_index: root.on_index(*args)
+ id: carousel
+
+
+
+
+ border: 16, 0, 16, 0
+ markup: False
+ text_size: self.size
+ halign: 'center'
+ valign: 'middle'
+ bold: True
+ font_size: '12.5sp'
+ background_normal: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
+ background_down: 'atlas://electrum/gui/kivy/theming/light/tab_btn_pressed'
+
+
+:
+ font_size: '48sp'
+ color: (.6, .6, .6, 1)
+ canvas.before:
+ Color:
+ rgb: (.9, .9, .9)
+ Rectangle:
+ pos: self.x + sp(2), self.y + sp(2)
+ size: self.width - sp(4), self.height - sp(4)
+
+
+
+ orientation: 'vertical'
+ title: ''
+ description: ''
+ size_hint: 1, None
+ height: '60dp'
+ canvas.before:
+ Color:
+ rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0)
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ on_release:
+ Clock.schedule_once(self.action)
+ Widget
+ TopLabel:
+ id: title
+ text: self.parent.title
+ bold: True
+ halign: 'left'
+ TopLabel:
+ text: self.parent.description
+ color: 0.8, 0.8, 0.8, 1
+ halign: 'left'
+ Widget
+
+
+
+
+
+ TabbedCarousel:
+ id: panel
+ tab_height: '48dp'
+ tab_width: panel.width/3
+ strip_border: 0, 0, 0, 0
+ SendScreen:
+ id: send_screen
+ tab: send_tab
+ HistoryScreen:
+ id: history_screen
+ tab: history_tab
+ ReceiveScreen:
+ id: receive_screen
+ tab: receive_tab
+ CleanHeader:
+ id: send_tab
+ text: _('Send')
+ slide: 0
+ CleanHeader:
+ id: history_tab
+ text: _('Balance')
+ slide: 1
+ CleanHeader:
+ id: receive_tab
+ text: _('Receive')
+ slide: 2
+
+
+
+ #on_release:
+ # fixme: the following line was commented out because it does not seem to do what it is intended
+ # Clock.schedule_once(lambda dt: self.parent.parent.dismiss() if self.parent else None, 0.05)
+ on_press:
+ Clock.schedule_once(lambda dt: app.popup_dialog(self.name), 0.05)
+ self.state = 'normal'
+
+
+BoxLayout:
+ orientation: 'vertical'
+
+ canvas.before:
+ Color:
+ rgb: .6, .6, .6
+ Rectangle:
+ size: self.size
+ source: 'electrum/gui/kivy/data/background.png'
+
+ ActionBar:
+
+ ActionView:
+ id: av
+ ActionPrevious:
+ app_icon: 'atlas://electrum/gui/kivy/theming/light/logo'
+ app_icon_width: '100dp'
+ with_previous: False
+ size_hint_x: None
+ on_release: app.popup_dialog('network')
+
+ ActionButton:
+ id: action_status
+ important: True
+ size_hint: 1, 1
+ bold: True
+ color: 0.7, 0.7, 0.7, 1
+ text: app.status
+ font_size: '22dp'
+ #minimum_width: '1dp'
+ on_release: app.popup_dialog('status')
+
+ ActionOverflow:
+ id: ao
+ ActionOvrButton:
+ name: 'about'
+ text: _('About')
+ ActionOvrButton:
+ name: 'wallets'
+ text: _('Wallets')
+ ActionOvrButton:
+ name: 'network'
+ text: _('Network')
+ ActionOvrButton:
+ name: 'settings'
+ text: _('Settings')
+ on_parent:
+ # when widget overflow drop down is shown, adjust the width
+ parent = args[1]
+ if parent: ao._dropdown.width = sp(200)
+ ScreenManager:
+ id: manager
+ ScreenTabs:
+ id: tabs
diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
new file mode 100644
index 000000000..ba07660f0
--- /dev/null
+++ b/electrum/gui/kivy/main_window.py
@@ -0,0 +1,1041 @@
+import re
+import os
+import sys
+import time
+import datetime
+import traceback
+from decimal import Decimal
+import threading
+
+from electrum.bitcoin import TYPE_ADDRESS
+from electrum.storage import WalletStorage
+from electrum.wallet import Wallet
+from electrum.paymentrequest import InvoiceStore
+from electrum.util import profiler, InvalidPassword
+from electrum.plugin import run_hook
+from electrum.util import format_satoshis, format_satoshis_plain
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from .i18n import _
+
+from kivy.app import App
+from kivy.core.window import Window
+from kivy.logger import Logger
+from kivy.utils import platform
+from kivy.properties import (OptionProperty, AliasProperty, ObjectProperty,
+ StringProperty, ListProperty, BooleanProperty, NumericProperty)
+from kivy.cache import Cache
+from kivy.clock import Clock
+from kivy.factory import Factory
+from kivy.metrics import inch
+from kivy.lang import Builder
+
+## lazy imports for factory so that widgets can be used in kv
+#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
+#Factory.register('InfoBubble', module='electrum.gui.kivy.uix.dialogs')
+#Factory.register('OutputList', module='electrum.gui.kivy.uix.dialogs')
+#Factory.register('OutputItem', module='electrum.gui.kivy.uix.dialogs')
+
+from .uix.dialogs.installwizard import InstallWizard
+from .uix.dialogs import InfoBubble, crash_reporter
+from .uix.dialogs import OutputList, OutputItem
+from .uix.dialogs import TopLabel, RefLabel
+
+#from kivy.core.window import Window
+#Window.softinput_mode = 'below_target'
+
+# delayed imports: for startup speed on android
+notification = app = ref = None
+util = False
+
+# register widget cache for keeping memory down timeout to forever to cache
+# the data
+Cache.register('electrum_widgets', timeout=0)
+
+from kivy.uix.screenmanager import Screen
+from kivy.uix.tabbedpanel import TabbedPanel
+from kivy.uix.label import Label
+from kivy.core.clipboard import Clipboard
+
+Factory.register('TabbedCarousel', module='electrum.gui.kivy.uix.screens')
+
+# Register fonts without this you won't be able to use bold/italic...
+# inside markup.
+from kivy.core.text import Label
+Label.register('Roboto',
+ 'electrum/gui/kivy/data/fonts/Roboto.ttf',
+ 'electrum/gui/kivy/data/fonts/Roboto.ttf',
+ 'electrum/gui/kivy/data/fonts/Roboto-Bold.ttf',
+ 'electrum/gui/kivy/data/fonts/Roboto-Bold.ttf')
+
+
+from electrum.util import (base_units, NoDynamicFeeEstimates, decimal_point_to_base_unit_name,
+ base_unit_name_to_decimal_point, NotEnoughFunds)
+
+
+class ElectrumWindow(App):
+
+ electrum_config = ObjectProperty(None)
+ language = StringProperty('en')
+
+ # properties might be updated by the network
+ num_blocks = NumericProperty(0)
+ num_nodes = NumericProperty(0)
+ server_host = StringProperty('')
+ server_port = StringProperty('')
+ num_chains = NumericProperty(0)
+ blockchain_name = StringProperty('')
+ fee_status = StringProperty('Fee')
+ balance = StringProperty('')
+ fiat_balance = StringProperty('')
+ is_fiat = BooleanProperty(False)
+ blockchain_forkpoint = NumericProperty(0)
+
+ auto_connect = BooleanProperty(False)
+ def on_auto_connect(self, instance, x):
+ host, port, protocol, proxy, auto_connect = self.network.get_parameters()
+ self.network.set_parameters(host, port, protocol, proxy, self.auto_connect)
+ def toggle_auto_connect(self, x):
+ self.auto_connect = not self.auto_connect
+
+ def choose_server_dialog(self, popup):
+ from .uix.dialogs.choice_dialog import ChoiceDialog
+ protocol = 's'
+ def cb2(host):
+ from electrum import constants
+ pp = servers.get(host, constants.net.DEFAULT_PORTS)
+ port = pp.get(protocol, '')
+ popup.ids.host.text = host
+ popup.ids.port.text = port
+ servers = self.network.get_servers()
+ ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open()
+
+ def choose_blockchain_dialog(self, dt):
+ from .uix.dialogs.choice_dialog import ChoiceDialog
+ chains = self.network.get_blockchains()
+ def cb(name):
+ for index, b in self.network.blockchains.items():
+ if name == b.get_name():
+ self.network.follow_chain(index)
+ names = [self.network.blockchains[b].get_name() for b in chains]
+ if len(names) > 1:
+ cur_chain = self.network.blockchain().get_name()
+ ChoiceDialog(_('Choose your chain'), names, cur_chain, cb).open()
+
+ use_rbf = BooleanProperty(False)
+ def on_use_rbf(self, instance, x):
+ self.electrum_config.set_key('use_rbf', self.use_rbf, True)
+
+ use_change = BooleanProperty(False)
+ def on_use_change(self, instance, x):
+ self.electrum_config.set_key('use_change', self.use_change, True)
+
+ use_unconfirmed = BooleanProperty(False)
+ def on_use_unconfirmed(self, instance, x):
+ self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True)
+
+ def set_URI(self, uri):
+ self.switch_to('send')
+ self.send_screen.set_URI(uri)
+
+ def on_new_intent(self, intent):
+ if intent.getScheme() != 'bitcore':
+ return
+ uri = intent.getDataString()
+ self.set_URI(uri)
+
+ def on_language(self, instance, language):
+ Logger.info('language: {}'.format(language))
+ _.switch_lang(language)
+
+ def update_history(self, *dt):
+ if self.history_screen:
+ self.history_screen.update()
+
+ def on_quotes(self, d):
+ Logger.info("on_quotes")
+ self._trigger_update_history()
+
+ def on_history(self, d):
+ Logger.info("on_history")
+ self._trigger_update_history()
+
+ def on_fee_histogram(self, *args):
+ self._trigger_update_history()
+
+ def _get_bu(self):
+ decimal_point = self.electrum_config.get('decimal_point', 5)
+ return decimal_point_to_base_unit_name(decimal_point)
+
+ def _set_bu(self, value):
+ assert value in base_units.keys()
+ decimal_point = base_unit_name_to_decimal_point(value)
+ self.electrum_config.set_key('decimal_point', decimal_point, True)
+ self._trigger_update_status()
+ self._trigger_update_history()
+
+ base_unit = AliasProperty(_get_bu, _set_bu)
+ status = StringProperty('')
+ fiat_unit = StringProperty('')
+
+ def on_fiat_unit(self, a, b):
+ self._trigger_update_history()
+
+ def decimal_point(self):
+ return base_units[self.base_unit]
+
+ def btc_to_fiat(self, amount_str):
+ if not amount_str:
+ return ''
+ if not self.fx.is_enabled():
+ return ''
+ rate = self.fx.exchange_rate()
+ if rate.is_nan():
+ return ''
+ fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
+ return "{:.2f}".format(fiat_amount).rstrip('0').rstrip('.')
+
+ def fiat_to_btc(self, fiat_amount):
+ if not fiat_amount:
+ return ''
+ rate = self.fx.exchange_rate()
+ if rate.is_nan():
+ return ''
+ satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
+ return format_satoshis_plain(satoshis, self.decimal_point())
+
+ def get_amount(self, amount_str):
+ a, u = amount_str.split()
+ assert u == self.base_unit
+ try:
+ x = Decimal(a)
+ except:
+ return None
+ p = pow(10, self.decimal_point())
+ return int(p * x)
+
+
+ _orientation = OptionProperty('landscape',
+ options=('landscape', 'portrait'))
+
+ def _get_orientation(self):
+ return self._orientation
+
+ orientation = AliasProperty(_get_orientation,
+ None,
+ bind=('_orientation',))
+ '''Tries to ascertain the kind of device the app is running on.
+ Cane be one of `tablet` or `phone`.
+
+ :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'
+ '''
+
+ _ui_mode = OptionProperty('phone', options=('tablet', 'phone'))
+
+ def _get_ui_mode(self):
+ return self._ui_mode
+
+ ui_mode = AliasProperty(_get_ui_mode,
+ None,
+ bind=('_ui_mode',))
+ '''Defines tries to ascertain the kind of device the app is running on.
+ Cane be one of `tablet` or `phone`.
+
+ :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone'
+ '''
+
+ def __init__(self, **kwargs):
+ # initialize variables
+ self._clipboard = Clipboard
+ self.info_bubble = None
+ self.nfcscanner = None
+ self.tabs = None
+ self.is_exit = False
+ self.wallet = None
+ self.pause_time = 0
+
+ App.__init__(self)#, **kwargs)
+
+ title = _('Electrum App')
+ self.electrum_config = config = kwargs.get('config', None)
+ self.language = config.get('language', 'en')
+ self.network = network = kwargs.get('network', None)
+ if self.network:
+ self.num_blocks = self.network.get_local_height()
+ self.num_nodes = len(self.network.get_interfaces())
+ host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()
+ self.server_host = host
+ self.server_port = port
+ self.auto_connect = auto_connect
+ self.proxy_config = proxy_config if proxy_config else {}
+
+ self.plugins = kwargs.get('plugins', [])
+ self.gui_object = kwargs.get('gui_object', None)
+ self.daemon = self.gui_object.daemon
+ self.fx = self.daemon.fx
+
+ self.use_rbf = config.get('use_rbf', False)
+ self.use_change = config.get('use_change', True)
+ self.use_unconfirmed = not config.get('confirmed_only', False)
+
+ # create triggers so as to minimize updating a max of 2 times a sec
+ self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)
+ self._trigger_update_status = Clock.create_trigger(self.update_status, .5)
+ self._trigger_update_history = Clock.create_trigger(self.update_history, .5)
+ self._trigger_update_interfaces = Clock.create_trigger(self.update_interfaces, .5)
+ # cached dialogs
+ self._settings_dialog = None
+ self._password_dialog = None
+ self.fee_status = self.electrum_config.get_fee_status()
+
+ def wallet_name(self):
+ return os.path.basename(self.wallet.storage.path) if self.wallet else ' '
+
+ def on_pr(self, pr):
+ if not self.wallet:
+ self.show_error(_('No wallet loaded.'))
+ return
+ if pr.verify(self.wallet.contacts):
+ key = self.wallet.invoices.add(pr)
+ if self.invoices_screen:
+ self.invoices_screen.update()
+ status = self.wallet.invoices.get_status(key)
+ if status == PR_PAID:
+ self.show_error("invoice already paid")
+ self.send_screen.do_clear()
+ else:
+ if pr.has_expired():
+ self.show_error(_('Payment request has expired'))
+ else:
+ self.switch_to('send')
+ self.send_screen.set_request(pr)
+ else:
+ self.show_error("invoice error:" + pr.error)
+ self.send_screen.do_clear()
+
+ def on_qr(self, data):
+ from electrum.bitcoin import base_decode, is_address
+ data = data.strip()
+ if is_address(data):
+ self.set_URI(data)
+ return
+ if data.startswith('bitcore:'):
+ self.set_URI(data)
+ return
+ # try to decode transaction
+ from electrum.transaction import Transaction
+ from electrum.util import bh2u
+ try:
+ text = bh2u(base_decode(data, None, base=43))
+ tx = Transaction(text)
+ tx.deserialize()
+ except:
+ tx = None
+ if tx:
+ self.tx_dialog(tx)
+ return
+ # show error
+ self.show_error("Unable to decode QR data")
+
+ def update_tab(self, name):
+ s = getattr(self, name + '_screen', None)
+ if s:
+ s.update()
+
+ @profiler
+ def update_tabs(self):
+ for tab in ['invoices', 'send', 'history', 'receive', 'address']:
+ self.update_tab(tab)
+
+ def switch_to(self, name):
+ s = getattr(self, name + '_screen', None)
+ if s is None:
+ s = self.tabs.ids[name + '_screen']
+ s.load_screen()
+ panel = self.tabs.ids.panel
+ tab = self.tabs.ids[name + '_tab']
+ panel.switch_to(tab)
+
+ def show_request(self, addr):
+ self.switch_to('receive')
+ self.receive_screen.screen.address = addr
+
+ def show_pr_details(self, req, status, is_invoice):
+ from electrum.util import format_time
+ requestor = req.get('requestor')
+ exp = req.get('exp')
+ memo = req.get('memo')
+ amount = req.get('amount')
+ fund = req.get('fund')
+ popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv')
+ popup.is_invoice = is_invoice
+ popup.amount = amount
+ popup.requestor = requestor if is_invoice else req.get('address')
+ popup.exp = format_time(exp) if exp else ''
+ popup.description = memo if memo else ''
+ popup.signature = req.get('signature', '')
+ popup.status = status
+ popup.fund = fund if fund else 0
+ txid = req.get('txid')
+ popup.tx_hash = txid or ''
+ popup.on_open = lambda: popup.ids.output_list.update(req.get('outputs', []))
+ popup.export = self.export_private_keys
+ popup.open()
+
+ def show_addr_details(self, req, status):
+ from electrum.util import format_time
+ fund = req.get('fund')
+ isaddr = 'y'
+ popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv')
+ popup.isaddr = isaddr
+ popup.is_invoice = False
+ popup.status = status
+ popup.requestor = req.get('address')
+ popup.fund = fund if fund else 0
+ popup.export = self.export_private_keys
+ popup.open()
+
+ def qr_dialog(self, title, data, show_text=False, text_for_clipboard=None):
+ from .uix.dialogs.qr_dialog import QRDialog
+ def on_qr_failure():
+ popup.dismiss()
+ msg = _('Failed to display QR code.')
+ if text_for_clipboard:
+ msg += '\n' + _('Text copied to clipboard.')
+ self._clipboard.copy(text_for_clipboard)
+ Clock.schedule_once(lambda dt: self.show_info(msg))
+ popup = QRDialog(title, data, show_text, on_qr_failure)
+ popup.open()
+
+ def scan_qr(self, on_complete):
+ if platform != 'android':
+ return
+ from jnius import autoclass, cast
+ from android import activity
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity")
+ Intent = autoclass('android.content.Intent')
+ intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)
+
+ def on_qr_result(requestCode, resultCode, intent):
+ try:
+ if resultCode == -1: # RESULT_OK:
+ # this doesn't work due to some bug in jnius:
+ # contents = intent.getStringExtra("text")
+ String = autoclass("java.lang.String")
+ contents = intent.getStringExtra(String("text"))
+ on_complete(contents)
+ finally:
+ activity.unbind(on_activity_result=on_qr_result)
+ activity.bind(on_activity_result=on_qr_result)
+ PythonActivity.mActivity.startActivityForResult(intent, 0)
+
+ def do_share(self, data, title):
+ if platform != 'android':
+ return
+ from jnius import autoclass, cast
+ JS = autoclass('java.lang.String')
+ Intent = autoclass('android.content.Intent')
+ sendIntent = Intent()
+ sendIntent.setAction(Intent.ACTION_SEND)
+ sendIntent.setType("text/plain")
+ sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
+ it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title)))
+ currentActivity.startActivity(it)
+
+ def build(self):
+ return Builder.load_file('electrum/gui/kivy/main.kv')
+
+ def _pause(self):
+ if platform == 'android':
+ # move activity to back
+ from jnius import autoclass
+ python_act = autoclass('org.kivy.android.PythonActivity')
+ mActivity = python_act.mActivity
+ mActivity.moveTaskToBack(True)
+
+ def on_start(self):
+ ''' This is the start point of the kivy ui
+ '''
+ import time
+ Logger.info('Time to on_start: {} <<<<<<<<'.format(time.clock()))
+ win = Window
+ win.bind(size=self.on_size, on_keyboard=self.on_keyboard)
+ win.bind(on_key_down=self.on_key_down)
+ #win.softinput_mode = 'below_target'
+ self.on_size(win, win.size)
+ self.init_ui()
+ crash_reporter.ExceptionHook(self)
+ # init plugins
+ run_hook('init_kivy', self)
+ # fiat currency
+ self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
+ # default tab
+ self.switch_to('history')
+ # bind intent for bitcore: URI scheme
+ if platform == 'android':
+ from android import activity
+ from jnius import autoclass
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ mactivity = PythonActivity.mActivity
+ self.on_new_intent(mactivity.getIntent())
+ activity.bind(on_new_intent=self.on_new_intent)
+ # connect callbacks
+ if self.network:
+ interests = ['updated', 'status', 'new_transaction', 'verified', 'interfaces']
+ self.network.register_callback(self.on_network_event, interests)
+ self.network.register_callback(self.on_fee, ['fee'])
+ self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
+ self.network.register_callback(self.on_quotes, ['on_quotes'])
+ self.network.register_callback(self.on_history, ['on_history'])
+ # load wallet
+ self.load_wallet_by_name(self.electrum_config.get_wallet_path())
+ # URI passed in config
+ uri = self.electrum_config.get('url')
+ if uri:
+ self.set_URI(uri)
+
+
+ def get_wallet_path(self):
+ if self.wallet:
+ return self.wallet.storage.path
+ else:
+ return ''
+
+ def on_wizard_complete(self, wizard, wallet):
+ if wallet: # wizard returned a wallet
+ wallet.start_threads(self.daemon.network)
+ self.daemon.add_wallet(wallet)
+ self.load_wallet(wallet)
+ elif not self.wallet:
+ # wizard did not return a wallet; and there is no wallet open atm
+ # try to open last saved wallet (potentially start wizard again)
+ self.load_wallet_by_name(self.electrum_config.get_wallet_path(), ask_if_wizard=True)
+
+ def load_wallet_by_name(self, path, ask_if_wizard=False):
+ if not path:
+ return
+ if self.wallet and self.wallet.storage.path == path:
+ return
+ wallet = self.daemon.load_wallet(path, None)
+ if wallet:
+ if wallet.has_password():
+ self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
+ else:
+ self.load_wallet(wallet)
+ else:
+ Logger.debug('Electrum: Wallet not found or action needed. Launching install wizard')
+
+ def launch_wizard():
+ storage = WalletStorage(path, manual_upgrades=True)
+ wizard = Factory.InstallWizard(self.electrum_config, self.plugins, storage)
+ wizard.bind(on_wizard_complete=self.on_wizard_complete)
+ action = wizard.storage.get_action()
+ wizard.run(action)
+ if not ask_if_wizard:
+ launch_wizard()
+ else:
+ from .uix.dialogs.question import Question
+
+ def handle_answer(b: bool):
+ if b:
+ launch_wizard()
+ else:
+ try: os.unlink(path)
+ except FileNotFoundError: pass
+ self.stop()
+ d = Question(_('Do you want to launch the wizard again?'), handle_answer)
+ d.open()
+
+ def on_stop(self):
+ Logger.info('on_stop')
+ if self.wallet:
+ self.electrum_config.save_last_wallet(self.wallet)
+ self.stop_wallet()
+
+ def stop_wallet(self):
+ if self.wallet:
+ self.daemon.stop_wallet(self.wallet.storage.path)
+ self.wallet = None
+
+ def on_key_down(self, instance, key, keycode, codepoint, modifiers):
+ if 'ctrl' in modifiers:
+ # q=24 w=25
+ if keycode in (24, 25):
+ self.stop()
+ elif keycode == 27:
+ # r=27
+ # force update wallet
+ self.update_wallet()
+ elif keycode == 112:
+ # pageup
+ #TODO move to next tab
+ pass
+ elif keycode == 117:
+ # pagedown
+ #TODO move to prev tab
+ pass
+ #TODO: alt+tab_number to activate the particular tab
+
+ def on_keyboard(self, instance, key, keycode, codepoint, modifiers):
+ if key == 27 and self.is_exit is False:
+ self.is_exit = True
+ self.show_info(_('Press again to exit'))
+ return True
+ # override settings button
+ if key in (319, 282): #f1/settings button on android
+ #self.gui.main_gui.toggle_settings(self)
+ return True
+
+ def settings_dialog(self):
+ from .uix.dialogs.settings import SettingsDialog
+ if self._settings_dialog is None:
+ self._settings_dialog = SettingsDialog(self)
+ self._settings_dialog.update()
+ self._settings_dialog.open()
+
+ def popup_dialog(self, name):
+ if name == 'settings':
+ self.settings_dialog()
+ elif name == 'wallets':
+ from .uix.dialogs.wallets import WalletDialog
+ d = WalletDialog()
+ d.open()
+ elif name == 'status':
+ popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv')
+ master_public_keys_layout = popup.ids.master_public_keys
+ for xpub in self.wallet.get_master_public_keys()[1:]:
+ master_public_keys_layout.add_widget(TopLabel(text=_('Master Public Key')))
+ ref = RefLabel()
+ ref.name = _('Master Public Key')
+ ref.data = xpub
+ master_public_keys_layout.add_widget(ref)
+ popup.open()
+ else:
+ popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv')
+ popup.open()
+
+ @profiler
+ def init_ui(self):
+ ''' Initialize The Ux part of electrum. This function performs the basic
+ tasks of setting up the ui.
+ '''
+ #from weakref import ref
+
+ self.funds_error = False
+ # setup UX
+ self.screens = {}
+
+ #setup lazy imports for mainscreen
+ Factory.register('AnimatedPopup',
+ module='electrum.gui.kivy.uix.dialogs')
+ Factory.register('QRCodeWidget',
+ module='electrum.gui.kivy.uix.qrcodewidget')
+
+ # preload widgets. Remove this if you want to load the widgets on demand
+ #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup())
+ #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget())
+
+ # load and focus the ui
+ self.root.manager = self.root.ids['manager']
+
+ self.history_screen = None
+ self.contacts_screen = None
+ self.send_screen = None
+ self.invoices_screen = None
+ self.receive_screen = None
+ self.requests_screen = None
+ self.address_screen = None
+ self.icon = "icons/electrum.png"
+ self.tabs = self.root.ids['tabs']
+
+ def update_interfaces(self, dt):
+ self.num_nodes = len(self.network.get_interfaces())
+ self.num_chains = len(self.network.get_blockchains())
+ chain = self.network.blockchain()
+ self.blockchain_forkpoint = chain.get_forkpoint()
+ self.blockchain_name = chain.get_name()
+ interface = self.network.interface
+ if interface:
+ self.server_host = interface.host
+
+ def on_network_event(self, event, *args):
+ Logger.info('network event: '+ event)
+ if event == 'interfaces':
+ self._trigger_update_interfaces()
+ elif event == 'updated':
+ self._trigger_update_wallet()
+ self._trigger_update_status()
+ elif event == 'status':
+ self._trigger_update_status()
+ elif event == 'new_transaction':
+ self._trigger_update_wallet()
+ elif event == 'verified':
+ self._trigger_update_wallet()
+
+ @profiler
+ def load_wallet(self, wallet):
+ if self.wallet:
+ self.stop_wallet()
+ self.wallet = wallet
+ self.update_wallet()
+ # Once GUI has been initialized check if we want to announce something
+ # since the callback has been called before the GUI was initialized
+ if self.receive_screen:
+ self.receive_screen.clear()
+ self.update_tabs()
+ run_hook('load_wallet', wallet, self)
+
+ def update_status(self, *dt):
+ self.num_blocks = self.network.get_local_height()
+ if not self.wallet:
+ self.status = _("No Wallet")
+ return
+ if self.network is None or not self.network.is_running():
+ status = _("Offline")
+ elif self.network.is_connected():
+ server_height = self.network.get_server_height()
+ server_lag = self.network.get_local_height() - server_height
+ if not self.wallet.up_to_date or server_height == 0:
+ status = _("Synchronizing...")
+ elif server_lag > 1:
+ status = _("Server lagging")
+ else:
+ status = ''
+ else:
+ status = _("Disconnected")
+ self.status = self.wallet.basename() + (' [size=15dp](%s)[/size]'%status if status else '')
+ # balance
+ c, u, x = self.wallet.get_balance()
+ text = self.format_amount(c+x+u)
+ self.balance = str(text.strip()) + ' [size=22dp]%s[/size]'% self.base_unit
+ self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
+
+ def get_max_amount(self):
+ from electrum.transaction import TxOutput
+ if run_hook('abort_send', self):
+ return ''
+ inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
+ if not inputs:
+ return ''
+ addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
+ outputs = [TxOutput(TYPE_ADDRESS, addr, '!')]
+ try:
+ tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
+ except NoDynamicFeeEstimates as e:
+ Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e)))
+ return ''
+ except NotEnoughFunds:
+ return ''
+ amount = tx.output_value()
+ __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
+ amount_after_all_fees = amount - x_fee_amount
+ return format_satoshis_plain(amount_after_all_fees, self.decimal_point())
+
+ def format_amount(self, x, is_diff=False, whitespaces=False):
+ return format_satoshis(x, 0, self.decimal_point(), is_diff=is_diff, whitespaces=whitespaces)
+
+ def format_amount_and_units(self, x):
+ return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit
+
+ #@profiler
+ def update_wallet(self, *dt):
+ self._trigger_update_status()
+ if self.wallet and (self.wallet.up_to_date or not self.network or not self.network.is_connected()):
+ self.update_tabs()
+
+ def notify(self, message):
+ try:
+ global notification, os
+ if not notification:
+ from plyer import notification
+ icon = (os.path.dirname(os.path.realpath(__file__))
+ + '/../../' + self.icon)
+ notification.notify('Electrum', message,
+ app_icon=icon, app_name='Electrum')
+ except ImportError:
+ Logger.Error('Notification: needs plyer; `sudo pip install plyer`')
+
+ def on_pause(self):
+ self.pause_time = time.time()
+ # pause nfc
+ if self.nfcscanner:
+ self.nfcscanner.nfc_disable()
+ return True
+
+ def on_resume(self):
+ now = time.time()
+ if self.wallet and self.wallet.has_password() and now - self.pause_time > 60:
+ self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop)
+ if self.nfcscanner:
+ self.nfcscanner.nfc_enable()
+
+ def on_size(self, instance, value):
+ width, height = value
+ self._orientation = 'landscape' if width > height else 'portrait'
+ self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone'
+
+ def on_ref_label(self, label, touch):
+ if label.touched:
+ label.touched = False
+ self.qr_dialog(label.name, label.data, True)
+ else:
+ label.touched = True
+ self._clipboard.copy(label.data)
+ Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\nTap again to display it as QR code.')))
+
+ def set_send(self, address, amount, label, message):
+ self.send_payment(address, amount=amount, label=label, message=message)
+
+ def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
+ exit=False, icon='atlas://electrum/gui/kivy/theming/light/error', duration=0,
+ modal=False):
+ ''' Show an error Message Bubble.
+ '''
+ self.show_info_bubble( text=error, icon=icon, width=width,
+ pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,
+ duration=duration, modal=modal)
+
+ def show_info(self, error, width='200dp', pos=None, arrow_pos=None,
+ exit=False, duration=0, modal=False):
+ ''' Show an Info Message Bubble.
+ '''
+ self.show_error(error, icon='atlas://electrum/gui/kivy/theming/light/important',
+ duration=duration, modal=modal, exit=exit, pos=pos,
+ arrow_pos=arrow_pos)
+
+ def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,
+ arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):
+ '''Method to show an Information Bubble
+
+ .. parameters::
+ text: Message to be displayed
+ pos: position for the bubble
+ duration: duration the bubble remains on screen. 0 = click to hide
+ width: width of the Bubble
+ arrow_pos: arrow position for the bubble
+ '''
+ info_bubble = self.info_bubble
+ if not info_bubble:
+ info_bubble = self.info_bubble = Factory.InfoBubble()
+
+ win = Window
+ if info_bubble.parent:
+ win.remove_widget(info_bubble
+ if not info_bubble.modal else
+ info_bubble._modal_view)
+
+ if not arrow_pos:
+ info_bubble.show_arrow = False
+ else:
+ info_bubble.show_arrow = True
+ info_bubble.arrow_pos = arrow_pos
+ img = info_bubble.ids.img
+ if text == 'texture':
+ # icon holds a texture not a source image
+ # display the texture in full screen
+ text = ''
+ img.texture = icon
+ info_bubble.fs = True
+ info_bubble.show_arrow = False
+ img.allow_stretch = True
+ info_bubble.dim_background = True
+ info_bubble.background_image = 'atlas://electrum/gui/kivy/theming/light/card'
+ else:
+ info_bubble.fs = False
+ info_bubble.icon = icon
+ #if img.texture and img._coreimage:
+ # img.reload()
+ img.allow_stretch = False
+ info_bubble.dim_background = False
+ info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble'
+ info_bubble.message = text
+ if not pos:
+ pos = (win.center[0], win.center[1] - (info_bubble.height/2))
+ info_bubble.show(pos, duration, width, modal=modal, exit=exit)
+
+ def tx_dialog(self, tx):
+ from .uix.dialogs.tx_dialog import TxDialog
+ d = TxDialog(self, tx)
+ d.open()
+
+ def sign_tx(self, *args):
+ threading.Thread(target=self._sign_tx, args=args).start()
+
+ def _sign_tx(self, tx, password, on_success, on_failure):
+ try:
+ self.wallet.sign_transaction(tx, password)
+ except InvalidPassword:
+ Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN")))
+ return
+ on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
+ Clock.schedule_once(lambda dt: on_success(tx))
+
+ def _broadcast_thread(self, tx, on_complete):
+ ok, txid = self.network.broadcast_transaction(tx)
+ Clock.schedule_once(lambda dt: on_complete(ok, txid))
+
+ def broadcast(self, tx, pr=None):
+ def on_complete(ok, msg):
+ if ok:
+ self.show_info(_('Payment sent.'))
+ if self.send_screen:
+ self.send_screen.do_clear()
+ if pr:
+ self.wallet.invoices.set_paid(pr, tx.txid())
+ self.wallet.invoices.save()
+ self.update_tab('invoices')
+ else:
+ msg = msg[:500] if msg else _('There was an error broadcasting the transaction.')
+ self.show_error(msg)
+
+ if self.network and self.network.is_connected():
+ self.show_info(_('Sending'))
+ threading.Thread(target=self._broadcast_thread, args=(tx, on_complete)).start()
+ else:
+ self.show_info(_('Cannot broadcast transaction') + ':\n' + _('Not connected'))
+
+ def description_dialog(self, screen):
+ from .uix.dialogs.label_dialog import LabelDialog
+ text = screen.message
+ def callback(text):
+ screen.message = text
+ d = LabelDialog(_('Enter description'), text, callback)
+ d.open()
+
+ def amount_dialog(self, screen, show_max):
+ from .uix.dialogs.amount_dialog import AmountDialog
+ amount = screen.amount
+ if amount:
+ amount, u = str(amount).split()
+ assert u == self.base_unit
+ def cb(amount):
+ screen.amount = amount
+ popup = AmountDialog(show_max, amount, cb)
+ popup.open()
+
+ def invoices_dialog(self, screen):
+ from .uix.dialogs.invoices import InvoicesDialog
+ if len(self.wallet.invoices.sorted_list()) == 0:
+ self.show_info(' '.join([
+ _('No saved invoices.'),
+ _('Signed invoices are saved automatically when you scan them.'),
+ _('You may also save unsigned requests or contact addresses using the save button.')
+ ]))
+ return
+ popup = InvoicesDialog(self, screen, None)
+ popup.update()
+ popup.open()
+
+ def requests_dialog(self, screen):
+ from .uix.dialogs.requests import RequestsDialog
+ if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0:
+ self.show_info(_('No saved requests.'))
+ return
+ popup = RequestsDialog(self, screen, None)
+ popup.update()
+ popup.open()
+
+ def addresses_dialog(self, screen):
+ from .uix.dialogs.addresses import AddressesDialog
+ popup = AddressesDialog(self, screen, None)
+ popup.update()
+ popup.open()
+
+ def fee_dialog(self, label, dt):
+ from .uix.dialogs.fee_dialog import FeeDialog
+ def cb():
+ self.fee_status = self.electrum_config.get_fee_status()
+ fee_dialog = FeeDialog(self, self.electrum_config, cb)
+ fee_dialog.open()
+
+ def on_fee(self, event, *arg):
+ self.fee_status = self.electrum_config.get_fee_status()
+
+ def protected(self, msg, f, args):
+ if self.wallet.has_password():
+ on_success = lambda pw: f(*(args + (pw,)))
+ self.password_dialog(self.wallet, msg, on_success, lambda: None)
+ else:
+ f(*(args + (None,)))
+
+ def delete_wallet(self):
+ from .uix.dialogs.question import Question
+ basename = os.path.basename(self.wallet.storage.path)
+ d = Question(_('Delete wallet?') + '\n' + basename, self._delete_wallet)
+ d.open()
+
+ def _delete_wallet(self, b):
+ if b:
+ basename = self.wallet.basename()
+ self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ())
+
+ def __delete_wallet(self, pw):
+ wallet_path = self.get_wallet_path()
+ dirname = os.path.dirname(wallet_path)
+ basename = os.path.basename(wallet_path)
+ if self.wallet.has_password():
+ try:
+ self.wallet.check_password(pw)
+ except:
+ self.show_error("Invalid PIN")
+ return
+ self.stop_wallet()
+ os.unlink(wallet_path)
+ self.show_error(_("Wallet removed: {}").format(basename))
+ new_path = self.electrum_config.get_wallet_path()
+ self.load_wallet_by_name(new_path)
+
+ def show_seed(self, label):
+ self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,))
+
+ def _show_seed(self, label, password):
+ if self.wallet.has_password() and password is None:
+ return
+ keystore = self.wallet.keystore
+ try:
+ seed = keystore.get_seed(password)
+ passphrase = keystore.get_passphrase(password)
+ except:
+ self.show_error("Invalid PIN")
+ return
+ label.text = _('Seed') + ':\n' + seed
+ if passphrase:
+ label.text += '\n\n' + _('Passphrase') + ': ' + passphrase
+
+ def password_dialog(self, wallet, msg, on_success, on_failure):
+ from .uix.dialogs.password_dialog import PasswordDialog
+ if self._password_dialog is None:
+ self._password_dialog = PasswordDialog()
+ self._password_dialog.init(self, wallet, msg, on_success, on_failure)
+ self._password_dialog.open()
+
+ def change_password(self, cb):
+ from .uix.dialogs.password_dialog import PasswordDialog
+ if self._password_dialog is None:
+ self._password_dialog = PasswordDialog()
+ message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:")
+ def on_success(old_password, new_password):
+ self.wallet.update_password(old_password, new_password)
+ self.show_info(_("Your PIN code was updated"))
+ on_failure = lambda: self.show_error(_("PIN codes do not match"))
+ self._password_dialog.init(self, self.wallet, message, on_success, on_failure, is_change=1)
+ self._password_dialog.open()
+
+ def export_private_keys(self, pk_label, addr):
+ if self.wallet.is_watching_only():
+ self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
+ return
+ def show_private_key(addr, pk_label, password):
+ if self.wallet.has_password() and password is None:
+ return
+ if not self.wallet.can_export():
+ return
+ try:
+ key = str(self.wallet.export_private_key(addr, password)[0])
+ pk_label.data = key
+ except InvalidPassword:
+ self.show_error("Invalid PIN")
+ return
+ self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))
diff --git a/electrum/gui/kivy/nfc_scanner/__init__.py b/electrum/gui/kivy/nfc_scanner/__init__.py
new file mode 100644
index 000000000..81084a64c
--- /dev/null
+++ b/electrum/gui/kivy/nfc_scanner/__init__.py
@@ -0,0 +1,44 @@
+__all__ = ('NFCBase', 'NFCScanner')
+
+class NFCBase(Widget):
+ ''' This is the base Abstract definition class that the actual hardware dependent
+ implementations would be based on. If you want to define a feature that is
+ accessible and implemented by every platform implementation then define that
+ method in this class.
+ '''
+
+ payload = ObjectProperty(None)
+ '''This is the data gotten from the tag.
+ '''
+
+ def nfc_init(self):
+ ''' Initialize the adapter.
+ '''
+ pass
+
+ def nfc_disable(self):
+ ''' Disable scanning
+ '''
+ pass
+
+ def nfc_enable(self):
+ ''' Enable Scanning
+ '''
+ pass
+
+ def nfc_enable_exchange(self, data):
+ ''' Enable P2P Ndef exchange
+ '''
+ pass
+
+ def nfc_disable_exchange(self):
+ ''' Disable/Stop P2P Ndef exchange
+ '''
+ pass
+
+# load NFCScanner implementation
+
+NFCScanner = core_select_lib('nfc_manager', (
+ # keep the dummy implementation as the last one to make it the fallback provider.NFCScanner = core_select_lib('nfc_scanner', (
+ ('android', 'scanner_android', 'ScannerAndroid'),
+ ('dummy', 'scanner_dummy', 'ScannerDummy')), True, 'electrum.gui.kivy')
diff --git a/electrum/gui/kivy/nfc_scanner/scanner_android.py b/electrum/gui/kivy/nfc_scanner/scanner_android.py
new file mode 100644
index 000000000..32ffda169
--- /dev/null
+++ b/electrum/gui/kivy/nfc_scanner/scanner_android.py
@@ -0,0 +1,242 @@
+'''This is the Android implementation of NFC Scanning using the
+built in NFC adapter of some android phones.
+'''
+
+from kivy.app import App
+from kivy.clock import Clock
+#Detect which platform we are on
+from kivy.utils import platform
+if platform != 'android':
+ raise ImportError
+import threading
+
+from . import NFCBase
+from jnius import autoclass, cast
+from android.runnable import run_on_ui_thread
+from android import activity
+
+BUILDVERSION = autoclass('android.os.Build$VERSION').SDK_INT
+NfcAdapter = autoclass('android.nfc.NfcAdapter')
+PythonActivity = autoclass('org.kivy.android.PythonActivity')
+JString = autoclass('java.lang.String')
+Charset = autoclass('java.nio.charset.Charset')
+locale = autoclass('java.util.Locale')
+Intent = autoclass('android.content.Intent')
+IntentFilter = autoclass('android.content.IntentFilter')
+PendingIntent = autoclass('android.app.PendingIntent')
+Ndef = autoclass('android.nfc.tech.Ndef')
+NdefRecord = autoclass('android.nfc.NdefRecord')
+NdefMessage = autoclass('android.nfc.NdefMessage')
+
+app = None
+
+
+
+class ScannerAndroid(NFCBase):
+ ''' This is the class responsible for handling the interface with the
+ Android NFC adapter. See Module Documentation for details.
+ '''
+
+ name = 'NFCAndroid'
+
+ def nfc_init(self):
+ ''' This is where we initialize NFC adapter.
+ '''
+ # Initialize NFC
+ global app
+ app = App.get_running_app()
+
+ # Make sure we are listening to new intent
+ activity.bind(on_new_intent=self.on_new_intent)
+
+ # Configure nfc
+ self.j_context = context = PythonActivity.mActivity
+ self.nfc_adapter = NfcAdapter.getDefaultAdapter(context)
+ # Check if adapter exists
+ if not self.nfc_adapter:
+ return False
+
+ # specify that we want our activity to remain on top when a new intent
+ # is fired
+ self.nfc_pending_intent = PendingIntent.getActivity(context, 0,
+ Intent(context, context.getClass()).addFlags(
+ Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
+
+ # Filter for different types of action, by default we enable all.
+ # These are only for handling different NFC technologies when app is in foreground
+ self.ndef_detected = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
+ #self.tech_detected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
+ #self.tag_detected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
+
+ # setup tag discovery for ourt tag type
+ try:
+ self.ndef_detected.addCategory(Intent.CATEGORY_DEFAULT)
+ # setup the foreground dispatch to detect all mime types
+ self.ndef_detected.addDataType('*/*')
+
+ self.ndef_exchange_filters = [self.ndef_detected]
+ except Exception as err:
+ raise Exception(repr(err))
+ return True
+
+ def get_ndef_details(self, tag):
+ ''' Get all the details from the tag.
+ '''
+ details = {}
+
+ try:
+ #print 'id'
+ details['uid'] = ':'.join(['{:02x}'.format(bt & 0xff) for bt in tag.getId()])
+ #print 'technologies'
+ details['Technologies'] = tech_list = [tech.split('.')[-1] for tech in tag.getTechList()]
+ #print 'get NDEF tag details'
+ ndefTag = cast('android.nfc.tech.Ndef', Ndef.get(tag))
+ #print 'tag size'
+ details['MaxSize'] = ndefTag.getMaxSize()
+ #details['usedSize'] = '0'
+ #print 'is tag writable?'
+ details['writable'] = ndefTag.isWritable()
+ #print 'Data format'
+ # Can be made readonly
+ # get NDEF message details
+ ndefMesg = ndefTag.getCachedNdefMessage()
+ # get size of current records
+ details['consumed'] = len(ndefMesg.toByteArray())
+ #print 'tag type'
+ details['Type'] = ndefTag.getType()
+
+ # check if tag is empty
+ if not ndefMesg:
+ details['Message'] = None
+ return details
+
+ ndefrecords = ndefMesg.getRecords()
+ length = len(ndefrecords)
+ #print 'length', length
+ # will contain the NDEF record types
+ recTypes = []
+ for record in ndefrecords:
+ recTypes.append({
+ 'type': ''.join(map(unichr, record.getType())),
+ 'payload': ''.join(map(unichr, record.getPayload()))
+ })
+
+ details['recTypes'] = recTypes
+ except Exception as err:
+ print(str(err))
+
+ return details
+
+ def on_new_intent(self, intent):
+ ''' This function is called when the application receives a
+ new intent, for the ones the application has registered previously,
+ either in the manifest or in the foreground dispatch setup in the
+ nfc_init function above.
+ '''
+
+ action_list = (NfcAdapter.ACTION_NDEF_DISCOVERED,)
+ # get TAG
+ #tag = cast('android.nfc.Tag', intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
+
+ #details = self.get_ndef_details(tag)
+
+ if intent.getAction() not in action_list:
+ print('unknow action, avoid.')
+ return
+
+ rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
+ if not rawmsgs:
+ return
+ for message in rawmsgs:
+ message = cast(NdefMessage, message)
+ payload = message.getRecords()[0].getPayload()
+ print('payload: {}'.format(''.join(map(chr, payload))))
+
+ def nfc_disable(self):
+ '''Disable app from handling tags.
+ '''
+ self.disable_foreground_dispatch()
+
+ def nfc_enable(self):
+ '''Enable app to handle tags when app in foreground.
+ '''
+ self.enable_foreground_dispatch()
+
+ def create_AAR(self):
+ '''Create the record responsible for linking our application to the tag.
+ '''
+ return NdefRecord.createApplicationRecord(JString("org.electrum.kivy"))
+
+ def create_TNF_EXTERNAL(self, data):
+ '''Create our actual payload record.
+ '''
+ if BUILDVERSION >= 14:
+ domain = "org.electrum"
+ stype = "externalType"
+ extRecord = NdefRecord.createExternal(domain, stype, data)
+ else:
+ # Creating the NdefRecord manually:
+ extRecord = NdefRecord(
+ NdefRecord.TNF_EXTERNAL_TYPE,
+ "org.electrum:externalType",
+ '',
+ data)
+ return extRecord
+
+ def create_ndef_message(self, *recs):
+ ''' Create the Ndef message that will be written to tag
+ '''
+ records = []
+ for record in recs:
+ if record:
+ records.append(record)
+
+ return NdefMessage(records)
+
+
+ @run_on_ui_thread
+ def disable_foreground_dispatch(self):
+ '''Disable foreground dispatch when app is paused.
+ '''
+ self.nfc_adapter.disableForegroundDispatch(self.j_context)
+
+ @run_on_ui_thread
+ def enable_foreground_dispatch(self):
+ '''Start listening for new tags
+ '''
+ self.nfc_adapter.enableForegroundDispatch(self.j_context,
+ self.nfc_pending_intent, self.ndef_exchange_filters, self.ndef_tech_list)
+
+ @run_on_ui_thread
+ def _nfc_enable_ndef_exchange(self, data):
+ # Enable p2p exchange
+ # Create record
+ ndef_record = NdefRecord(
+ NdefRecord.TNF_MIME_MEDIA,
+ 'org.electrum.kivy', '', data)
+
+ # Create message
+ ndef_message = NdefMessage([ndef_record])
+
+ # Enable ndef push
+ self.nfc_adapter.enableForegroundNdefPush(self.j_context, ndef_message)
+
+ # Enable dispatch
+ self.nfc_adapter.enableForegroundDispatch(self.j_context,
+ self.nfc_pending_intent, self.ndef_exchange_filters, [])
+
+ @run_on_ui_thread
+ def _nfc_disable_ndef_exchange(self):
+ # Disable p2p exchange
+ self.nfc_adapter.disableForegroundNdefPush(self.j_context)
+ self.nfc_adapter.disableForegroundDispatch(self.j_context)
+
+ def nfc_enable_exchange(self, data):
+ '''Enable Ndef exchange for p2p
+ '''
+ self._nfc_enable_ndef_exchange()
+
+ def nfc_disable_exchange(self):
+ ''' Disable Ndef exchange for p2p
+ '''
+ self._nfc_disable_ndef_exchange()
diff --git a/electrum/gui/kivy/nfc_scanner/scanner_dummy.py b/electrum/gui/kivy/nfc_scanner/scanner_dummy.py
new file mode 100644
index 000000000..a0d3e2643
--- /dev/null
+++ b/electrum/gui/kivy/nfc_scanner/scanner_dummy.py
@@ -0,0 +1,52 @@
+''' Dummy NFC Provider to be used on desktops in case no other provider is found
+'''
+from . import NFCBase
+from kivy.clock import Clock
+from kivy.logger import Logger
+
+class ScannerDummy(NFCBase):
+ '''This is the dummy interface that gets selected in case any other
+ hardware interface to NFC is not available.
+ '''
+
+ _initialised = False
+
+ name = 'NFCDummy'
+
+ def nfc_init(self):
+ # print 'nfc_init()'
+
+ Logger.debug('NFC: configure nfc')
+ self._initialised = True
+ self.nfc_enable()
+ return True
+
+ def on_new_intent(self, dt):
+ tag_info = {'type': 'dymmy',
+ 'message': 'dummy',
+ 'extra details': None}
+
+ # let Main app know that a tag has been detected
+ app = App.get_running_app()
+ app.tag_discovered(tag_info)
+ app.show_info('New tag detected.', duration=2)
+ Logger.debug('NFC: got new dummy tag')
+
+ def nfc_enable(self):
+ Logger.debug('NFC: enable')
+ if self._initialised:
+ Clock.schedule_interval(self.on_new_intent, 22)
+
+ def nfc_disable(self):
+ # print 'nfc_enable()'
+ Clock.unschedule(self.on_new_intent)
+
+ def nfc_enable_exchange(self, data):
+ ''' Start sending data
+ '''
+ Logger.debug('NFC: sending data {}'.format(data))
+
+ def nfc_disable_exchange(self):
+ ''' Disable/Stop ndef exchange
+ '''
+ Logger.debug('NFC: disable nfc exchange')
diff --git a/electrum/gui/kivy/theming/light/action_bar.png b/electrum/gui/kivy/theming/light/action_bar.png
new file mode 100644
index 000000000..7745a91ea
Binary files /dev/null and b/electrum/gui/kivy/theming/light/action_bar.png differ
diff --git a/electrum/gui/kivy/theming/light/action_button_group.png b/electrum/gui/kivy/theming/light/action_button_group.png
new file mode 100644
index 000000000..e0ff28c38
Binary files /dev/null and b/electrum/gui/kivy/theming/light/action_button_group.png differ
diff --git a/electrum/gui/kivy/theming/light/action_group_dark.png b/electrum/gui/kivy/theming/light/action_group_dark.png
new file mode 100644
index 000000000..13e3a4528
Binary files /dev/null and b/electrum/gui/kivy/theming/light/action_group_dark.png differ
diff --git a/electrum/gui/kivy/theming/light/action_group_light.png b/electrum/gui/kivy/theming/light/action_group_light.png
new file mode 100644
index 000000000..4e0b472dc
Binary files /dev/null and b/electrum/gui/kivy/theming/light/action_group_light.png differ
diff --git a/electrum/gui/kivy/theming/light/add_contact.png b/electrum/gui/kivy/theming/light/add_contact.png
new file mode 100644
index 000000000..5bf9793ae
Binary files /dev/null and b/electrum/gui/kivy/theming/light/add_contact.png differ
diff --git a/electrum/gui/kivy/theming/light/arrow_back.png b/electrum/gui/kivy/theming/light/arrow_back.png
new file mode 100644
index 000000000..0fb9e29ed
Binary files /dev/null and b/electrum/gui/kivy/theming/light/arrow_back.png differ
diff --git a/electrum/gui/kivy/theming/light/bit_logo.png b/electrum/gui/kivy/theming/light/bit_logo.png
new file mode 100644
index 000000000..2f5cd8236
Binary files /dev/null and b/electrum/gui/kivy/theming/light/bit_logo.png differ
diff --git a/electrum/gui/kivy/theming/light/blue_bg_round_rb.png b/electrum/gui/kivy/theming/light/blue_bg_round_rb.png
new file mode 100644
index 000000000..b13c4bc19
Binary files /dev/null and b/electrum/gui/kivy/theming/light/blue_bg_round_rb.png differ
diff --git a/electrum/gui/kivy/theming/light/btn_create_account.png b/electrum/gui/kivy/theming/light/btn_create_account.png
new file mode 100644
index 000000000..c10294a52
Binary files /dev/null and b/electrum/gui/kivy/theming/light/btn_create_account.png differ
diff --git a/electrum/gui/kivy/theming/light/btn_create_act_disabled.png b/electrum/gui/kivy/theming/light/btn_create_act_disabled.png
new file mode 100644
index 000000000..812e9de24
Binary files /dev/null and b/electrum/gui/kivy/theming/light/btn_create_act_disabled.png differ
diff --git a/electrum/gui/kivy/theming/light/btn_nfc.png b/electrum/gui/kivy/theming/light/btn_nfc.png
new file mode 100644
index 000000000..eb9d2d7c8
Binary files /dev/null and b/electrum/gui/kivy/theming/light/btn_nfc.png differ
diff --git a/electrum/gui/kivy/theming/light/btn_send_address.png b/electrum/gui/kivy/theming/light/btn_send_address.png
new file mode 100644
index 000000000..046196073
Binary files /dev/null and b/electrum/gui/kivy/theming/light/btn_send_address.png differ
diff --git a/electrum/gui/kivy/theming/light/btn_send_nfc.png b/electrum/gui/kivy/theming/light/btn_send_nfc.png
new file mode 100644
index 000000000..623bdde5c
Binary files /dev/null and b/electrum/gui/kivy/theming/light/btn_send_nfc.png differ
diff --git a/electrum/gui/kivy/theming/light/calculator.png b/electrum/gui/kivy/theming/light/calculator.png
new file mode 100644
index 000000000..a74584e8d
Binary files /dev/null and b/electrum/gui/kivy/theming/light/calculator.png differ
diff --git a/electrum/gui/kivy/theming/light/camera.png b/electrum/gui/kivy/theming/light/camera.png
new file mode 100644
index 000000000..1a07a6f50
Binary files /dev/null and b/electrum/gui/kivy/theming/light/camera.png differ
diff --git a/electrum/gui/kivy/theming/light/card.png b/electrum/gui/kivy/theming/light/card.png
new file mode 100644
index 000000000..9b348bd8a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/card.png differ
diff --git a/electrum/gui/kivy/theming/light/card_bottom.png b/electrum/gui/kivy/theming/light/card_bottom.png
new file mode 100644
index 000000000..bfc484514
Binary files /dev/null and b/electrum/gui/kivy/theming/light/card_bottom.png differ
diff --git a/electrum/gui/kivy/theming/light/card_btn.png b/electrum/gui/kivy/theming/light/card_btn.png
new file mode 100644
index 000000000..038ed749f
Binary files /dev/null and b/electrum/gui/kivy/theming/light/card_btn.png differ
diff --git a/electrum/gui/kivy/theming/light/card_top.png b/electrum/gui/kivy/theming/light/card_top.png
new file mode 100644
index 000000000..db434ee49
Binary files /dev/null and b/electrum/gui/kivy/theming/light/card_top.png differ
diff --git a/electrum/gui/kivy/theming/light/carousel_deselected.png b/electrum/gui/kivy/theming/light/carousel_deselected.png
new file mode 100644
index 000000000..d9e9e10be
Binary files /dev/null and b/electrum/gui/kivy/theming/light/carousel_deselected.png differ
diff --git a/electrum/gui/kivy/theming/light/carousel_selected.png b/electrum/gui/kivy/theming/light/carousel_selected.png
new file mode 100644
index 000000000..bd9ecce19
Binary files /dev/null and b/electrum/gui/kivy/theming/light/carousel_selected.png differ
diff --git a/electrum/gui/kivy/theming/light/clock1.png b/electrum/gui/kivy/theming/light/clock1.png
new file mode 100644
index 000000000..7c59b66de
Binary files /dev/null and b/electrum/gui/kivy/theming/light/clock1.png differ
diff --git a/electrum/gui/kivy/theming/light/clock2.png b/electrum/gui/kivy/theming/light/clock2.png
new file mode 100644
index 000000000..cc8006555
Binary files /dev/null and b/electrum/gui/kivy/theming/light/clock2.png differ
diff --git a/electrum/gui/kivy/theming/light/clock3.png b/electrum/gui/kivy/theming/light/clock3.png
new file mode 100644
index 000000000..ac4134248
Binary files /dev/null and b/electrum/gui/kivy/theming/light/clock3.png differ
diff --git a/electrum/gui/kivy/theming/light/clock4.png b/electrum/gui/kivy/theming/light/clock4.png
new file mode 100644
index 000000000..25d9162f5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/clock4.png differ
diff --git a/electrum/gui/kivy/theming/light/clock5.png b/electrum/gui/kivy/theming/light/clock5.png
new file mode 100644
index 000000000..79138bf97
Binary files /dev/null and b/electrum/gui/kivy/theming/light/clock5.png differ
diff --git a/electrum/gui/kivy/theming/light/close.png b/electrum/gui/kivy/theming/light/close.png
new file mode 100644
index 000000000..180295a6a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/close.png differ
diff --git a/electrum/gui/kivy/theming/light/closebutton.png b/electrum/gui/kivy/theming/light/closebutton.png
new file mode 100644
index 000000000..349241801
Binary files /dev/null and b/electrum/gui/kivy/theming/light/closebutton.png differ
diff --git a/electrum/gui/kivy/theming/light/confirmed.png b/electrum/gui/kivy/theming/light/confirmed.png
new file mode 100644
index 000000000..ba66aa084
Binary files /dev/null and b/electrum/gui/kivy/theming/light/confirmed.png differ
diff --git a/electrum/gui/kivy/theming/light/contact.png b/electrum/gui/kivy/theming/light/contact.png
new file mode 100644
index 000000000..e9d73336b
Binary files /dev/null and b/electrum/gui/kivy/theming/light/contact.png differ
diff --git a/electrum/gui/kivy/theming/light/contact_overlay.png b/electrum/gui/kivy/theming/light/contact_overlay.png
new file mode 100644
index 000000000..61ad143a4
Binary files /dev/null and b/electrum/gui/kivy/theming/light/contact_overlay.png differ
diff --git a/electrum/gui/kivy/theming/light/create_act_text.png b/electrum/gui/kivy/theming/light/create_act_text.png
new file mode 100644
index 000000000..10d19b38e
Binary files /dev/null and b/electrum/gui/kivy/theming/light/create_act_text.png differ
diff --git a/electrum/gui/kivy/theming/light/create_act_text_active.png b/electrum/gui/kivy/theming/light/create_act_text_active.png
new file mode 100644
index 000000000..f3bbc9b51
Binary files /dev/null and b/electrum/gui/kivy/theming/light/create_act_text_active.png differ
diff --git a/electrum/gui/kivy/theming/light/dialog.png b/electrum/gui/kivy/theming/light/dialog.png
new file mode 100644
index 000000000..f7e2bc080
Binary files /dev/null and b/electrum/gui/kivy/theming/light/dialog.png differ
diff --git a/electrum/gui/kivy/theming/light/dropdown_background.png b/electrum/gui/kivy/theming/light/dropdown_background.png
new file mode 100644
index 000000000..d8f80c2ca
Binary files /dev/null and b/electrum/gui/kivy/theming/light/dropdown_background.png differ
diff --git a/electrum/gui/kivy/theming/light/electrum_icon640.png b/electrum/gui/kivy/theming/light/electrum_icon640.png
new file mode 100644
index 000000000..7533c3299
Binary files /dev/null and b/electrum/gui/kivy/theming/light/electrum_icon640.png differ
diff --git a/electrum/gui/kivy/theming/light/error.png b/electrum/gui/kivy/theming/light/error.png
new file mode 100644
index 000000000..7715cbf8b
Binary files /dev/null and b/electrum/gui/kivy/theming/light/error.png differ
diff --git a/electrum/gui/kivy/theming/light/gear.png b/electrum/gui/kivy/theming/light/gear.png
new file mode 100644
index 000000000..e06f0c3df
Binary files /dev/null and b/electrum/gui/kivy/theming/light/gear.png differ
diff --git a/electrum/gui/kivy/theming/light/globe.png b/electrum/gui/kivy/theming/light/globe.png
new file mode 100644
index 000000000..d56382d0c
Binary files /dev/null and b/electrum/gui/kivy/theming/light/globe.png differ
diff --git a/electrum/gui/kivy/theming/light/icon_border.png b/electrum/gui/kivy/theming/light/icon_border.png
new file mode 100644
index 000000000..64bdca2f9
Binary files /dev/null and b/electrum/gui/kivy/theming/light/icon_border.png differ
diff --git a/electrum/gui/kivy/theming/light/important.png b/electrum/gui/kivy/theming/light/important.png
new file mode 100644
index 000000000..b0ecb27f1
Binary files /dev/null and b/electrum/gui/kivy/theming/light/important.png differ
diff --git a/electrum/gui/kivy/theming/light/info.png b/electrum/gui/kivy/theming/light/info.png
new file mode 100644
index 000000000..d8f78294a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/info.png differ
diff --git a/electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png b/electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png
new file mode 100644
index 000000000..1c86c454a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/lightblue_bg_round_lb.png differ
diff --git a/electrum/gui/kivy/theming/light/logo.png b/electrum/gui/kivy/theming/light/logo.png
new file mode 100644
index 000000000..e07a7dc07
Binary files /dev/null and b/electrum/gui/kivy/theming/light/logo.png differ
diff --git a/electrum/gui/kivy/theming/light/logo_atom_dull.png b/electrum/gui/kivy/theming/light/logo_atom_dull.png
new file mode 100644
index 000000000..f5ed23df5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/logo_atom_dull.png differ
diff --git a/electrum/gui/kivy/theming/light/mail_icon.png b/electrum/gui/kivy/theming/light/mail_icon.png
new file mode 100644
index 000000000..32f163259
Binary files /dev/null and b/electrum/gui/kivy/theming/light/mail_icon.png differ
diff --git a/electrum/gui/kivy/theming/light/manualentry.png b/electrum/gui/kivy/theming/light/manualentry.png
new file mode 100644
index 000000000..b691358f1
Binary files /dev/null and b/electrum/gui/kivy/theming/light/manualentry.png differ
diff --git a/electrum/gui/kivy/theming/light/network.png b/electrum/gui/kivy/theming/light/network.png
new file mode 100644
index 000000000..f25817011
Binary files /dev/null and b/electrum/gui/kivy/theming/light/network.png differ
diff --git a/electrum/gui/kivy/theming/light/nfc.png b/electrum/gui/kivy/theming/light/nfc.png
new file mode 100644
index 000000000..23db4d016
Binary files /dev/null and b/electrum/gui/kivy/theming/light/nfc.png differ
diff --git a/electrum/gui/kivy/theming/light/nfc_clock.png b/electrum/gui/kivy/theming/light/nfc_clock.png
new file mode 100644
index 000000000..f438caf87
Binary files /dev/null and b/electrum/gui/kivy/theming/light/nfc_clock.png differ
diff --git a/electrum/gui/kivy/theming/light/nfc_phone.png b/electrum/gui/kivy/theming/light/nfc_phone.png
new file mode 100644
index 000000000..e5462f05a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/nfc_phone.png differ
diff --git a/electrum/gui/kivy/theming/light/nfc_stage_one.png b/electrum/gui/kivy/theming/light/nfc_stage_one.png
new file mode 100644
index 000000000..47fe855f5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/nfc_stage_one.png differ
diff --git a/electrum/gui/kivy/theming/light/overflow_background.png b/electrum/gui/kivy/theming/light/overflow_background.png
new file mode 100644
index 000000000..0819d75e5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/overflow_background.png differ
diff --git a/electrum/gui/kivy/theming/light/overflow_btn_dn.png b/electrum/gui/kivy/theming/light/overflow_btn_dn.png
new file mode 100644
index 000000000..63fc9aa3b
Binary files /dev/null and b/electrum/gui/kivy/theming/light/overflow_btn_dn.png differ
diff --git a/electrum/gui/kivy/theming/light/paste_icon.png b/electrum/gui/kivy/theming/light/paste_icon.png
new file mode 100644
index 000000000..86218e2a5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/paste_icon.png differ
diff --git a/electrum/gui/kivy/theming/light/pen.png b/electrum/gui/kivy/theming/light/pen.png
new file mode 100644
index 000000000..74b9468e5
Binary files /dev/null and b/electrum/gui/kivy/theming/light/pen.png differ
diff --git a/electrum/gui/kivy/theming/light/qrcode.png b/electrum/gui/kivy/theming/light/qrcode.png
new file mode 100644
index 000000000..5aee12381
Binary files /dev/null and b/electrum/gui/kivy/theming/light/qrcode.png differ
diff --git a/electrum/gui/kivy/theming/light/save.png b/electrum/gui/kivy/theming/light/save.png
new file mode 100644
index 000000000..43859c85f
Binary files /dev/null and b/electrum/gui/kivy/theming/light/save.png differ
diff --git a/electrum/gui/kivy/theming/light/settings.png b/electrum/gui/kivy/theming/light/settings.png
new file mode 100644
index 000000000..8cd7dfd28
Binary files /dev/null and b/electrum/gui/kivy/theming/light/settings.png differ
diff --git a/electrum/gui/kivy/theming/light/shadow.png b/electrum/gui/kivy/theming/light/shadow.png
new file mode 100644
index 000000000..9e0ac86bf
Binary files /dev/null and b/electrum/gui/kivy/theming/light/shadow.png differ
diff --git a/electrum/gui/kivy/theming/light/shadow_right.png b/electrum/gui/kivy/theming/light/shadow_right.png
new file mode 100644
index 000000000..c7a777c46
Binary files /dev/null and b/electrum/gui/kivy/theming/light/shadow_right.png differ
diff --git a/electrum/gui/kivy/theming/light/share.png b/electrum/gui/kivy/theming/light/share.png
new file mode 100644
index 000000000..d0dc761d4
Binary files /dev/null and b/electrum/gui/kivy/theming/light/share.png differ
diff --git a/electrum/gui/kivy/theming/light/star_big_inactive.png b/electrum/gui/kivy/theming/light/star_big_inactive.png
new file mode 100644
index 000000000..f246bd45d
Binary files /dev/null and b/electrum/gui/kivy/theming/light/star_big_inactive.png differ
diff --git a/electrum/gui/kivy/theming/light/stepper_full.png b/electrum/gui/kivy/theming/light/stepper_full.png
new file mode 100644
index 000000000..36eb6fd20
Binary files /dev/null and b/electrum/gui/kivy/theming/light/stepper_full.png differ
diff --git a/electrum/gui/kivy/theming/light/stepper_left.png b/electrum/gui/kivy/theming/light/stepper_left.png
new file mode 100644
index 000000000..d96287589
Binary files /dev/null and b/electrum/gui/kivy/theming/light/stepper_left.png differ
diff --git a/electrum/gui/kivy/theming/light/stepper_restore_password.png b/electrum/gui/kivy/theming/light/stepper_restore_password.png
new file mode 100644
index 000000000..322f4813a
Binary files /dev/null and b/electrum/gui/kivy/theming/light/stepper_restore_password.png differ
diff --git a/electrum/gui/kivy/theming/light/stepper_restore_seed.png b/electrum/gui/kivy/theming/light/stepper_restore_seed.png
new file mode 100644
index 000000000..fb493f892
Binary files /dev/null and b/electrum/gui/kivy/theming/light/stepper_restore_seed.png differ
diff --git a/electrum/gui/kivy/theming/light/tab.png b/electrum/gui/kivy/theming/light/tab.png
new file mode 100644
index 000000000..050f9d8bc
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab.png differ
diff --git a/electrum/gui/kivy/theming/light/tab_btn.png b/electrum/gui/kivy/theming/light/tab_btn.png
new file mode 100644
index 000000000..d50c573c1
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab_btn.png differ
diff --git a/electrum/gui/kivy/theming/light/tab_btn_disabled.png b/electrum/gui/kivy/theming/light/tab_btn_disabled.png
new file mode 100644
index 000000000..d50c573c1
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab_btn_disabled.png differ
diff --git a/electrum/gui/kivy/theming/light/tab_btn_pressed.png b/electrum/gui/kivy/theming/light/tab_btn_pressed.png
new file mode 100644
index 000000000..c3cdfb46e
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab_btn_pressed.png differ
diff --git a/electrum/gui/kivy/theming/light/tab_disabled.png b/electrum/gui/kivy/theming/light/tab_disabled.png
new file mode 100644
index 000000000..74c5ffdd2
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab_disabled.png differ
diff --git a/electrum/gui/kivy/theming/light/tab_strip.png b/electrum/gui/kivy/theming/light/tab_strip.png
new file mode 100644
index 000000000..9b87c0ac1
Binary files /dev/null and b/electrum/gui/kivy/theming/light/tab_strip.png differ
diff --git a/electrum/gui/kivy/theming/light/textinput_active.png b/electrum/gui/kivy/theming/light/textinput_active.png
new file mode 100644
index 000000000..4dc0044b8
Binary files /dev/null and b/electrum/gui/kivy/theming/light/textinput_active.png differ
diff --git a/electrum/gui/kivy/theming/light/unconfirmed.png b/electrum/gui/kivy/theming/light/unconfirmed.png
new file mode 100644
index 000000000..d332b978e
Binary files /dev/null and b/electrum/gui/kivy/theming/light/unconfirmed.png differ
diff --git a/electrum/gui/kivy/theming/light/wallet.png b/electrum/gui/kivy/theming/light/wallet.png
new file mode 100644
index 000000000..d740fc7a8
Binary files /dev/null and b/electrum/gui/kivy/theming/light/wallet.png differ
diff --git a/electrum/gui/kivy/theming/light/wallets.png b/electrum/gui/kivy/theming/light/wallets.png
new file mode 100644
index 000000000..86069ca92
Binary files /dev/null and b/electrum/gui/kivy/theming/light/wallets.png differ
diff --git a/electrum/gui/kivy/theming/light/white_bg_round_top.png b/electrum/gui/kivy/theming/light/white_bg_round_top.png
new file mode 100644
index 000000000..897e24bfd
Binary files /dev/null and b/electrum/gui/kivy/theming/light/white_bg_round_top.png differ
diff --git a/electrum/gui/kivy/tools/bitcoin_intent.xml b/electrum/gui/kivy/tools/bitcoin_intent.xml
new file mode 100644
index 000000000..f433f72c1
--- /dev/null
+++ b/electrum/gui/kivy/tools/bitcoin_intent.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/electrum/gui/kivy/tools/blacklist.txt b/electrum/gui/kivy/tools/blacklist.txt
new file mode 100644
index 000000000..6bd911712
--- /dev/null
+++ b/electrum/gui/kivy/tools/blacklist.txt
@@ -0,0 +1,107 @@
+# eggs
+*.egg-info
+
+# unit test
+unittest/*
+
+# python config
+config/makesetup
+
+# unused pygame files
+pygame/_camera_*
+pygame/camera.pyo
+pygame/*.html
+pygame/*.bmp
+pygame/*.svg
+pygame/cdrom.so
+pygame/pygame_icon.icns
+pygame/LGPL
+pygame/threads/Py25Queue.pyo
+pygame/*.ttf
+pygame/mac*
+pygame/_numpy*
+pygame/sndarray.pyo
+pygame/surfarray.pyo
+pygame/_arraysurfarray.pyo
+
+# unused kivy files (platform specific)
+kivy/input/providers/wm_*
+kivy/input/providers/mactouch*
+kivy/input/providers/probesysfs*
+kivy/input/providers/mtdev*
+kivy/input/providers/hidinput*
+kivy/core/camera/camera_videocapture*
+kivy/core/spelling/*osx*
+kivy/core/video/video_pyglet*
+
+kivy/adapters
+kivy/modules
+kivy/uix/sandbox
+kivy/uix/pagelayout
+kivy/uix/video
+kivy/uix/vkeyboard
+kivy/uix/videoplayer
+
+# unused encodings
+lib-dynload/*codec*
+encodings/cp*.pyo
+encodings/tis*
+encodings/shift*
+encodings/bz2*
+encodings/iso*
+encodings/undefined*
+encodings/johab*
+encodings/p*
+encodings/m*
+encodings/euc*
+encodings/k*
+encodings/unicode_internal*
+encodings/quo*
+encodings/gb*
+encodings/big5*
+encodings/hp*
+encodings/hz*
+
+# unused python modules
+bsddb/*
+wsgiref/*
+hotshot/*
+pydoc_data/*
+tty.pyo
+#anydbm.pyo
+nturl2path.pyo
+LICENSE.txt
+macurl2path.pyo
+dummy_threading.pyo
+audiodev.pyo
+antigravity.pyo
+#dumbdbm.pyo
+sndhdr.pyo
+__phello__.foo.pyo
+sunaudio.pyo
+os2emxpath.pyo
+multiprocessing/dummy*
+
+# unused binaries python modules
+lib-dynload/termios.so
+lib-dynload/_lsprof.so
+lib-dynload/*audioop.so
+#lib-dynload/mmap.so
+lib-dynload/_hotshot.so
+#lib-dynload/_csv.so
+lib-dynload/future_builtins.so
+lib-dynload/_heapq.so
+lib-dynload/_json.so
+lib-dynload/grp.so
+lib-dynload/resource.so
+lib-dynload/pyexpat.so
+
+# odd files
+plat-linux3/regen
+
+#>sqlite3
+# conditionnal include depending if some recipes are included or not.
+#sqlite3/*
+#lib-dynload/_sqlite3.so
+# tag
+android.manifest.intent_filters = electrum/gui/kivy/tools/bitcoin_intent.xml
+
+# (str) launchMode to set for the main activity
+android.manifest.launch_mode = singleTask
+
+# (list) Android additionnal libraries to copy into libs/armeabi
+#android.add_libs_armeabi = lib/android/*.so
+
+# (bool) Indicate whether the screen should stay on
+# Don't forget to add the WAKE_LOCK permission if you set this to True
+#android.wakelock = False
+
+# (list) Android application meta-data to set (key=value format)
+#android.meta_data =
+
+# (list) Android library project to add (will be added in the
+# project.properties automatically.)
+#android.library_references =
+
+android.whitelist = lib-dynload/_csv.so
+
+# local version that merges branch 866
+p4a.source_dir = /opt/python-for-android
+
+#
+# iOS specific
+#
+
+# (str) Name of the certificate to use for signing the debug version
+# Get a list of available identities: buildozer ios list_identities
+#ios.codesign.debug = "iPhone Developer: ()"
+
+# (str) Name of the certificate to use for signing the release version
+#ios.codesign.release = %(ios.codesign.debug)s
+
+
+
+[buildozer]
+
+# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
+log_level = 2
+
+
+# -----------------------------------------------------------------------------
+# List as sections
+#
+# You can define all the "list" as [section:key].
+# Each line will be considered as a option to the list.
+# Let's take [app] / source.exclude_patterns.
+# Instead of doing:
+#
+# [app]
+# source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
+#
+# This can be translated into:
+#
+# [app:source.exclude_patterns]
+# license
+# data/audio/*.wav
+# data/images/original/*
+#
+
+# -----------------------------------------------------------------------------
+# Profiles
+#
+# You can extend section / key with a profile
+# For example, you want to deploy a demo version of your application without
+# HD content. You could first change the title to add "(demo)" in the name
+# and extend the excluded directories to remove the HD content.
+#
+# [app@demo]
+# title = My Application (demo)
+#
+# [app:source.exclude_patterns@demo]
+# images/hd/*
+#
+# Then, invoke the command line with the "demo" profile:
+#
+# buildozer --profile demo android debug
diff --git a/electrum/gui/kivy/uix/__init__.py b/electrum/gui/kivy/uix/__init__.py
new file mode 100644
index 000000000..8d1c8b69c
--- /dev/null
+++ b/electrum/gui/kivy/uix/__init__.py
@@ -0,0 +1 @@
+
diff --git a/electrum/gui/kivy/uix/combobox.py b/electrum/gui/kivy/uix/combobox.py
new file mode 100644
index 000000000..26e9f1f63
--- /dev/null
+++ b/electrum/gui/kivy/uix/combobox.py
@@ -0,0 +1,93 @@
+'''
+ComboBox
+=======
+
+Based on Spinner
+'''
+
+__all__ = ('ComboBox', 'ComboBoxOption')
+
+from kivy.properties import ListProperty, ObjectProperty, BooleanProperty
+from kivy.uix.button import Button
+from kivy.uix.dropdown import DropDown
+from kivy.lang import Builder
+
+
+Builder.load_string('''
+:
+ size_hint_y: None
+ height: 44
+
+:
+ background_normal: 'atlas://data/images/defaulttheme/spinner'
+ background_down: 'atlas://data/images/defaulttheme/spinner_pressed'
+ on_key:
+ if self.items: x, y = zip(*self.items); self.text = y[x.index(args[1])]
+''')
+
+
+class ComboBoxOption(Button):
+ pass
+
+
+class ComboBox(Button):
+ items = ListProperty()
+ key = ObjectProperty()
+
+ option_cls = ObjectProperty(ComboBoxOption)
+
+ dropdown_cls = ObjectProperty(DropDown)
+
+ is_open = BooleanProperty(False)
+
+ def __init__(self, **kwargs):
+ self._dropdown = None
+ super(ComboBox, self).__init__(**kwargs)
+ self.items_dict = dict(self.items)
+ self.bind(
+ on_release=self._toggle_dropdown,
+ dropdown_cls=self._build_dropdown,
+ option_cls=self._build_dropdown,
+ items=self._update_dropdown,
+ key=self._update_text)
+ self._build_dropdown()
+ self._update_text()
+
+ def _update_text(self, *largs):
+ try:
+ self.text = self.items_dict[self.key]
+ except KeyError:
+ pass
+
+ def _build_dropdown(self, *largs):
+ if self._dropdown:
+ self._dropdown.unbind(on_select=self._on_dropdown_select)
+ self._dropdown.dismiss()
+ self._dropdown = None
+ self._dropdown = self.dropdown_cls()
+ self._dropdown.bind(on_select=self._on_dropdown_select)
+ self._update_dropdown()
+
+ def _update_dropdown(self, *largs):
+ dp = self._dropdown
+ cls = self.option_cls
+ dp.clear_widgets()
+ for key, value in self.items:
+ item = cls(text=value)
+ # extra attribute
+ item.key = key
+ item.bind(on_release=lambda option: dp.select(option.key))
+ dp.add_widget(item)
+
+ def _toggle_dropdown(self, *largs):
+ self.is_open = not self.is_open
+
+ def _on_dropdown_select(self, instance, data, *largs):
+ self.key = data
+ self.is_open = False
+
+ def on_is_open(self, instance, value):
+ if value:
+ self._dropdown.open(self)
+ else:
+ self._dropdown.dismiss()
diff --git a/electrum/gui/kivy/uix/context_menu.py b/electrum/gui/kivy/uix/context_menu.py
new file mode 100644
index 000000000..84d5ba647
--- /dev/null
+++ b/electrum/gui/kivy/uix/context_menu.py
@@ -0,0 +1,56 @@
+#!python
+#!/usr/bin/env python
+from kivy.app import App
+from kivy.uix.bubble import Bubble
+from kivy.animation import Animation
+from kivy.uix.floatlayout import FloatLayout
+from kivy.lang import Builder
+from kivy.factory import Factory
+from kivy.clock import Clock
+
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+
+ background_normal: ''
+ background_color: (0.192, .498, 0.745, 1)
+ height: '48dp'
+ size_hint: 1, None
+
+
+ size_hint: 1, None
+ height: '48dp'
+ pos: (0, 0)
+ show_arrow: False
+ arrow_pos: 'top_mid'
+ padding: 0
+ orientation: 'horizontal'
+ BoxLayout:
+ size_hint: 1, 1
+ height: '48dp'
+ padding: '12dp', '0dp'
+ spacing: '3dp'
+ orientation: 'horizontal'
+ id: buttons
+''')
+
+
+class MenuItem(Factory.Button):
+ pass
+
+class ContextMenu(Bubble):
+
+ def __init__(self, obj, action_list):
+ Bubble.__init__(self)
+ self.obj = obj
+ for k, v in action_list:
+ l = MenuItem()
+ l.text = _(k)
+ def func(f=v):
+ Clock.schedule_once(lambda dt: f(obj), 0.15)
+ l.on_release = func
+ self.ids.buttons.add_widget(l)
+
+ def hide(self):
+ if self.parent:
+ self.parent.hide_menu()
diff --git a/electrum/gui/kivy/uix/dialogs/__init__.py b/electrum/gui/kivy/uix/dialogs/__init__.py
new file mode 100644
index 000000000..352675a37
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/__init__.py
@@ -0,0 +1,220 @@
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.factory import Factory
+from kivy.properties import NumericProperty, StringProperty, BooleanProperty
+from kivy.core.window import Window
+from kivy.uix.recycleview import RecycleView
+from kivy.uix.boxlayout import BoxLayout
+
+from electrum.gui.kivy.i18n import _
+
+
+
+class AnimatedPopup(Factory.Popup):
+ ''' An Animated Popup that animates in and out.
+ '''
+
+ anim_duration = NumericProperty(.36)
+ '''Duration of animation to be used
+ '''
+
+ __events__ = ['on_activate', 'on_deactivate']
+
+
+ def on_activate(self):
+ '''Base function to be overridden on inherited classes.
+ Called when the popup is done animating.
+ '''
+ pass
+
+ def on_deactivate(self):
+ '''Base function to be overridden on inherited classes.
+ Called when the popup is done animating.
+ '''
+ pass
+
+ def open(self):
+ '''Do the initialization of incoming animation here.
+ Override to set your custom animation.
+ '''
+ def on_complete(*l):
+ self.dispatch('on_activate')
+
+ self.opacity = 0
+ super(AnimatedPopup, self).open()
+ anim = Factory.Animation(opacity=1, d=self.anim_duration)
+ anim.bind(on_complete=on_complete)
+ anim.start(self)
+
+ def dismiss(self):
+ '''Do the initialization of incoming animation here.
+ Override to set your custom animation.
+ '''
+ def on_complete(*l):
+ super(AnimatedPopup, self).dismiss()
+ self.dispatch('on_deactivate')
+
+ anim = Factory.Animation(opacity=0, d=.25)
+ anim.bind(on_complete=on_complete)
+ anim.start(self)
+
+class EventsDialog(Factory.Popup):
+ ''' Abstract Popup that provides the following events
+ .. events::
+ `on_release`
+ `on_press`
+ '''
+
+ __events__ = ('on_release', 'on_press')
+
+ def __init__(self, **kwargs):
+ super(EventsDialog, self).__init__(**kwargs)
+
+ def on_release(self, instance):
+ pass
+
+ def on_press(self, instance):
+ pass
+
+ def close(self):
+ self.dismiss()
+
+
+class SelectionDialog(EventsDialog):
+
+ def add_widget(self, widget, index=0):
+ if self.content:
+ self.content.add_widget(widget, index)
+ return
+ super(SelectionDialog, self).add_widget(widget)
+
+
+class InfoBubble(Factory.Bubble):
+ '''Bubble to be used to display short Help Information'''
+
+ message = StringProperty(_('Nothing set !'))
+ '''Message to be displayed; defaults to "nothing set"'''
+
+ icon = StringProperty('')
+ ''' Icon to be displayed along with the message defaults to ''
+
+ :attr:`icon` is a `StringProperty` defaults to `''`
+ '''
+
+ fs = BooleanProperty(False)
+ ''' Show Bubble in half screen mode
+
+ :attr:`fs` is a `BooleanProperty` defaults to `False`
+ '''
+
+ modal = BooleanProperty(False)
+ ''' Allow bubble to be hidden on touch.
+
+ :attr:`modal` is a `BooleanProperty` defauult to `False`.
+ '''
+
+ exit = BooleanProperty(False)
+ '''Indicates whether to exit app after bubble is closed.
+
+ :attr:`exit` is a `BooleanProperty` defaults to False.
+ '''
+
+ dim_background = BooleanProperty(False)
+ ''' Indicates Whether to draw a background on the windows behind the bubble.
+
+ :attr:`dim` is a `BooleanProperty` defaults to `False`.
+ '''
+
+ def on_touch_down(self, touch):
+ if self.modal:
+ return True
+ self.hide()
+ if self.collide_point(*touch.pos):
+ return True
+
+ def show(self, pos, duration, width=None, modal=False, exit=False):
+ '''Animate the bubble into position'''
+ self.modal, self.exit = modal, exit
+ if width:
+ self.width = width
+ if self.modal:
+ from kivy.uix.modalview import ModalView
+ self._modal_view = m = ModalView(background_color=[.5, .5, .5, .2])
+ Window.add_widget(m)
+ m.add_widget(self)
+ else:
+ Window.add_widget(self)
+
+ # wait for the bubble to adjust its size according to text then animate
+ Clock.schedule_once(lambda dt: self._show(pos, duration))
+
+ def _show(self, pos, duration):
+
+ def on_stop(*l):
+ if duration:
+ Clock.schedule_once(self.hide, duration + .5)
+
+ self.opacity = 0
+ arrow_pos = self.arrow_pos
+ if arrow_pos[0] in ('l', 'r'):
+ pos = pos[0], pos[1] - (self.height/2)
+ else:
+ pos = pos[0] - (self.width/2), pos[1]
+
+ self.limit_to = Window
+
+ anim = Factory.Animation(opacity=1, pos=pos, d=.32)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
+
+
+ def hide(self, now=False):
+ ''' Auto fade out the Bubble
+ '''
+ def on_stop(*l):
+ if self.modal:
+ m = self._modal_view
+ m.remove_widget(self)
+ Window.remove_widget(m)
+ Window.remove_widget(self)
+ if self.exit:
+ App.get_running_app().stop()
+ import sys
+ sys.exit()
+ else:
+ App.get_running_app().is_exit = False
+
+ if now:
+ return on_stop()
+
+ anim = Factory.Animation(opacity=0, d=.25)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
+
+
+
+class OutputItem(BoxLayout):
+ pass
+
+class OutputList(RecycleView):
+
+ def __init__(self, **kwargs):
+ super(OutputList, self).__init__(**kwargs)
+ self.app = App.get_running_app()
+
+ def update(self, outputs):
+ res = []
+ for o in outputs:
+ value = self.app.format_amount_and_units(o.value)
+ res.append({'address': o.address, 'value': value})
+ self.data = res
+
+
+class TopLabel(Factory.Label):
+ pass
+
+
+class RefLabel(TopLabel):
+ pass
diff --git a/electrum/gui/kivy/uix/dialogs/addresses.py b/electrum/gui/kivy/uix/dialogs/addresses.py
new file mode 100644
index 000000000..b1fc40cd0
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/addresses.py
@@ -0,0 +1,180 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from decimal import Decimal
+
+Builder.load_string('''
+
+ text_size: self.width, None
+ halign: 'left'
+ valign: 'top'
+
+
+ address: ''
+ memo: ''
+ amount: ''
+ status: ''
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ AddressLabel:
+ text: root.address
+ shorten: True
+ Widget
+ AddressLabel:
+ text: (root.amount if root.status == 'Funded' else root.status) + ' ' + root.memo
+ color: .699, .699, .699, 1
+ font_size: '13sp'
+ shorten: True
+ Widget
+
+
+ id: popup
+ title: _('Addresses')
+ message: ''
+ pr_status: 'Pending'
+ show_change: 0
+ show_used: 0
+ on_message:
+ self.update()
+ BoxLayout:
+ id:box
+ padding: '12dp', '70dp', '12dp', '12dp'
+ spacing: '12dp'
+ orientation: 'vertical'
+ size_hint: 1, 1.1
+ BoxLayout:
+ spacing: '6dp'
+ size_hint: 1, None
+ orientation: 'horizontal'
+ AddressFilter:
+ opacity: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '5dp'
+ AddressButton:
+ id: search
+ text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change]
+ on_release:
+ root.show_change = (root.show_change + 1) % 3
+ Clock.schedule_once(lambda dt: root.update())
+ AddressFilter:
+ opacity: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '5dp'
+ AddressButton:
+ id: search
+ text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used]
+ on_release:
+ root.show_used = (root.show_used + 1) % 4
+ Clock.schedule_once(lambda dt: root.update())
+ AddressFilter:
+ opacity: 1
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '5dp'
+ canvas.before:
+ Color:
+ rgba: 0.9, 0.9, 0.9, 1
+ AddressButton:
+ id: change
+ text: root.message if root.message else _('Search')
+ on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup))
+ RecycleView:
+ scroll_type: ['bars', 'content']
+ bar_width: '15dp'
+ viewclass: 'AddressItem'
+ id: search_container
+ RecycleBoxLayout:
+ orientation: 'vertical'
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+''')
+
+
+from electrum.gui.kivy.i18n import _
+from electrum.gui.kivy.uix.context_menu import ContextMenu
+
+
+class AddressesDialog(Factory.Popup):
+
+ def __init__(self, app, screen, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.screen = screen
+ self.callback = callback
+ self.context_menu = None
+
+ def get_card(self, addr, balance, is_used, label):
+ ci = {}
+ ci['screen'] = self
+ ci['address'] = addr
+ ci['memo'] = label
+ ci['amount'] = self.app.format_amount_and_units(balance)
+ ci['status'] = _('Used') if is_used else _('Funded') if balance > 0 else _('Unused')
+ return ci
+
+ def update(self):
+ self.menu_actions = [(_('Use'), self.do_use), (_('Details'), self.do_view)]
+ wallet = self.app.wallet
+ if self.show_change == 0:
+ _list = wallet.get_receiving_addresses()
+ elif self.show_change == 1:
+ _list = wallet.get_change_addresses()
+ else:
+ _list = wallet.get_addresses()
+ search = self.message
+ container = self.ids.search_container
+ n = 0
+ cards = []
+ for address in _list:
+ label = wallet.labels.get(address, '')
+ balance = sum(wallet.get_addr_balance(address))
+ is_used_and_empty = wallet.is_used(address) and balance == 0
+ if self.show_used == 1 and (balance or is_used_and_empty):
+ continue
+ if self.show_used == 2 and balance == 0:
+ continue
+ if self.show_used == 3 and not is_used_and_empty:
+ continue
+ card = self.get_card(address, balance, is_used_and_empty, label)
+ if search and not self.ext_search(card, search):
+ continue
+ cards.append(card)
+ n += 1
+ container.data = cards
+ if not n:
+ self.app.show_error('No address matching your search')
+
+ def do_use(self, obj):
+ self.hide_menu()
+ self.dismiss()
+ self.app.show_request(obj.address)
+
+ def do_view(self, obj):
+ req = { 'address': obj.address, 'status' : obj.status }
+ status = obj.status
+ c, u, x = self.app.wallet.get_addr_balance(obj.address)
+ balance = c + u + x
+ if balance > 0:
+ req['fund'] = balance
+ self.app.show_addr_details(req, status)
+
+ def ext_search(self, card, search):
+ return card['memo'].find(search) >= 0 or card['amount'].find(search) >= 0
+
+ def show_menu(self, obj):
+ self.hide_menu()
+ self.context_menu = ContextMenu(obj, self.menu_actions)
+ self.ids.box.add_widget(self.context_menu)
+
+ def hide_menu(self):
+ if self.context_menu is not None:
+ self.ids.box.remove_widget(self.context_menu)
+ self.context_menu = None
diff --git a/electrum/gui/kivy/uix/dialogs/amount_dialog.py b/electrum/gui/kivy/uix/dialogs/amount_dialog.py
new file mode 100644
index 000000000..244f8e61d
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/amount_dialog.py
@@ -0,0 +1,148 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from decimal import Decimal
+
+Builder.load_string('''
+
+
+ id: popup
+ title: _('Amount')
+ AnchorLayout:
+ anchor_x: 'center'
+ BoxLayout:
+ orientation: 'vertical'
+ size_hint: 0.9, 1
+ Widget:
+ size_hint: 1, 0.2
+ BoxLayout:
+ size_hint: 1, None
+ height: '80dp'
+ Button:
+ background_color: 0, 0, 0, 0
+ id: btc
+ text: kb.amount + ' ' + app.base_unit
+ color: (0.7, 0.7, 1, 1) if kb.is_fiat else (1, 1, 1, 1)
+ halign: 'right'
+ size_hint: 1, None
+ font_size: '20dp'
+ height: '48dp'
+ on_release:
+ kb.is_fiat = False
+ Button:
+ background_color: 0, 0, 0, 0
+ id: fiat
+ text: kb.fiat_amount + ' ' + app.fiat_unit
+ color: (1, 1, 1, 1) if kb.is_fiat else (0.7, 0.7, 1, 1)
+ halign: 'right'
+ size_hint: 1, None
+ font_size: '20dp'
+ height: '48dp'
+ disabled: not app.fx.is_enabled()
+ on_release:
+ kb.is_fiat = True
+ Widget:
+ size_hint: 1, 0.2
+ GridLayout:
+ id: kb
+ amount: ''
+ fiat_amount: ''
+ is_fiat: False
+ on_fiat_amount: if self.is_fiat: self.amount = app.fiat_to_btc(self.fiat_amount)
+ on_amount: if not self.is_fiat: self.fiat_amount = app.btc_to_fiat(self.amount)
+ size_hint: 1, None
+ update_amount: popup.update_amount
+ height: '300dp'
+ cols: 3
+ KButton:
+ text: '1'
+ KButton:
+ text: '2'
+ KButton:
+ text: '3'
+ KButton:
+ text: '4'
+ KButton:
+ text: '5'
+ KButton:
+ text: '6'
+ KButton:
+ text: '7'
+ KButton:
+ text: '8'
+ KButton:
+ text: '9'
+ KButton:
+ text: '.'
+ KButton:
+ text: '0'
+ KButton:
+ text: '<'
+ Widget:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ id: but_max
+ opacity: 1 if root.show_max else 0
+ disabled: not root.show_max
+ size_hint: 1, None
+ height: '48dp'
+ text: 'Max'
+ on_release:
+ kb.is_fiat = False
+ kb.amount = app.get_max_amount()
+ Button:
+ size_hint: 1, None
+ height: '48dp'
+ text: 'Clear'
+ on_release:
+ kb.amount = ''
+ kb.fiat_amount = ''
+ Widget:
+ size_hint: 1, 0.2
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Widget:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ size_hint: 1, None
+ height: '48dp'
+ text: _('OK')
+ on_release:
+ root.callback(btc.text if kb.amount else '')
+ popup.dismiss()
+''')
+
+from kivy.properties import BooleanProperty
+
+class AmountDialog(Factory.Popup):
+ show_max = BooleanProperty(False)
+ def __init__(self, show_max, amount, cb):
+ Factory.Popup.__init__(self)
+ self.show_max = show_max
+ self.callback = cb
+ if amount:
+ self.ids.kb.amount = amount
+
+ def update_amount(self, c):
+ kb = self.ids.kb
+ amount = kb.fiat_amount if kb.is_fiat else kb.amount
+ if c == '<':
+ amount = amount[:-1]
+ elif c == '.' and amount in ['0', '']:
+ amount = '0.'
+ elif amount == '0':
+ amount = c
+ else:
+ try:
+ Decimal(amount+c)
+ amount += c
+ except:
+ pass
+ if kb.is_fiat:
+ kb.fiat_amount = amount
+ else:
+ kb.amount = amount
diff --git a/electrum/gui/kivy/uix/dialogs/bump_fee_dialog.py b/electrum/gui/kivy/uix/dialogs/bump_fee_dialog.py
new file mode 100644
index 000000000..854be26bd
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/bump_fee_dialog.py
@@ -0,0 +1,118 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+
+ title: _('Bump fee')
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '10dp'
+
+ GridLayout:
+ height: self.minimum_height
+ size_hint_y: None
+ cols: 1
+ spacing: '10dp'
+ BoxLabel:
+ id: old_fee
+ text: _('Current Fee')
+ value: ''
+ BoxLabel:
+ id: new_fee
+ text: _('New Fee')
+ value: ''
+ Label:
+ id: tooltip1
+ text: ''
+ size_hint_y: None
+ Label:
+ id: tooltip2
+ text: ''
+ size_hint_y: None
+ Slider:
+ id: slider
+ range: 0, 4
+ step: 1
+ on_value: root.on_slider(self.value)
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Label:
+ text: _('Final')
+ CheckBox:
+ id: final_cb
+ Widget:
+ size_hint: 1, 1
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: root.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.dismiss()
+ root.on_ok()
+''')
+
+class BumpFeeDialog(Factory.Popup):
+
+ def __init__(self, app, fee, size, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.init_fee = fee
+ self.tx_size = size
+ self.callback = callback
+ self.config = app.electrum_config
+ self.mempool = self.config.use_mempool_fees()
+ self.dynfees = self.config.is_dynfee() and bool(self.app.network) and self.config.has_dynamic_fees_ready()
+ self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee)
+ self.update_slider()
+ self.update_text()
+
+ def update_text(self):
+ fee = self.get_fee()
+ self.ids.new_fee.value = self.app.format_amount_and_units(fee)
+ pos = int(self.ids.slider.value)
+ fee_rate = self.get_fee_rate()
+ text, tooltip = self.config.get_fee_text(pos, self.dynfees, self.mempool, fee_rate)
+ self.ids.tooltip1.text = text
+ self.ids.tooltip2.text = tooltip
+
+ def update_slider(self):
+ slider = self.ids.slider
+ maxp, pos, fee_rate = self.config.get_fee_slider(self.dynfees, self.mempool)
+ slider.range = (0, maxp)
+ slider.step = 1
+ slider.value = pos
+
+ def get_fee_rate(self):
+ pos = int(self.ids.slider.value)
+ if self.dynfees:
+ fee_rate = self.config.depth_to_fee(pos) if self.mempool else self.config.eta_to_fee(pos)
+ else:
+ fee_rate = self.config.static_fee(pos)
+ return fee_rate
+
+ def get_fee(self):
+ fee_rate = self.get_fee_rate()
+ return int(fee_rate * self.tx_size // 1000)
+
+ def on_ok(self):
+ new_fee = self.get_fee()
+ is_final = self.ids.final_cb.active
+ self.callback(self.init_fee, new_fee, is_final)
+
+ def on_slider(self, value):
+ self.update_text()
diff --git a/electrum/gui/kivy/uix/dialogs/checkbox_dialog.py b/electrum/gui/kivy/uix/dialogs/checkbox_dialog.py
new file mode 100644
index 000000000..dfdf7b62c
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/checkbox_dialog.py
@@ -0,0 +1,52 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+Builder.load_string('''
+
+ id: popup
+ title: ''
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ Label:
+ id: description
+ text: ''
+ halign: 'left'
+ text_size: self.width, None
+ size: self.texture_size
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Label:
+ text: _('Enable')
+ CheckBox:
+ id:cb
+ Widget:
+ size_hint: 1, 0.1
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(cb.active)
+ popup.dismiss()
+''')
+
+class CheckBoxDialog(Factory.Popup):
+ def __init__(self, title, text, status, callback):
+ Factory.Popup.__init__(self)
+ self.ids.cb.active = status
+ self.ids.description.text = text
+ self.callback = callback
+ self.title = title
diff --git a/electrum/gui/kivy/uix/dialogs/choice_dialog.py b/electrum/gui/kivy/uix/dialogs/choice_dialog.py
new file mode 100644
index 000000000..bf6905e4f
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/choice_dialog.py
@@ -0,0 +1,77 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from kivy.uix.checkbox import CheckBox
+from kivy.uix.label import Label
+from kivy.uix.widget import Widget
+
+Builder.load_string('''
+
+ id: popup
+ title: ''
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ Widget:
+ size_hint: 1, 0.1
+ ScrollView:
+ orientation: 'vertical'
+ size_hint: 1, 0.8
+ GridLayout:
+ row_default_height: '48dp'
+ orientation: 'vertical'
+ id: choices
+ cols: 2
+ size_hint: 1, None
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(popup.value)
+ popup.dismiss()
+''')
+
+class ChoiceDialog(Factory.Popup):
+
+ def __init__(self, title, choices, key, callback, keep_choice_order=False):
+ Factory.Popup.__init__(self)
+ print(choices, type(choices))
+ if keep_choice_order:
+ orig_index = {choice: i for (i, choice) in enumerate(choices)}
+ sort_key = lambda x: orig_index[x[0]]
+ else:
+ sort_key = lambda x: x
+ if type(choices) is list:
+ choices = dict(map(lambda x: (x,x), choices))
+ layout = self.ids.choices
+ layout.bind(minimum_height=layout.setter('height'))
+ for k, v in sorted(choices.items(), key=sort_key):
+ l = Label(text=v)
+ l.height = '48dp'
+ l.size_hint_x = 4
+ cb = CheckBox(group='choices')
+ cb.value = k
+ cb.height = '48dp'
+ cb.size_hint_x = 1
+ def f(cb, x):
+ if x: self.value = cb.value
+ cb.bind(active=f)
+ if k == key:
+ cb.active = True
+ layout.add_widget(l)
+ layout.add_widget(cb)
+ layout.add_widget(Widget(size_hint_y=1))
+ self.callback = callback
+ self.title = title
+ self.value = key
diff --git a/electrum/gui/kivy/uix/dialogs/crash_reporter.py b/electrum/gui/kivy/uix/dialogs/crash_reporter.py
new file mode 100644
index 000000000..04582b953
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/crash_reporter.py
@@ -0,0 +1,193 @@
+import sys
+
+import requests
+from kivy import base, utils
+from kivy.clock import Clock
+from kivy.core.window import Window
+from kivy.factory import Factory
+from kivy.lang import Builder
+from kivy.uix.label import Label
+from kivy.utils import platform
+
+from electrum.gui.kivy.i18n import _
+
+from electrum.base_crash_reporter import BaseCrashReporter
+
+
+Builder.load_string('''
+
+ BoxLayout:
+ orientation: 'vertical'
+ Label:
+ id: crash_message
+ text_size: root.width, None
+ size: self.texture_size
+ size_hint: None, None
+ Label:
+ id: request_help_message
+ text_size: root.width*.95, None
+ size: self.texture_size
+ size_hint: None, None
+ BoxLayout:
+ size_hint: 1, 0.1
+ Button:
+ text: 'Show report contents'
+ height: '48dp'
+ size_hint: 1, None
+ on_press: root.show_contents()
+ BoxLayout:
+ size_hint: 1, 0.1
+ Label:
+ id: describe_error_message
+ text_size: root.width, None
+ size: self.texture_size
+ size_hint: None, None
+ TextInput:
+ id: user_message
+ size_hint: 1, 0.3
+ BoxLayout:
+ size_hint: 1, 0.7
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ orientation: 'horizontal'
+ Button:
+ height: '48dp'
+ text: 'Send'
+ on_release: root.send_report()
+ Button:
+ text: 'Never'
+ on_release: root.show_never()
+ Button:
+ text: 'Not now'
+ on_release: root.dismiss()
+
+
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ do_scroll_x: False
+ Label:
+ id: contents
+ text_size: root.width*.9, None
+ size: self.texture_size
+ size_hint: None, None
+ Button:
+ text: 'Close'
+ height: '48dp'
+ size_hint: 1, None
+ on_release: root.dismiss()
+''')
+
+
+class CrashReporter(BaseCrashReporter, Factory.Popup):
+ issue_template = """[b]Traceback[/b]
+
+[i]{traceback}[/i]
+
+
+[b]Additional information[/b]
+ * Electrum version: {app_version}
+ * Operating system: {os}
+ * Wallet type: {wallet_type}
+ * Locale: {locale}
+ """
+
+ def __init__(self, main_window, exctype, value, tb):
+ BaseCrashReporter.__init__(self, exctype, value, tb)
+ Factory.Popup.__init__(self)
+ self.main_window = main_window
+ self.title = BaseCrashReporter.CRASH_TITLE
+ self.title_size = "24sp"
+ self.ids.crash_message.text = BaseCrashReporter.CRASH_MESSAGE
+ self.ids.request_help_message.text = BaseCrashReporter.REQUEST_HELP_MESSAGE
+ self.ids.describe_error_message.text = BaseCrashReporter.DESCRIBE_ERROR_MESSAGE
+
+ def show_contents(self):
+ details = CrashReportDetails(self.get_report_string())
+ details.open()
+
+ def show_popup(self, title, content):
+ popup = Factory.Popup(title=title,
+ content=Label(text=content, text_size=(Window.size[0] * 3/4, None)),
+ size_hint=(3/4, 3/4))
+ popup.open()
+
+ def send_report(self):
+ try:
+ response = BaseCrashReporter.send_report(self, "/crash.json").json()
+ except requests.exceptions.RequestException:
+ self.show_popup(_('Unable to send report'), _("Please check your network connection."))
+ else:
+ self.show_popup(_('Report sent'), response["text"])
+ if response["location"]:
+ self.open_url(response["location"])
+ self.dismiss()
+
+ def open_url(self, url):
+ if platform != 'android':
+ return
+ from jnius import autoclass, cast
+ String = autoclass("java.lang.String")
+ url = String(url)
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+ activity = PythonActivity.mActivity
+ Intent = autoclass('android.content.Intent')
+ Uri = autoclass('android.net.Uri')
+ browserIntent = Intent()
+ # This line crashes the app:
+ # browserIntent.setAction(Intent.ACTION_VIEW)
+ # Luckily we don't need it because the OS is smart enough to recognize the URL
+ browserIntent.setData(Uri.parse(url))
+ currentActivity = cast('android.app.Activity', activity)
+ currentActivity.startActivity(browserIntent)
+
+ def show_never(self):
+ self.main_window.electrum_config.set_key(BaseCrashReporter.config_key, False)
+ self.dismiss()
+
+ def get_user_description(self):
+ return self.ids.user_message.text
+
+ def get_wallet_type(self):
+ return self.main_window.wallet.wallet_type
+
+ def get_os_version(self):
+ if utils.platform is not "android":
+ return utils.platform
+ import jnius
+ bv = jnius.autoclass('android.os.Build$VERSION')
+ b = jnius.autoclass('android.os.Build')
+ return "Android {} on {} {} ({})".format(bv.RELEASE, b.BRAND, b.DEVICE, b.DISPLAY)
+
+
+class CrashReportDetails(Factory.Popup):
+ def __init__(self, text):
+ Factory.Popup.__init__(self)
+ self.title = "Report Details"
+ self.ids.contents.text = text
+ print(text)
+
+
+class ExceptionHook(base.ExceptionHandler):
+ def __init__(self, main_window):
+ super().__init__()
+ self.main_window = main_window
+ if not main_window.electrum_config.get(BaseCrashReporter.config_key, default=True):
+ return
+ # For exceptions in Kivy:
+ base.ExceptionManager.add_handler(self)
+ # For everything else:
+ sys.excepthook = lambda exctype, value, tb: self.handle_exception(value)
+
+ def handle_exception(self, _inst):
+ exc_info = sys.exc_info()
+ # Check if this is an exception from within the exception handler:
+ import traceback
+ for item in traceback.extract_tb(exc_info[2]):
+ if item.filename.endswith("crash_reporter.py"):
+ return
+ e = CrashReporter(self.main_window, *exc_info)
+ # Open in main thread:
+ Clock.schedule_once(lambda _: e.open(), 0)
+ return base.ExceptionManager.PASS
diff --git a/electrum/gui/kivy/uix/dialogs/fee_dialog.py b/electrum/gui/kivy/uix/dialogs/fee_dialog.py
new file mode 100644
index 000000000..7c6293973
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/fee_dialog.py
@@ -0,0 +1,131 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+
+ id: popup
+ title: _('Transaction Fees')
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ method: 0
+ BoxLayout:
+ orientation: 'vertical'
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ Label:
+ text: _('Method') + ':'
+ Button:
+ text: _('Mempool') if root.method == 2 else _('ETA') if root.method == 1 else _('Static')
+ background_color: (0,0,0,0)
+ bold: True
+ on_release:
+ root.method = (root.method + 1) % 3
+ root.update_slider()
+ root.update_text()
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ Label:
+ text: (_('Target') if root.method > 0 else _('Fee')) + ':'
+ Label:
+ id: fee_target
+ text: ''
+ Slider:
+ id: slider
+ range: 0, 4
+ step: 1
+ on_value: root.on_slider(self.value)
+ Widget:
+ size_hint: 1, 0.5
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ TopLabel:
+ id: fee_estimate
+ text: ''
+ font_size: '14dp'
+ Widget:
+ size_hint: 1, 0.5
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.on_ok()
+ root.dismiss()
+''')
+
+class FeeDialog(Factory.Popup):
+
+ def __init__(self, app, config, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.config = config
+ self.callback = callback
+ mempool = self.config.use_mempool_fees()
+ dynfees = self.config.is_dynfee()
+ self.method = (2 if mempool else 1) if dynfees else 0
+ self.update_slider()
+ self.update_text()
+
+ def update_text(self):
+ pos = int(self.ids.slider.value)
+ dynfees, mempool = self.get_method()
+ if self.method == 2:
+ fee_rate = self.config.depth_to_fee(pos)
+ target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
+ msg = 'In the current network conditions, in order to be positioned %s, a transaction will require a fee of %s.' % (target, estimate)
+ elif self.method == 1:
+ fee_rate = self.config.eta_to_fee(pos)
+ target, estimate = self.config.get_fee_text(pos, dynfees, mempool, fee_rate)
+ msg = 'In the last few days, transactions that confirmed %s usually paid a fee of at least %s.' % (target.lower(), estimate)
+ else:
+ fee_rate = self.config.static_fee(pos)
+ target, estimate = self.config.get_fee_text(pos, dynfees, True, fee_rate)
+ msg = 'In the current network conditions, a transaction paying %s would be positioned %s.' % (target, estimate)
+
+ self.ids.fee_target.text = target
+ self.ids.fee_estimate.text = msg
+
+ def get_method(self):
+ dynfees = self.method > 0
+ mempool = self.method == 2
+ return dynfees, mempool
+
+ def update_slider(self):
+ slider = self.ids.slider
+ dynfees, mempool = self.get_method()
+ maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool)
+ slider.range = (0, maxp)
+ slider.step = 1
+ slider.value = pos
+
+ def on_ok(self):
+ value = int(self.ids.slider.value)
+ dynfees, mempool = self.get_method()
+ self.config.set_key('dynamic_fees', dynfees, False)
+ self.config.set_key('mempool_fees', mempool, False)
+ if dynfees:
+ if mempool:
+ self.config.set_key('depth_level', value, True)
+ else:
+ self.config.set_key('fee_level', value, True)
+ else:
+ self.config.set_key('fee_per_kb', self.config.static_fee(value), True)
+ self.callback()
+
+ def on_slider(self, value):
+ self.update_text()
diff --git a/electrum/gui/kivy/uix/dialogs/fx_dialog.py b/electrum/gui/kivy/uix/dialogs/fx_dialog.py
new file mode 100644
index 000000000..fce858290
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/fx_dialog.py
@@ -0,0 +1,111 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+Builder.load_string('''
+
+ id: popup
+ title: 'Fiat Currency'
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+
+ Widget:
+ size_hint: 1, 0.1
+
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.1
+ Label:
+ text: _('Currency')
+ height: '48dp'
+ Spinner:
+ height: '48dp'
+ id: ccy
+ on_text: popup.on_currency(self.text)
+
+ Widget:
+ size_hint: 1, 0.1
+
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.1
+ Label:
+ text: _('Source')
+ height: '48dp'
+ Spinner:
+ height: '48dp'
+ id: exchanges
+ on_text: popup.on_exchange(self.text)
+
+ Widget:
+ size_hint: 1, 0.2
+
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback()
+ popup.dismiss()
+''')
+
+
+from kivy.uix.label import Label
+from kivy.uix.checkbox import CheckBox
+from kivy.uix.widget import Widget
+from kivy.clock import Clock
+
+from electrum.gui.kivy.i18n import _
+from functools import partial
+
+class FxDialog(Factory.Popup):
+
+ def __init__(self, app, plugins, config, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.config = config
+ self.callback = callback
+ self.fx = self.app.fx
+ self.fx.set_history_config(True)
+ self.add_currencies()
+
+ def add_exchanges(self):
+ exchanges = sorted(self.fx.get_exchanges_by_ccy(self.fx.get_currency(), True)) if self.fx.is_enabled() else []
+ mx = self.fx.exchange.name() if self.fx.is_enabled() else ''
+ ex = self.ids.exchanges
+ ex.values = exchanges
+ ex.text = (mx if mx in exchanges else exchanges[0]) if self.fx.is_enabled() else ''
+
+ def on_exchange(self, text):
+ if not text:
+ return
+ if self.fx.is_enabled() and text != self.fx.exchange.name():
+ self.fx.set_exchange(text)
+
+ def add_currencies(self):
+ currencies = [_('None')] + self.fx.get_currencies(True)
+ my_ccy = self.fx.get_currency() if self.fx.is_enabled() else _('None')
+ self.ids.ccy.values = currencies
+ self.ids.ccy.text = my_ccy
+
+ def on_currency(self, ccy):
+ b = (ccy != _('None'))
+ self.fx.set_enabled(b)
+ if b:
+ if ccy != self.fx.get_currency():
+ self.fx.set_currency(ccy)
+ self.app.fiat_unit = ccy
+ else:
+ self.app.is_fiat = False
+ Clock.schedule_once(lambda dt: self.add_exchanges())
diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
new file mode 100644
index 000000000..04af3734f
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/installwizard.py
@@ -0,0 +1,1043 @@
+
+from functools import partial
+import threading
+import os
+
+from kivy.app import App
+from kivy.clock import Clock
+from kivy.lang import Builder
+from kivy.properties import ObjectProperty, StringProperty, OptionProperty
+from kivy.core.window import Window
+from kivy.uix.button import Button
+from kivy.utils import platform
+from kivy.uix.widget import Widget
+from kivy.core.window import Window
+from kivy.clock import Clock
+from kivy.utils import platform
+
+from electrum.base_wizard import BaseWizard
+from electrum.util import is_valid_email
+
+
+from . import EventsDialog
+from ...i18n import _
+from .password_dialog import PasswordDialog
+
+# global Variables
+is_test = (platform == "linux")
+test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
+test_seed = "grape impose jazz bind spatial mind jelly tourist tank today holiday stomach"
+test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
+
+Builder.load_string('''
+#:import Window kivy.core.window.Window
+#:import _ electrum.gui.kivy.i18n._
+
+
+
+ border: 4, 4, 4, 4
+ font_size: '15sp'
+ padding: '15dp', '15dp'
+ background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
+ foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
+ hint_text_color: self.foreground_color
+ background_active: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
+ background_normal: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
+ size_hint_y: None
+ height: '48sp'
+
+:
+ root: None
+ size_hint: 1, None
+ height: '48sp'
+ on_press: if self.root: self.root.dispatch('on_press', self)
+ on_release: if self.root: self.root.dispatch('on_release', self)
+
+
+ color: .854, .925, .984, 1
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ bold: True
+
+<-WizardDialog>
+ text_color: .854, .925, .984, 1
+ value: ''
+ #auto_dismiss: False
+ size_hint: None, None
+ canvas.before:
+ Color:
+ rgba: .239, .588, .882, 1
+ Rectangle:
+ size: Window.size
+
+ crcontent: crcontent
+ # add electrum icon
+ BoxLayout:
+ orientation: 'vertical' if self.width < self.height else 'horizontal'
+ padding:
+ min(dp(27), self.width/32), min(dp(27), self.height/32),\
+ min(dp(27), self.width/32), min(dp(27), self.height/32)
+ spacing: '10dp'
+ GridLayout:
+ id: grid_logo
+ cols: 1
+ pos_hint: {'center_y': .5}
+ size_hint: 1, None
+ height: self.minimum_height
+ Label:
+ color: root.text_color
+ text: 'ELECTRUM'
+ size_hint: 1, None
+ height: self.texture_size[1] if self.opacity else 0
+ font_size: '33sp'
+ font_name: 'electrum/gui/kivy/data/fonts/tron/Tr2n.ttf'
+ GridLayout:
+ cols: 1
+ id: crcontent
+ spacing: '1dp'
+ Widget:
+ size_hint: 1, 0.3
+ GridLayout:
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ WizardButton:
+ id: back
+ text: _('Back')
+ root: root
+ WizardButton:
+ id: next
+ text: _('Next')
+ root: root
+ disabled: root.value == ''
+
+
+
+ value: 'next'
+ Widget
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: _("Choose the number of signatures needed to unlock funds in your wallet")
+ Widget
+ size_hint: 1, 1
+ GridLayout:
+ orientation: 'vertical'
+ cols: 2
+ spacing: '14dp'
+ size_hint: 1, 1
+ height: self.minimum_height
+ Label:
+ color: root.text_color
+ text: _('From {} cosigners').format(n.value)
+ Slider:
+ id: n
+ range: 2, 5
+ step: 1
+ value: 2
+ Label:
+ color: root.text_color
+ text: _('Require {} signatures').format(m.value)
+ Slider:
+ id: m
+ range: 1, n.value
+ step: 1
+ value: 2
+
+
+
+ message : ''
+ Widget:
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message
+ Widget
+ size_hint: 1, 1
+ GridLayout:
+ row_default_height: '48dp'
+ orientation: 'vertical'
+ id: choices
+ cols: 1
+ spacing: '14dp'
+ size_hint: 1, None
+
+
+ message : ''
+ Widget:
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message
+ Widget
+ size_hint: 1, 1
+
+
+ message : ''
+ size_hint: 1, 1
+ ScrollView:
+ size_hint: 1, 1
+ TextInput:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.minimum_height
+ text: root.message
+ disabled: True
+
+
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: 'Please enter your email address'
+ WizardTextInput:
+ id: email
+ on_text: Clock.schedule_once(root.on_text)
+ multiline: False
+ on_text_validate: Clock.schedule_once(root.on_enter)
+
+
+ message : ''
+ message2: ''
+ Widget:
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message
+ Widget
+ size_hint: 1, 1
+ WizardTextInput:
+ id: otp
+ on_text: Clock.schedule_once(root.on_text)
+ multiline: False
+ on_text_validate: Clock.schedule_once(root.on_enter)
+ Widget
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message2
+ Widget
+ size_hint: 1, 1
+ height: '48sp'
+ BoxLayout:
+ orientation: 'horizontal'
+ WizardButton:
+ id: cb
+ text: _('Request new secret')
+ on_release: root.request_new_secret()
+ size_hint: 1, None
+ WizardButton:
+ id: abort
+ text: _('Abort creation')
+ on_release: root.abort_wallet_creation()
+ size_hint: 1, None
+
+
+
+ message : ''
+ message2 : ''
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message
+ QRCodeWidget:
+ id: qr
+ size_hint: 1, 1
+ Label:
+ color: root.text_color
+ size_hint: 1, None
+ text_size: self.width, None
+ height: self.texture_size[1]
+ text: root.message2
+ WizardTextInput:
+ id: otp
+ on_text: Clock.schedule_once(root.on_text)
+ multiline: False
+ on_text_validate: Clock.schedule_once(root.on_enter)
+
+:
+ size_hint: 1, None
+ height: '33dp'
+ on_release:
+ self.parent.update_amount(self.text)
+
+:
+ size_hint: None, None
+ padding: '5dp', '5dp'
+ text_size: None, self.height
+ width: self.texture_size[0]
+ height: '30dp'
+ on_release:
+ self.parent.new_word(self.text)
+
+
+:
+ height: dp(100)
+ border: 4, 4, 4, 4
+ halign: 'justify'
+ valign: 'top'
+ font_size: '18dp'
+ text_size: self.width - dp(24), self.height - dp(12)
+ color: .1, .1, .1, 1
+ background_normal: 'atlas://electrum/gui/kivy/theming/light/white_bg_round_top'
+ background_down: self.background_normal
+ size_hint_y: None
+
+
+:
+ font_size: '12sp'
+ text_size: self.width, None
+ size_hint: 1, None
+ height: self.texture_size[1]
+ halign: 'justify'
+ valign: 'middle'
+ border: 4, 4, 4, 4
+
+
+
+ message: ''
+ word: ''
+ BigLabel:
+ text: "ENTER YOUR SEED PHRASE"
+ GridLayout
+ cols: 1
+ padding: 0, '12dp'
+ orientation: 'vertical'
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ SeedButton:
+ id: text_input_seed
+ text: ''
+ on_text: Clock.schedule_once(root.on_text)
+ on_release: root.options_dialog()
+ SeedLabel:
+ text: root.message
+ BoxLayout:
+ id: suggestions
+ height: '35dp'
+ size_hint: 1, None
+ new_word: root.on_word
+ BoxLayout:
+ id: line1
+ update_amount: root.update_text
+ size_hint: 1, None
+ height: '30dp'
+ MButton:
+ text: 'Q'
+ MButton:
+ text: 'W'
+ MButton:
+ text: 'E'
+ MButton:
+ text: 'R'
+ MButton:
+ text: 'T'
+ MButton:
+ text: 'Y'
+ MButton:
+ text: 'U'
+ MButton:
+ text: 'I'
+ MButton:
+ text: 'O'
+ MButton:
+ text: 'P'
+ BoxLayout:
+ id: line2
+ update_amount: root.update_text
+ size_hint: 1, None
+ height: '30dp'
+ Widget:
+ size_hint: 0.5, None
+ height: '33dp'
+ MButton:
+ text: 'A'
+ MButton:
+ text: 'S'
+ MButton:
+ text: 'D'
+ MButton:
+ text: 'F'
+ MButton:
+ text: 'G'
+ MButton:
+ text: 'H'
+ MButton:
+ text: 'J'
+ MButton:
+ text: 'K'
+ MButton:
+ text: 'L'
+ Widget:
+ size_hint: 0.5, None
+ height: '33dp'
+ BoxLayout:
+ id: line3
+ update_amount: root.update_text
+ size_hint: 1, None
+ height: '30dp'
+ Widget:
+ size_hint: 1, None
+ MButton:
+ text: 'Z'
+ MButton:
+ text: 'X'
+ MButton:
+ text: 'C'
+ MButton:
+ text: 'V'
+ MButton:
+ text: 'B'
+ MButton:
+ text: 'N'
+ MButton:
+ text: 'M'
+ MButton:
+ text: ' '
+ MButton:
+ text: '<'
+
+
+ title: ''
+ message: ''
+ BigLabel:
+ text: root.title
+ GridLayout
+ cols: 1
+ padding: 0, '12dp'
+ orientation: 'vertical'
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ SeedButton:
+ id: text_input
+ text: ''
+ on_text: Clock.schedule_once(root.check_text)
+ SeedLabel:
+ text: root.message
+ GridLayout
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ IconButton:
+ id: scan
+ height: '48sp'
+ on_release: root.scan_xpub()
+ icon: 'atlas://electrum/gui/kivy/theming/light/camera'
+ size_hint: 1, None
+ WizardButton:
+ text: _('Paste')
+ on_release: root.do_paste()
+ WizardButton:
+ text: _('Clear')
+ on_release: root.do_clear()
+
+
+
+ xpub: ''
+ message: _('Here is your master public key. Share it with your cosigners.')
+ BigLabel:
+ text: "MASTER PUBLIC KEY"
+ GridLayout
+ cols: 1
+ padding: 0, '12dp'
+ orientation: 'vertical'
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ SeedButton:
+ id: text_input
+ text: root.xpub
+ SeedLabel:
+ text: root.message
+ GridLayout
+ rows: 1
+ spacing: '12dp'
+ size_hint: 1, None
+ height: self.minimum_height
+ WizardButton:
+ text: _('QR code')
+ on_release: root.do_qr()
+ WizardButton:
+ text: _('Copy')
+ on_release: root.do_copy()
+ WizardButton:
+ text: _('Share')
+ on_release: root.do_share()
+
+
+
+ spacing: '12dp'
+ value: 'next'
+ BigLabel:
+ text: "PLEASE WRITE DOWN YOUR SEED PHRASE"
+ GridLayout:
+ id: grid
+ cols: 1
+ pos_hint: {'center_y': .5}
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: 'vertical'
+ spacing: '12dp'
+ SeedButton:
+ text: root.seed_text
+ on_release: root.options_dialog()
+ SeedLabel:
+ text: root.message
+
+
+
+
+ BigLabel:
+ text: root.title
+ SeedLabel:
+ text: root.message
+ TextInput:
+ id: passphrase_input
+ multiline: False
+ size_hint: 1, None
+ height: '27dp'
+ SeedLabel:
+ text: root.warning
+
+''')
+
+
+
+class WizardDialog(EventsDialog):
+ ''' Abstract dialog to be used as the base for all Create Account Dialogs
+ '''
+ crcontent = ObjectProperty(None)
+
+ def __init__(self, wizard, **kwargs):
+ super(WizardDialog, self).__init__()
+ self.wizard = wizard
+ self.ids.back.disabled = not wizard.can_go_back()
+ self.app = App.get_running_app()
+ self.run_next = kwargs['run_next']
+ _trigger_size_dialog = Clock.create_trigger(self._size_dialog)
+ Window.bind(size=_trigger_size_dialog,
+ rotation=_trigger_size_dialog)
+ _trigger_size_dialog()
+ self._on_release = False
+
+ def _size_dialog(self, dt):
+ app = App.get_running_app()
+ if app.ui_mode[0] == 'p':
+ self.size = Window.size
+ else:
+ #tablet
+ if app.orientation[0] == 'p':
+ #portrait
+ self.size = Window.size[0]/1.67, Window.size[1]/1.4
+ else:
+ self.size = Window.size[0]/2.5, Window.size[1]
+
+ def add_widget(self, widget, index=0):
+ if not self.crcontent:
+ super(WizardDialog, self).add_widget(widget)
+ else:
+ self.crcontent.add_widget(widget, index=index)
+
+ def on_dismiss(self):
+ app = App.get_running_app()
+ if app.wallet is None and not self._on_release:
+ app.stop()
+
+ def get_params(self, button):
+ return (None,)
+
+ def on_release(self, button):
+ self._on_release = True
+ self.close()
+ if not button:
+ self.parent.dispatch('on_wizard_complete', None)
+ return
+ if button is self.ids.back:
+ self.wizard.go_back()
+ return
+ params = self.get_params(button)
+ self.run_next(*params)
+
+
+class WizardMultisigDialog(WizardDialog):
+
+ def get_params(self, button):
+ m = self.ids.m.value
+ n = self.ids.n.value
+ return m, n
+
+
+class WizardOTPDialogBase(WizardDialog):
+
+ def get_otp(self):
+ otp = self.ids.otp.text
+ if len(otp) != 6:
+ return
+ try:
+ return int(otp)
+ except:
+ return
+
+ def on_text(self, dt):
+ self.ids.next.disabled = self.get_otp() is None
+
+ def on_enter(self, dt):
+ # press next
+ next = self.ids.next
+ if not next.disabled:
+ next.dispatch('on_release')
+
+
+class WizardKnownOTPDialog(WizardOTPDialogBase):
+
+ def __init__(self, wizard, **kwargs):
+ WizardOTPDialogBase.__init__(self, wizard, **kwargs)
+ self.message = _("This wallet is already registered with TrustedCoin. To finalize wallet creation, please enter your Google Authenticator Code.")
+ self.message2 =_("If you have lost your Google Authenticator account, you can request a new secret. You will need to retype your seed.")
+ self.request_new = False
+
+ def get_params(self, button):
+ return (self.get_otp(), self.request_new)
+
+ def request_new_secret(self):
+ self.request_new = True
+ self.on_release(True)
+
+ def abort_wallet_creation(self):
+ self._on_release = True
+ os.unlink(self.wizard.storage.path)
+ self.wizard.terminate()
+ self.dismiss()
+
+
+class WizardNewOTPDialog(WizardOTPDialogBase):
+
+ def __init__(self, wizard, **kwargs):
+ WizardOTPDialogBase.__init__(self, wizard, **kwargs)
+ otp_secret = kwargs['otp_secret']
+ uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
+ self.message = "Please scan the following QR code in Google Authenticator. You may also use the secret key: %s"%otp_secret
+ self.message2 = _('Then, enter your Google Authenticator code:')
+ self.ids.qr.set_data(uri)
+
+ def get_params(self, button):
+ return (self.get_otp(), False)
+
+class WizardTOSDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.ids.next.text = 'Accept'
+ self.ids.next.disabled = False
+ self.message = kwargs['tos']
+ self.message2 = _('Enter your email address:')
+
+class WizardEmailDialog(WizardDialog):
+
+ def get_params(self, button):
+ return (self.ids.email.text,)
+
+ def on_text(self, dt):
+ self.ids.next.disabled = not is_valid_email(self.ids.email.text)
+
+ def on_enter(self, dt):
+ # press next
+ next = self.ids.next
+ if not next.disabled:
+ next.dispatch('on_release')
+
+class WizardConfirmDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ super(WizardConfirmDialog, self).__init__(wizard, **kwargs)
+ self.message = kwargs.get('message', '')
+ self.value = 'ok'
+
+ def on_parent(self, instance, value):
+ if value:
+ app = App.get_running_app()
+ self._back = _back = partial(app.dispatch, 'on_back')
+
+ def get_params(self, button):
+ return (True,)
+
+class WizardChoiceDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
+ self.message = kwargs.get('message', '')
+ choices = kwargs.get('choices', [])
+ layout = self.ids.choices
+ layout.bind(minimum_height=layout.setter('height'))
+ for action, text in choices:
+ l = WizardButton(text=text)
+ l.action = action
+ l.height = '48dp'
+ l.root = self
+ layout.add_widget(l)
+
+ def on_parent(self, instance, value):
+ if value:
+ app = App.get_running_app()
+ self._back = _back = partial(app.dispatch, 'on_back')
+
+ def get_params(self, button):
+ return (button.action,)
+
+
+
+class LineDialog(WizardDialog):
+ title = StringProperty('')
+ message = StringProperty('')
+ warning = StringProperty('')
+
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.ids.next.disabled = False
+
+ def get_params(self, b):
+ return (self.ids.passphrase_input.text,)
+
+class ShowSeedDialog(WizardDialog):
+ seed_text = StringProperty('')
+ message = _("If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.")
+ ext = False
+
+ def __init__(self, wizard, **kwargs):
+ super(ShowSeedDialog, self).__init__(wizard, **kwargs)
+ self.seed_text = kwargs['seed_text']
+
+ def on_parent(self, instance, value):
+ if value:
+ app = App.get_running_app()
+ self._back = _back = partial(self.ids.back.dispatch, 'on_release')
+
+ def options_dialog(self):
+ from .seed_options import SeedOptionsDialog
+ def callback(status):
+ self.ext = status
+ d = SeedOptionsDialog(self.ext, callback)
+ d.open()
+
+ def get_params(self, b):
+ return (self.ext,)
+
+
+class WordButton(Button):
+ pass
+
+class WizardButton(Button):
+ pass
+
+
+class RestoreSeedDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
+ self._test = kwargs['test']
+ from electrum.mnemonic import Mnemonic
+ from electrum.old_mnemonic import words as old_wordlist
+ self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
+ self.ids.text_input_seed.text = test_seed if is_test else ''
+ self.message = _('Please type your seed phrase using the virtual keyboard.')
+ self.title = _('Enter Seed')
+ self.ext = False
+
+ def options_dialog(self):
+ from .seed_options import SeedOptionsDialog
+ def callback(status):
+ self.ext = status
+ d = SeedOptionsDialog(self.ext, callback)
+ d.open()
+
+ def get_suggestions(self, prefix):
+ for w in self.words:
+ if w.startswith(prefix):
+ yield w
+
+ def on_text(self, dt):
+ self.ids.next.disabled = not bool(self._test(self.get_text()))
+
+ text = self.ids.text_input_seed.text
+ if not text:
+ last_word = ''
+ elif text[-1] == ' ':
+ last_word = ''
+ else:
+ last_word = text.split(' ')[-1]
+
+ enable_space = False
+ self.ids.suggestions.clear_widgets()
+ suggestions = [x for x in self.get_suggestions(last_word)]
+
+ if last_word in suggestions:
+ b = WordButton(text=last_word)
+ self.ids.suggestions.add_widget(b)
+ enable_space = True
+
+ for w in suggestions:
+ if w != last_word and len(suggestions) < 10:
+ b = WordButton(text=w)
+ self.ids.suggestions.add_widget(b)
+
+ i = len(last_word)
+ p = set()
+ for x in suggestions:
+ if len(x)>i: p.add(x[i])
+
+ for line in [self.ids.line1, self.ids.line2, self.ids.line3]:
+ for c in line.children:
+ if isinstance(c, Button):
+ if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
+ c.disabled = (c.text.lower() not in p) and bool(last_word)
+ elif c.text == ' ':
+ c.disabled = not enable_space
+
+ def on_word(self, w):
+ text = self.get_text()
+ words = text.split(' ')
+ words[-1] = w
+ text = ' '.join(words)
+ self.ids.text_input_seed.text = text + ' '
+ self.ids.suggestions.clear_widgets()
+
+ def get_text(self):
+ ti = self.ids.text_input_seed
+ return ' '.join(ti.text.strip().split())
+
+ def update_text(self, c):
+ c = c.lower()
+ text = self.ids.text_input_seed.text
+ if c == '<':
+ text = text[:-1]
+ else:
+ text += c
+ self.ids.text_input_seed.text = text
+
+ def on_parent(self, instance, value):
+ if value:
+ tis = self.ids.text_input_seed
+ tis.focus = True
+ #tis._keyboard.bind(on_key_down=self.on_key_down)
+ self._back = _back = partial(self.ids.back.dispatch,
+ 'on_release')
+ app = App.get_running_app()
+
+ def on_key_down(self, keyboard, keycode, key, modifiers):
+ if keycode[0] in (13, 271):
+ self.on_enter()
+ return True
+
+ def on_enter(self):
+ #self._remove_keyboard()
+ # press next
+ next = self.ids.next
+ if not next.disabled:
+ next.dispatch('on_release')
+
+ def _remove_keyboard(self):
+ tis = self.ids.text_input_seed
+ if tis._keyboard:
+ tis._keyboard.unbind(on_key_down=self.on_key_down)
+ tis.focus = False
+
+ def get_params(self, b):
+ return (self.get_text(), False, self.ext)
+
+
+class ConfirmSeedDialog(RestoreSeedDialog):
+ def get_params(self, b):
+ return (self.get_text(),)
+ def options_dialog(self):
+ pass
+
+
+class ShowXpubDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.xpub = kwargs['xpub']
+ self.ids.next.disabled = False
+
+ def do_copy(self):
+ self.app._clipboard.copy(self.xpub)
+
+ def do_share(self):
+ self.app.do_share(self.xpub, _("Master Public Key"))
+
+ def do_qr(self):
+ from .qr_dialog import QRDialog
+ popup = QRDialog(_("Master Public Key"), self.xpub, True)
+ popup.open()
+
+
+class AddXpubDialog(WizardDialog):
+
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.is_valid = kwargs['is_valid']
+ self.title = kwargs['title']
+ self.message = kwargs['message']
+ self.allow_multi = kwargs.get('allow_multi', False)
+
+ def check_text(self, dt):
+ self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
+
+ def get_text(self):
+ ti = self.ids.text_input
+ return ti.text.strip()
+
+ def get_params(self, button):
+ return (self.get_text(),)
+
+ def scan_xpub(self):
+ def on_complete(text):
+ if self.allow_multi:
+ self.ids.text_input.text += text + '\n'
+ else:
+ self.ids.text_input.text = text
+ self.app.scan_qr(on_complete)
+
+ def do_paste(self):
+ self.ids.text_input.text = test_xpub if is_test else self.app._clipboard.paste()
+
+ def do_clear(self):
+ self.ids.text_input.text = ''
+
+
+
+
+class InstallWizard(BaseWizard, Widget):
+ '''
+ events::
+ `on_wizard_complete` Fired when the wizard is done creating/ restoring
+ wallet/s.
+ '''
+
+ __events__ = ('on_wizard_complete', )
+
+ def on_wizard_complete(self, wallet):
+ """overriden by main_window"""
+ pass
+
+ def waiting_dialog(self, task, msg, on_finished=None):
+ '''Perform a blocking task in the background by running the passed
+ method in a thread.
+ '''
+ def target():
+ # run your threaded function
+ try:
+ task()
+ except Exception as err:
+ self.show_error(str(err))
+ # on completion hide message
+ Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
+ if on_finished:
+ def protected_on_finished():
+ try:
+ on_finished()
+ except Exception as e:
+ self.show_error(str(e))
+ Clock.schedule_once(lambda dt: protected_on_finished(), -1)
+
+ app = App.get_running_app()
+ app.show_info_bubble(
+ text=msg, icon='atlas://electrum/gui/kivy/theming/light/important',
+ pos=Window.center, width='200sp', arrow_pos=None, modal=True)
+ t = threading.Thread(target = target)
+ t.start()
+
+ def terminate(self, **kwargs):
+ self.dispatch('on_wizard_complete', self.wallet)
+
+ def choice_dialog(self, **kwargs):
+ choices = kwargs['choices']
+ if len(choices) > 1:
+ WizardChoiceDialog(self, **kwargs).open()
+ else:
+ f = kwargs['run_next']
+ f(choices[0][0])
+
+ def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
+ def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
+ def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
+
+ def confirm_seed_dialog(self, **kwargs):
+ kwargs['title'] = _('Confirm Seed')
+ kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
+ ConfirmSeedDialog(self, **kwargs).open()
+
+ def restore_seed_dialog(self, **kwargs):
+ RestoreSeedDialog(self, **kwargs).open()
+
+ def confirm_dialog(self, **kwargs):
+ WizardConfirmDialog(self, **kwargs).open()
+
+ def tos_dialog(self, **kwargs):
+ WizardTOSDialog(self, **kwargs).open()
+
+ def email_dialog(self, **kwargs):
+ WizardEmailDialog(self, **kwargs).open()
+
+ def otp_dialog(self, **kwargs):
+ if kwargs['otp_secret']:
+ WizardNewOTPDialog(self, **kwargs).open()
+ else:
+ WizardKnownOTPDialog(self, **kwargs).open()
+
+ def add_xpub_dialog(self, **kwargs):
+ kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')
+ AddXpubDialog(self, **kwargs).open()
+
+ def add_cosigner_dialog(self, **kwargs):
+ kwargs['title'] = _("Add Cosigner") + " %d"%kwargs['index']
+ kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')
+ AddXpubDialog(self, **kwargs).open()
+
+ def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
+
+ def show_message(self, msg): self.show_error(msg)
+
+ def show_error(self, msg):
+ app = App.get_running_app()
+ Clock.schedule_once(lambda dt: app.show_error(msg))
+
+ def request_password(self, run_next, force_disable_encrypt_cb=False):
+ def on_success(old_pin, pin):
+ assert old_pin is None
+ run_next(pin, False)
+ def on_failure():
+ self.show_error(_('PIN mismatch'))
+ self.run('request_password', run_next)
+ popup = PasswordDialog()
+ app = App.get_running_app()
+ popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
+ popup.open()
+
+ def action_dialog(self, action, run_next):
+ f = getattr(self, action)
+ f()
diff --git a/electrum/gui/kivy/uix/dialogs/invoices.py b/electrum/gui/kivy/uix/dialogs/invoices.py
new file mode 100644
index 000000000..4fb986df1
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/invoices.py
@@ -0,0 +1,169 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from decimal import Decimal
+
+Builder.load_string('''
+
+ #color: .305, .309, .309, 1
+ text_size: self.width, None
+ halign: 'left'
+ valign: 'top'
+
+
+ requestor: ''
+ memo: ''
+ amount: ''
+ status: ''
+ date: ''
+ icon: 'atlas://electrum/gui/kivy/theming/light/important'
+ Image:
+ id: icon
+ source: root.icon
+ size_hint: None, 1
+ width: self.height *.54
+ mipmap: True
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ InvoicesLabel:
+ text: root.requestor
+ shorten: True
+ Widget
+ InvoicesLabel:
+ text: root.memo
+ color: .699, .699, .699, 1
+ font_size: '13sp'
+ shorten: True
+ Widget
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ InvoicesLabel:
+ text: root.amount
+ font_size: '15sp'
+ halign: 'right'
+ width: '110sp'
+ Widget
+ InvoicesLabel:
+ text: root.status
+ font_size: '13sp'
+ halign: 'right'
+ color: .699, .699, .699, 1
+ Widget
+
+
+
+ id: popup
+ title: _('Invoices')
+ BoxLayout:
+ id: box
+ orientation: 'vertical'
+ spacing: '1dp'
+ ScrollView:
+ GridLayout:
+ cols: 1
+ id: invoices_container
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '2dp'
+ padding: '12dp'
+''')
+
+from kivy.properties import BooleanProperty
+from electrum.gui.kivy.i18n import _
+from electrum.util import format_time
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from electrum.gui.kivy.uix.context_menu import ContextMenu
+
+invoice_text = {
+ PR_UNPAID:_('Pending'),
+ PR_UNKNOWN:_('Unknown'),
+ PR_PAID:_('Paid'),
+ PR_EXPIRED:_('Expired')
+}
+pr_icon = {
+ PR_UNPAID: 'atlas://electrum/gui/kivy/theming/light/important',
+ PR_UNKNOWN: 'atlas://electrum/gui/kivy/theming/light/important',
+ PR_PAID: 'atlas://electrum/gui/kivy/theming/light/confirmed',
+ PR_EXPIRED: 'atlas://electrum/gui/kivy/theming/light/close'
+}
+
+
+class InvoicesDialog(Factory.Popup):
+
+ def __init__(self, app, screen, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.screen = screen
+ self.callback = callback
+ self.cards = {}
+ self.context_menu = None
+
+ def get_card(self, pr):
+ key = pr.get_id()
+ ci = self.cards.get(key)
+ if ci is None:
+ ci = Factory.InvoiceItem()
+ ci.key = key
+ ci.screen = self
+ self.cards[key] = ci
+ ci.requestor = pr.get_requestor()
+ ci.memo = pr.get_memo()
+ amount = pr.get_amount()
+ if amount:
+ ci.amount = self.app.format_amount_and_units(amount)
+ status = self.app.wallet.invoices.get_status(ci.key)
+ ci.status = invoice_text[status]
+ ci.icon = pr_icon[status]
+ else:
+ ci.amount = _('No Amount')
+ ci.status = ''
+ exp = pr.get_expiration_date()
+ ci.date = format_time(exp) if exp else _('Never')
+ return ci
+
+ def update(self):
+ self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
+ invoices_list = self.ids.invoices_container
+ invoices_list.clear_widgets()
+ _list = self.app.wallet.invoices.sorted_list()
+ for pr in _list:
+ ci = self.get_card(pr)
+ invoices_list.add_widget(ci)
+
+ def do_pay(self, obj):
+ self.hide_menu()
+ self.dismiss()
+ pr = self.app.wallet.invoices.get(obj.key)
+ self.app.on_pr(pr)
+
+ def do_view(self, obj):
+ pr = self.app.wallet.invoices.get(obj.key)
+ pr.verify(self.app.wallet.contacts)
+ self.app.show_pr_details(pr.get_dict(), obj.status, True)
+
+ def do_delete(self, obj):
+ from .question import Question
+ def cb(result):
+ if result:
+ self.app.wallet.invoices.remove(obj.key)
+ self.hide_menu()
+ self.update()
+ d = Question(_('Delete invoice?'), cb)
+ d.open()
+
+ def show_menu(self, obj):
+ self.hide_menu()
+ self.context_menu = ContextMenu(obj, self.menu_actions)
+ self.ids.box.add_widget(self.context_menu)
+
+ def hide_menu(self):
+ if self.context_menu is not None:
+ self.ids.box.remove_widget(self.context_menu)
+ self.context_menu = None
diff --git a/electrum/gui/kivy/uix/dialogs/label_dialog.py b/electrum/gui/kivy/uix/dialogs/label_dialog.py
new file mode 100644
index 000000000..974cd0667
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/label_dialog.py
@@ -0,0 +1,55 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+Builder.load_string('''
+
+ id: popup
+ title: ''
+ size_hint: 0.8, 0.3
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ Widget:
+ size_hint: 1, 0.2
+ TextInput:
+ id:input
+ padding: '5dp'
+ size_hint: 1, None
+ height: '27dp'
+ pos_hint: {'center_y':.5}
+ text:''
+ multiline: False
+ background_normal: 'atlas://electrum/gui/kivy/theming/light/tab_btn'
+ background_active: 'atlas://electrum/gui/kivy/theming/light/textinput_active'
+ hint_text_color: self.foreground_color
+ foreground_color: 1, 1, 1, 1
+ font_size: '16dp'
+ focus: True
+ Widget:
+ size_hint: 1, 0.2
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.5
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(input.text)
+ popup.dismiss()
+''')
+
+class LabelDialog(Factory.Popup):
+
+ def __init__(self, title, text, callback):
+ Factory.Popup.__init__(self)
+ self.ids.input.text = text
+ self.callback = callback
+ self.title = title
diff --git a/electrum/gui/kivy/uix/dialogs/nfc_transaction.py b/electrum/gui/kivy/uix/dialogs/nfc_transaction.py
new file mode 100644
index 000000000..f6dfd5792
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/nfc_transaction.py
@@ -0,0 +1,32 @@
+class NFCTransactionDialog(AnimatedPopup):
+
+ mode = OptionProperty('send', options=('send','receive'))
+
+ scanner = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ # Delayed Init
+ global NFCSCanner
+ if NFCSCanner is None:
+ from electrum.gui.kivy.nfc_scanner import NFCScanner
+ self.scanner = NFCSCanner
+
+ super(NFCTransactionDialog, self).__init__(**kwargs)
+ self.scanner.nfc_init()
+ self.scanner.bind()
+
+ def on_parent(self, instance, value):
+ sctr = self.ids.sctr
+ if value:
+ def _cmp(*l):
+ anim = Animation(rotation=2, scale=1, opacity=1)
+ anim.start(sctr)
+ anim.bind(on_complete=_start)
+
+ def _start(*l):
+ anim = Animation(rotation=350, scale=2, opacity=0)
+ anim.start(sctr)
+ anim.bind(on_complete=_cmp)
+ _start()
+ return
+ Animation.cancel_all(sctr)
\ No newline at end of file
diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py
new file mode 100644
index 000000000..665ab136e
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/password_dialog.py
@@ -0,0 +1,142 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from decimal import Decimal
+from kivy.clock import Clock
+
+from electrum.util import InvalidPassword
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+
+
+ id: popup
+ title: 'Electrum'
+ message: ''
+ BoxLayout:
+ size_hint: 1, 1
+ orientation: 'vertical'
+ Widget:
+ size_hint: 1, 0.05
+ Label:
+ font_size: '20dp'
+ text: root.message
+ text_size: self.width, None
+ size: self.texture_size
+ Widget:
+ size_hint: 1, 0.05
+ Label:
+ id: a
+ font_size: '50dp'
+ text: '*'*len(kb.password) + '-'*(6-len(kb.password))
+ size: self.texture_size
+ Widget:
+ size_hint: 1, 0.05
+ GridLayout:
+ id: kb
+ size_hint: 1, None
+ height: self.minimum_height
+ update_amount: popup.update_password
+ password: ''
+ on_password: popup.on_password(self.password)
+ spacing: '2dp'
+ cols: 3
+ KButton:
+ text: '1'
+ KButton:
+ text: '2'
+ KButton:
+ text: '3'
+ KButton:
+ text: '4'
+ KButton:
+ text: '5'
+ KButton:
+ text: '6'
+ KButton:
+ text: '7'
+ KButton:
+ text: '8'
+ KButton:
+ text: '9'
+ KButton:
+ text: 'Clear'
+ KButton:
+ text: '0'
+ KButton:
+ text: '<'
+''')
+
+
+class PasswordDialog(Factory.Popup):
+
+ def init(self, app, wallet, message, on_success, on_failure, is_change=0):
+ self.app = app
+ self.wallet = wallet
+ self.message = message
+ self.on_success = on_success
+ self.on_failure = on_failure
+ self.ids.kb.password = ''
+ self.success = False
+ self.is_change = is_change
+ self.pw = None
+ self.new_password = None
+ self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '')
+
+ def check_password(self, password):
+ if self.is_change > 1:
+ return True
+ try:
+ self.wallet.check_password(password)
+ return True
+ except InvalidPassword as e:
+ return False
+
+ def on_dismiss(self):
+ if not self.success:
+ if self.on_failure:
+ self.on_failure()
+ else:
+ # keep dialog open
+ return True
+ else:
+ if self.on_success:
+ args = (self.pw, self.new_password) if self.is_change else (self.pw,)
+ Clock.schedule_once(lambda dt: self.on_success(*args), 0.1)
+
+ def update_password(self, c):
+ kb = self.ids.kb
+ text = kb.password
+ if c == '<':
+ text = text[:-1]
+ elif c == 'Clear':
+ text = ''
+ else:
+ text += c
+ kb.password = text
+
+ def on_password(self, pw):
+ if len(pw) == 6:
+ if self.check_password(pw):
+ if self.is_change == 0:
+ self.success = True
+ self.pw = pw
+ self.message = _('Please wait...')
+ self.dismiss()
+ elif self.is_change == 1:
+ self.pw = pw
+ self.message = _('Enter new PIN')
+ self.ids.kb.password = ''
+ self.is_change = 2
+ elif self.is_change == 2:
+ self.new_password = pw
+ self.message = _('Confirm new PIN')
+ self.ids.kb.password = ''
+ self.is_change = 3
+ elif self.is_change == 3:
+ self.success = pw == self.new_password
+ self.dismiss()
+ else:
+ self.app.show_error(_('Wrong PIN'))
+ self.ids.kb.password = ''
diff --git a/electrum/gui/kivy/uix/dialogs/qr_dialog.py b/electrum/gui/kivy/uix/dialogs/qr_dialog.py
new file mode 100644
index 000000000..b12eb6ce6
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/qr_dialog.py
@@ -0,0 +1,47 @@
+from kivy.factory import Factory
+from kivy.lang import Builder
+
+Builder.load_string('''
+
+ id: popup
+ title: ''
+ data: ''
+ shaded: False
+ show_text: False
+ AnchorLayout:
+ anchor_x: 'center'
+ BoxLayout:
+ orientation: 'vertical'
+ size_hint: 1, 1
+ padding: '10dp'
+ spacing: '10dp'
+ QRCodeWidget:
+ id: qr
+ TopLabel:
+ text: root.data if root.show_text else ''
+ Widget:
+ size_hint: 1, 0.2
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Widget:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ size_hint: 1, None
+ height: '48dp'
+ text: _('Close')
+ on_release:
+ popup.dismiss()
+''')
+
+class QRDialog(Factory.Popup):
+ def __init__(self, title, data, show_text, failure_cb=None):
+ Factory.Popup.__init__(self)
+ self.title = title
+ self.data = data
+ self.show_text = show_text
+ self.failure_cb = failure_cb
+
+ def on_open(self):
+ self.ids.qr.set_data(self.data, self.failure_cb)
diff --git a/electrum/gui/kivy/uix/dialogs/qr_scanner.py b/electrum/gui/kivy/uix/dialogs/qr_scanner.py
new file mode 100644
index 000000000..8a565f019
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/qr_scanner.py
@@ -0,0 +1,44 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.lang import Builder
+
+Factory.register('QRScanner', module='electrum.gui.kivy.qr_scanner')
+
+class QrScannerDialog(Factory.AnimatedPopup):
+
+ __events__ = ('on_complete', )
+
+ def on_symbols(self, instance, value):
+ instance.stop()
+ self.dismiss()
+ data = value[0].data
+ self.dispatch('on_complete', data)
+
+ def on_complete(self, x):
+ ''' Default Handler for on_complete event.
+ '''
+ print(x)
+
+
+Builder.load_string('''
+
+ title:
+ _(\
+ '[size=18dp]Hold your QRCode up to the camera[/size][size=7dp]\\n[/size]')
+ title_size: '24sp'
+ border: 7, 7, 7, 7
+ size_hint: None, None
+ size: '340dp', '290dp'
+ pos_hint: {'center_y': .53}
+ #separator_color: .89, .89, .89, 1
+ #separator_height: '1.2dp'
+ #title_color: .437, .437, .437, 1
+ #background: 'atlas://electrum/gui/kivy/theming/light/dialog'
+ on_activate:
+ qrscr.start()
+ qrscr.size = self.size
+ on_deactivate: qrscr.stop()
+ QRScanner:
+ id: qrscr
+ on_symbols: root.on_symbols(*args)
+''')
diff --git a/electrum/gui/kivy/uix/dialogs/question.py b/electrum/gui/kivy/uix/dialogs/question.py
new file mode 100644
index 000000000..4b5e085fa
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/question.py
@@ -0,0 +1,53 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from kivy.uix.checkbox import CheckBox
+from kivy.uix.label import Label
+from kivy.uix.widget import Widget
+
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+
+ id: popup
+ title: ''
+ message: ''
+ size_hint: 0.8, 0.5
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ Label:
+ id: label
+ text: root.message
+ text_size: self.width, None
+ Widget:
+ size_hint: 1, 0.1
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Button:
+ text: _('No')
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(False)
+ popup.dismiss()
+ Button:
+ text: _('Yes')
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(True)
+ popup.dismiss()
+''')
+
+
+
+class Question(Factory.Popup):
+
+ def __init__(self, msg, callback):
+ Factory.Popup.__init__(self)
+ self.title = _('Question')
+ self.message = msg
+ self.callback = callback
diff --git a/electrum/gui/kivy/uix/dialogs/requests.py b/electrum/gui/kivy/uix/dialogs/requests.py
new file mode 100644
index 000000000..5aadae0d0
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/requests.py
@@ -0,0 +1,157 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from decimal import Decimal
+
+Builder.load_string('''
+
+ #color: .305, .309, .309, 1
+ text_size: self.width, None
+ halign: 'left'
+ valign: 'top'
+
+
+ address: ''
+ memo: ''
+ amount: ''
+ status: ''
+ date: ''
+ icon: 'atlas://electrum/gui/kivy/theming/light/important'
+ Image:
+ id: icon
+ source: root.icon
+ size_hint: None, 1
+ width: self.height *.54
+ mipmap: True
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ RequestLabel:
+ text: root.address
+ shorten: True
+ Widget
+ RequestLabel:
+ text: root.memo
+ color: .699, .699, .699, 1
+ font_size: '13sp'
+ shorten: True
+ Widget
+ BoxLayout:
+ spacing: '8dp'
+ height: '32dp'
+ orientation: 'vertical'
+ Widget
+ RequestLabel:
+ text: root.amount
+ halign: 'right'
+ font_size: '15sp'
+ Widget
+ RequestLabel:
+ text: root.status
+ halign: 'right'
+ font_size: '13sp'
+ color: .699, .699, .699, 1
+ Widget
+
+
+ id: popup
+ title: _('Requests')
+ BoxLayout:
+ id:box
+ orientation: 'vertical'
+ spacing: '1dp'
+ ScrollView:
+ GridLayout:
+ cols: 1
+ id: requests_container
+ size_hint: 1, None
+ height: self.minimum_height
+ spacing: '2dp'
+ padding: '12dp'
+''')
+
+from kivy.properties import BooleanProperty
+from electrum.gui.kivy.i18n import _
+from electrum.util import format_time
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from electrum.gui.kivy.uix.context_menu import ContextMenu
+
+pr_icon = {
+ PR_UNPAID: 'atlas://electrum/gui/kivy/theming/light/important',
+ PR_UNKNOWN: 'atlas://electrum/gui/kivy/theming/light/important',
+ PR_PAID: 'atlas://electrum/gui/kivy/theming/light/confirmed',
+ PR_EXPIRED: 'atlas://electrum/gui/kivy/theming/light/close'
+}
+request_text = {
+ PR_UNPAID: _('Pending'),
+ PR_UNKNOWN: _('Unknown'),
+ PR_PAID: _('Received'),
+ PR_EXPIRED: _('Expired')
+}
+
+
+class RequestsDialog(Factory.Popup):
+
+ def __init__(self, app, screen, callback):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.screen = screen
+ self.callback = callback
+ self.cards = {}
+ self.context_menu = None
+
+ def get_card(self, req):
+ address = req['address']
+ ci = self.cards.get(address)
+ if ci is None:
+ ci = Factory.RequestItem()
+ ci.address = address
+ ci.screen = self
+ self.cards[address] = ci
+
+ amount = req.get('amount')
+ ci.amount = self.app.format_amount_and_units(amount) if amount else ''
+ ci.memo = req.get('memo', '')
+ status, conf = self.app.wallet.get_request_status(address)
+ ci.status = request_text[status]
+ ci.icon = pr_icon[status]
+ #exp = pr.get_expiration_date()
+ #ci.date = format_time(exp) if exp else _('Never')
+ return ci
+
+ def update(self):
+ self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
+ requests_list = self.ids.requests_container
+ requests_list.clear_widgets()
+ _list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
+ for pr in _list:
+ ci = self.get_card(pr)
+ requests_list.add_widget(ci)
+
+ def do_show(self, obj):
+ self.hide_menu()
+ self.dismiss()
+ self.app.show_request(obj.address)
+
+ def do_delete(self, req):
+ from .question import Question
+ def cb(result):
+ if result:
+ self.app.wallet.remove_payment_request(req.address, self.app.electrum_config)
+ self.hide_menu()
+ self.update()
+ d = Question(_('Delete request'), cb)
+ d.open()
+
+ def show_menu(self, obj):
+ self.hide_menu()
+ self.context_menu = ContextMenu(obj, self.menu_actions)
+ self.ids.box.add_widget(self.context_menu)
+
+ def hide_menu(self):
+ if self.context_menu is not None:
+ self.ids.box.remove_widget(self.context_menu)
+ self.context_menu = None
diff --git a/electrum/gui/kivy/uix/dialogs/seed_options.py b/electrum/gui/kivy/uix/dialogs/seed_options.py
new file mode 100644
index 000000000..a4fb38fec
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/seed_options.py
@@ -0,0 +1,51 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+Builder.load_string('''
+
+ id: popup
+ title: _('Seed Options')
+ size_hint: 0.8, 0.8
+ pos_hint: {'top':0.9}
+ BoxLayout:
+ orientation: 'vertical'
+ Label:
+ id: description
+ text: _('You may extend your seed with custom words')
+ halign: 'left'
+ text_size: self.width, None
+ size: self.texture_size
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Label:
+ text: _('Extend Seed')
+ CheckBox:
+ id:cb
+ Widget:
+ size_hint: 1, 0.1
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint: 1, 0.2
+ Button:
+ text: 'Cancel'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release: popup.dismiss()
+ Button:
+ text: 'OK'
+ size_hint: 0.5, None
+ height: '48dp'
+ on_release:
+ root.callback(cb.active)
+ popup.dismiss()
+''')
+
+
+class SeedOptionsDialog(Factory.Popup):
+ def __init__(self, status, callback):
+ Factory.Popup.__init__(self)
+ self.ids.cb.active = status
+ self.callback = callback
diff --git a/electrum/gui/kivy/uix/dialogs/settings.py b/electrum/gui/kivy/uix/dialogs/settings.py
new file mode 100644
index 000000000..29539e38e
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/settings.py
@@ -0,0 +1,220 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+from electrum.util import base_units_list
+from electrum.i18n import languages
+from electrum.gui.kivy.i18n import _
+from electrum.plugin import run_hook
+from electrum import coinchooser
+
+from .choice_dialog import ChoiceDialog
+
+Builder.load_string('''
+#:import partial functools.partial
+#:import _ electrum.gui.kivy.i18n._
+
+
+ id: settings
+ title: _('Electrum Settings')
+ disable_pin: False
+ use_encryption: False
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ GridLayout:
+ id: scrollviewlayout
+ cols:1
+ size_hint: 1, None
+ height: self.minimum_height
+ padding: '10dp'
+ SettingsItem:
+ lang: settings.get_language_name()
+ title: 'Language' + ': ' + str(self.lang)
+ description: _('Language')
+ action: partial(root.language_dialog, self)
+ CardSeparator
+ SettingsItem:
+ disabled: root.disable_pin
+ title: _('PIN code')
+ description: _("Change your PIN code.")
+ action: partial(root.change_password, self)
+ CardSeparator
+ SettingsItem:
+ bu: app.base_unit
+ title: _('Denomination') + ': ' + self.bu
+ description: _("Base unit for Bitcore amounts.")
+ action: partial(root.unit_dialog, self)
+ CardSeparator
+ SettingsItem:
+ status: root.fx_status()
+ title: _('Fiat Currency') + ': ' + self.status
+ description: _("Display amounts in fiat currency.")
+ action: partial(root.fx_dialog, self)
+ CardSeparator
+ SettingsItem:
+ status: 'ON' if bool(app.plugins.get('labels')) else 'OFF'
+ title: _('Labels Sync') + ': ' + self.status
+ description: _("Save and synchronize your labels.")
+ action: partial(root.plugin_dialog, 'labels', self)
+ CardSeparator
+ SettingsItem:
+ status: 'ON' if app.use_rbf else 'OFF'
+ title: _('Replace-by-fee') + ': ' + self.status
+ description: _("Create replaceable transactions.")
+ message:
+ _('If you check this box, your transactions will be marked as non-final,') \
+ + ' ' + _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pays higher fees.') \
+ + ' ' + _('Note that some merchants do not accept non-final transactions until they are confirmed.')
+ action: partial(root.boolean_dialog, 'use_rbf', _('Replace by fee'), self.message)
+ CardSeparator
+ SettingsItem:
+ status: _('Yes') if app.use_unconfirmed else _('No')
+ title: _('Spend unconfirmed') + ': ' + self.status
+ description: _("Use unconfirmed coins in transactions.")
+ message: _('Spend unconfirmed coins')
+ action: partial(root.boolean_dialog, 'use_unconfirmed', _('Use unconfirmed'), self.message)
+ CardSeparator
+ SettingsItem:
+ status: _('Yes') if app.use_change else _('No')
+ title: _('Use change addresses') + ': ' + self.status
+ description: _("Send your change to separate addresses.")
+ message: _('Send excess coins to change addresses')
+ action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
+
+ # disabled: there is currently only one coin selection policy
+ #CardSeparator
+ #SettingsItem:
+ # status: root.coinselect_status()
+ # title: _('Coin selection') + ': ' + self.status
+ # description: "Coin selection method"
+ # action: partial(root.coinselect_dialog, self)
+''')
+
+
+
+class SettingsDialog(Factory.Popup):
+
+ def __init__(self, app):
+ self.app = app
+ self.plugins = self.app.plugins
+ self.config = self.app.electrum_config
+ Factory.Popup.__init__(self)
+ layout = self.ids.scrollviewlayout
+ layout.bind(minimum_height=layout.setter('height'))
+ # cached dialogs
+ self._fx_dialog = None
+ self._proxy_dialog = None
+ self._language_dialog = None
+ self._unit_dialog = None
+ self._coinselect_dialog = None
+
+ def update(self):
+ self.wallet = self.app.wallet
+ self.disable_pin = self.wallet.is_watching_only() if self.wallet else True
+ self.use_encryption = self.wallet.has_password() if self.wallet else False
+
+ def get_language_name(self):
+ return languages.get(self.config.get('language', 'en_UK'), '')
+
+ def change_password(self, item, dt):
+ self.app.change_password(self.update)
+
+ def language_dialog(self, item, dt):
+ if self._language_dialog is None:
+ l = self.config.get('language', 'en_UK')
+ def cb(key):
+ self.config.set_key("language", key, True)
+ item.lang = self.get_language_name()
+ self.app.language = key
+ self._language_dialog = ChoiceDialog(_('Language'), languages, l, cb)
+ self._language_dialog.open()
+
+ def unit_dialog(self, item, dt):
+ if self._unit_dialog is None:
+ def cb(text):
+ self.app._set_bu(text)
+ item.bu = self.app.base_unit
+ self._unit_dialog = ChoiceDialog(_('Denomination'), base_units_list,
+ self.app.base_unit, cb, keep_choice_order=True)
+ self._unit_dialog.open()
+
+ def coinselect_status(self):
+ return coinchooser.get_name(self.app.electrum_config)
+
+ def coinselect_dialog(self, item, dt):
+ if self._coinselect_dialog is None:
+ choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
+ chooser_name = coinchooser.get_name(self.config)
+ def cb(text):
+ self.config.set_key('coin_chooser', text)
+ item.status = text
+ self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb)
+ self._coinselect_dialog.open()
+
+ def proxy_status(self):
+ server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()
+ return proxy.get('host') +':' + proxy.get('port') if proxy else _('None')
+
+ def proxy_dialog(self, item, dt):
+ if self._proxy_dialog is None:
+ server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()
+ def callback(popup):
+ if popup.ids.mode.text != 'None':
+ proxy = {
+ 'mode':popup.ids.mode.text,
+ 'host':popup.ids.host.text,
+ 'port':popup.ids.port.text,
+ 'user':popup.ids.user.text,
+ 'password':popup.ids.password.text
+ }
+ else:
+ proxy = None
+ self.app.network.set_parameters(server, port, protocol, proxy, auto_connect)
+ item.status = self.proxy_status()
+ popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/proxy.kv')
+ popup.ids.mode.text = proxy.get('mode') if proxy else 'None'
+ popup.ids.host.text = proxy.get('host') if proxy else ''
+ popup.ids.port.text = proxy.get('port') if proxy else ''
+ popup.ids.user.text = proxy.get('user') if proxy else ''
+ popup.ids.password.text = proxy.get('password') if proxy else ''
+ popup.on_dismiss = lambda: callback(popup)
+ self._proxy_dialog = popup
+ self._proxy_dialog.open()
+
+ def plugin_dialog(self, name, label, dt):
+ from .checkbox_dialog import CheckBoxDialog
+ def callback(status):
+ self.plugins.enable(name) if status else self.plugins.disable(name)
+ label.status = 'ON' if status else 'OFF'
+ status = bool(self.plugins.get(name))
+ dd = self.plugins.descriptions.get(name)
+ descr = dd.get('description')
+ fullname = dd.get('fullname')
+ d = CheckBoxDialog(fullname, descr, status, callback)
+ d.open()
+
+ def fee_status(self):
+ return self.config.get_fee_status()
+
+ def boolean_dialog(self, name, title, message, dt):
+ from .checkbox_dialog import CheckBoxDialog
+ CheckBoxDialog(title, message, getattr(self.app, name), lambda x: setattr(self.app, name, x)).open()
+
+ def fx_status(self):
+ fx = self.app.fx
+ if fx.is_enabled():
+ source = fx.exchange.name()
+ ccy = fx.get_currency()
+ return '%s [%s]' %(ccy, source)
+ else:
+ return _('None')
+
+ def fx_dialog(self, label, dt):
+ if self._fx_dialog is None:
+ from .fx_dialog import FxDialog
+ def cb():
+ label.status = self.fx_status()
+ self._fx_dialog = FxDialog(self.app, self.plugins, self.config, cb)
+ self._fx_dialog.open()
diff --git a/electrum/gui/kivy/uix/dialogs/tx_dialog.py b/electrum/gui/kivy/uix/dialogs/tx_dialog.py
new file mode 100644
index 000000000..a99acb940
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/tx_dialog.py
@@ -0,0 +1,185 @@
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+from kivy.clock import Clock
+from kivy.uix.label import Label
+
+from electrum.gui.kivy.i18n import _
+from datetime import datetime
+from electrum.util import InvalidPassword
+
+Builder.load_string('''
+
+
+ id: popup
+ title: _('Transaction')
+ is_mine: True
+ can_sign: False
+ can_broadcast: False
+ can_rbf: False
+ fee_str: ''
+ date_str: ''
+ date_label:''
+ amount_str: ''
+ tx_hash: ''
+ status_str: ''
+ description: ''
+ outputs_str: ''
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ scroll_type: ['bars', 'content']
+ bar_width: '25dp'
+ GridLayout:
+ height: self.minimum_height
+ size_hint_y: None
+ cols: 1
+ spacing: '10dp'
+ padding: '10dp'
+ GridLayout:
+ height: self.minimum_height
+ size_hint_y: None
+ cols: 1
+ spacing: '10dp'
+ BoxLabel:
+ text: _('Status')
+ value: root.status_str
+ BoxLabel:
+ text: _('Description') if root.description else ''
+ value: root.description
+ BoxLabel:
+ text: root.date_label
+ value: root.date_str
+ BoxLabel:
+ text: _('Amount sent') if root.is_mine else _('Amount received')
+ value: root.amount_str
+ BoxLabel:
+ text: _('Transaction fee') if root.fee_str else ''
+ value: root.fee_str
+ TopLabel:
+ text: _('Transaction ID') + ':' if root.tx_hash else ''
+ TxHashLabel:
+ data: root.tx_hash
+ name: _('Transaction ID')
+ TopLabel:
+ text: _('Outputs') + ':'
+ OutputList:
+ id: output_list
+ Widget:
+ size_hint: 1, 0.1
+
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('Sign') if root.can_sign else _('Broadcast') if root.can_broadcast else _('Bump fee') if root.can_rbf else ''
+ disabled: not(root.can_sign or root.can_broadcast or root.can_rbf)
+ opacity: 0 if self.disabled else 1
+ on_release:
+ if root.can_sign: root.do_sign()
+ if root.can_broadcast: root.do_broadcast()
+ if root.can_rbf: root.do_rbf()
+ IconButton:
+ size_hint: 0.5, None
+ height: '48dp'
+ icon: 'atlas://electrum/gui/kivy/theming/light/qrcode'
+ on_release: root.show_qr()
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('Close')
+ on_release: root.dismiss()
+''')
+
+
+class TxDialog(Factory.Popup):
+
+ def __init__(self, app, tx):
+ Factory.Popup.__init__(self)
+ self.app = app
+ self.wallet = self.app.wallet
+ self.tx = tx
+
+ def on_open(self):
+ self.update()
+
+ def update(self):
+ format_amount = self.app.format_amount_and_units
+ 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)
+ self.tx_hash = tx_hash or ''
+ if timestamp:
+ self.date_label = _('Date')
+ self.date_str = datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+ elif exp_n:
+ self.date_label = _('Mempool depth')
+ self.date_str = _('{} from tip').format('%.2f MB'%(exp_n/1000000))
+ else:
+ self.date_label = ''
+ self.date_str = ''
+
+ if amount is None:
+ self.amount_str = _("Transaction unrelated to your wallet")
+ elif amount > 0:
+ self.is_mine = False
+ self.amount_str = format_amount(amount)
+ else:
+ self.is_mine = True
+ self.amount_str = format_amount(-amount)
+ self.fee_str = format_amount(fee) if fee is not None else _('unknown')
+ self.can_sign = self.wallet.can_sign(self.tx)
+ self.ids.output_list.update(self.tx.outputs())
+
+ def do_rbf(self):
+ from .bump_fee_dialog import BumpFeeDialog
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(self.tx)
+ if fee is None:
+ self.app.show_error(_("Can't bump fee: unknown fee for original transaction."))
+ return
+ size = self.tx.estimated_size()
+ d = BumpFeeDialog(self.app, fee, size, self._do_rbf)
+ d.open()
+
+ def _do_rbf(self, old_fee, new_fee, is_final):
+ if new_fee is None:
+ return
+ delta = new_fee - old_fee
+ if delta < 0:
+ self.app.show_error("fee too low")
+ return
+ try:
+ new_tx = self.wallet.bump_fee(self.tx, delta)
+ except BaseException as e:
+ self.app.show_error(str(e))
+ return
+ if is_final:
+ new_tx.set_rbf(False)
+ self.tx = new_tx
+ self.update()
+ self.do_sign()
+
+ def do_sign(self):
+ self.app.protected(_("Enter your PIN code in order to sign this transaction"), self._do_sign, ())
+
+ def _do_sign(self, password):
+ self.status_str = _('Signing') + '...'
+ Clock.schedule_once(lambda dt: self.__do_sign(password), 0.1)
+
+ def __do_sign(self, password):
+ try:
+ self.app.wallet.sign_transaction(self.tx, password)
+ except InvalidPassword:
+ self.app.show_error(_("Invalid PIN"))
+ self.update()
+
+ def do_broadcast(self):
+ self.app.broadcast(self.tx)
+
+ def show_qr(self):
+ from electrum.bitcoin import base_encode, bfh
+ raw_tx = str(self.tx)
+ text = bfh(raw_tx)
+ text = base_encode(text, base=43)
+ self.app.qr_dialog(_("Raw Transaction"), text, text_for_clipboard=raw_tx)
diff --git a/electrum/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py
new file mode 100644
index 000000000..d7b0469bf
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/wallets.py
@@ -0,0 +1,65 @@
+import os
+
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import ObjectProperty
+from kivy.lang import Builder
+
+from electrum.util import base_units
+
+from ...i18n import _
+from .label_dialog import LabelDialog
+
+Builder.load_string('''
+#:import os os
+:
+ title: _('Wallets')
+ id: popup
+ path: os.path.dirname(app.get_wallet_path())
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '10dp'
+ FileChooserListView:
+ id: wallet_selector
+ dirselect: False
+ filter_dirs: True
+ filter: '*.*'
+ path: root.path
+ rootpath: root.path
+ size_hint_y: 0.6
+ Widget
+ size_hint_y: 0.1
+ GridLayout:
+ cols: 3
+ size_hint_y: 0.1
+ Button:
+ id: open_button
+ size_hint: 0.1, None
+ height: '48dp'
+ text: _('New')
+ on_release:
+ popup.dismiss()
+ root.new_wallet(app, wallet_selector.path)
+ Button:
+ id: open_button
+ size_hint: 0.1, None
+ height: '48dp'
+ text: _('Open')
+ disabled: not wallet_selector.selection
+ on_release:
+ popup.dismiss()
+ root.open_wallet(app)
+''')
+
+class WalletDialog(Factory.Popup):
+
+ def new_wallet(self, app, dirname):
+ def cb(text):
+ if text:
+ app.load_wallet_by_name(os.path.join(dirname, text))
+ d = LabelDialog(_('Enter wallet name'), '', cb)
+ d.open()
+
+ def open_wallet(self, app):
+ app.load_wallet_by_name(self.ids.wallet_selector.selection[0])
+
diff --git a/electrum/gui/kivy/uix/drawer.py b/electrum/gui/kivy/uix/drawer.py
new file mode 100644
index 000000000..49a9c399b
--- /dev/null
+++ b/electrum/gui/kivy/uix/drawer.py
@@ -0,0 +1,257 @@
+'''Drawer Widget to hold the main window and the menu/hidden section that
+can be swiped in from the left. This Menu would be only hidden in phone mode
+and visible in Tablet Mode.
+
+This class is specifically in lined to save on start up speed(minimize i/o).
+'''
+
+from kivy.app import App
+from kivy.factory import Factory
+from kivy.properties import OptionProperty, NumericProperty, ObjectProperty
+from kivy.clock import Clock
+from kivy.lang import Builder
+
+import gc
+
+# delayed imports
+app = None
+
+
+class Drawer(Factory.RelativeLayout):
+ '''Drawer Widget to hold the main window and the menu/hidden section that
+ can be swiped in from the left. This Menu would be only hidden in phone mode
+ and visible in Tablet Mode.
+
+ '''
+
+ state = OptionProperty('closed',
+ options=('closed', 'open', 'opening', 'closing'))
+ '''This indicates the current state the drawer is in.
+
+ :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of
+ `closed`, `open`, `opening`, `closing`.
+ '''
+
+ scroll_timeout = NumericProperty(200)
+ '''Timeout allowed to trigger the :data:`scroll_distance`,
+ in milliseconds. If the user has not moved :data:`scroll_distance`
+ within the timeout, the scrolling will be disabled and the touch event
+ will go to the children.
+
+ :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to 200 (milliseconds)
+ '''
+
+ scroll_distance = NumericProperty('9dp')
+ '''Distance to move before scrolling the :class:`Drawer` in pixels.
+ As soon as the distance has been traveled, the :class:`Drawer` will
+ start to scroll, and no touch event will go to children.
+ It is advisable that you base this value on the dpi of your target
+ device's screen.
+
+ :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`
+ and defaults to 20dp.
+ '''
+
+ drag_area = NumericProperty('9dp')
+ '''The percentage of area on the left edge that triggers the opening of
+ the drawer. from 0-1
+
+ :attr:`drag_area` is a `NumericProperty` defaults to 2
+ '''
+
+ hidden_widget = ObjectProperty(None)
+ ''' This is the widget that is hidden in phone mode on the left side of
+ drawer or displayed on the left of the overlay widget in tablet mode.
+
+ :attr:`hidden_widget` is a `ObjectProperty` defaults to None.
+ '''
+
+ overlay_widget = ObjectProperty(None)
+ '''This a pointer to the default widget that is overlayed either on top or
+ to the right of the hidden widget.
+ '''
+
+ def __init__(self, **kwargs):
+ super(Drawer, self).__init__(**kwargs)
+
+ self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)
+
+ def toggle_drawer(self):
+ if app.ui_mode[0] == 't':
+ return
+ Factory.Animation.cancel_all(self.overlay_widget)
+ anim = Factory.Animation(x=self.hidden_widget.width
+ if self.state in ('opening', 'closed') else 0,
+ d=.1, t='linear')
+ anim.bind(on_complete = self._complete_drawer_animation)
+ anim.start(self.overlay_widget)
+
+ def _re_enable_gc(self, dt):
+ global gc
+ gc.enable()
+
+ def on_touch_down(self, touch):
+ if self.disabled:
+ return
+
+ if not self.collide_point(*touch.pos):
+ return
+
+ touch.grab(self)
+
+ # disable gc for smooth interaction
+ # This is still not enough while wallet is synchronising
+ # look into pausing all background tasks while ui interaction like this
+ gc.disable()
+
+ global app
+ if not app:
+ app = App.get_running_app()
+
+ # skip on tablet mode
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_down(touch)
+
+ state = self.state
+ touch.ud['send_touch_down'] = False
+ start = 0 #if state[0] == 'c' else self.hidden_widget.right
+ drag_area = self.drag_area\
+ if self.state[0] == 'c' else\
+ (self.overlay_widget.x)
+
+ if touch.x < start or touch.x > drag_area:
+ if self.state == 'open':
+ self.toggle_drawer()
+ return
+ return super(Drawer, self).on_touch_down(touch)
+
+ self._touch = touch
+ Clock.schedule_once(self._change_touch_mode,
+ self.scroll_timeout/1000.)
+ touch.ud['in_drag_area'] = True
+ touch.ud['send_touch_down'] = True
+ return
+
+ def on_touch_move(self, touch):
+ if not touch.grab_current is self:
+ return
+ self._touch = False
+ # skip on tablet mode
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_move(touch)
+
+ if not touch.ud.get('in_drag_area', None):
+ return super(Drawer, self).on_touch_move(touch)
+
+ ov = self.overlay_widget
+ ov.x=min(self.hidden_widget.width,
+ max(ov.x + touch.dx*2, 0))
+
+ #_anim = Animation(x=x, duration=1/2, t='in_out_quart')
+ #_anim.cancel_all(ov)
+ #_anim.start(ov)
+
+ if abs(touch.x - touch.ox) < self.scroll_distance:
+ return
+
+ touch.ud['send_touch_down'] = False
+ Clock.unschedule(self._change_touch_mode)
+ self._touch = None
+ self.state = 'opening' if touch.dx > 0 else 'closing'
+ touch.ox = touch.x
+ return
+
+ def _change_touch_mode(self, *args):
+ if not self._touch:
+ return
+ touch = self._touch
+ touch.ungrab(self)
+ touch.ud['in_drag_area'] = False
+ touch.ud['send_touch_down'] = False
+ self._touch = None
+ super(Drawer, self).on_touch_down(touch)
+ return
+
+ def on_touch_up(self, touch):
+ if not touch.grab_current is self:
+ return
+
+ self._triigger_gc()
+
+ touch.ungrab(self)
+ touch.grab_current = None
+
+ # skip on tablet mode
+ get = touch.ud.get
+ if app.ui_mode[0] == 't':
+ return super(Drawer, self).on_touch_up(touch)
+
+ self.old_x = [1, ] * 10
+ self.speed = sum((
+ (self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.
+
+ if get('send_touch_down', None):
+ # touch up called before moving
+ Clock.unschedule(self._change_touch_mode)
+ self._touch = None
+ Clock.schedule_once(
+ lambda dt: super(Drawer, self).on_touch_down(touch))
+ if get('in_drag_area', None):
+ if abs(touch.x - touch.ox) < self.scroll_distance:
+ anim_to = (0 if self.state[0] == 'c'
+ else self.hidden_widget.width)
+ Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)
+ return
+ touch.ud['in_drag_area'] = False
+ if not get('send_touch_down', None):
+ self.toggle_drawer()
+ Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))
+
+ def _complete_drawer_animation(self, *args):
+ self.state = 'open' if self.state in ('opening', 'closed') else 'closed'
+
+ def add_widget(self, widget, index=1):
+ if not widget:
+ return
+
+ iget = self.ids.get
+ if not iget('hidden_widget') or not iget('overlay_widget'):
+ super(Drawer, self).add_widget(widget)
+ return
+
+ if not self.hidden_widget:
+ self.hidden_widget = self.ids.hidden_widget
+ if not self.overlay_widget:
+ self.overlay_widget = self.ids.overlay_widget
+
+ if self.overlay_widget.children and self.hidden_widget.children:
+ Logger.debug('Drawer: Accepts only two widgets. discarding rest')
+ return
+
+ if not self.hidden_widget.children:
+ self.hidden_widget.add_widget(widget)
+ else:
+ self.overlay_widget.add_widget(widget)
+ widget.x = 0
+
+ def remove_widget(self, widget):
+ if self.overlay_widget.children[0] == widget:
+ self.overlay_widget.clear_widgets()
+ return
+ if widget == self.hidden_widget.children:
+ self.hidden_widget.clear_widgets()
+ return
+
+ def clear_widgets(self):
+ self.overlay_widget.clear_widgets()
+ self.hidden_widget.clear_widgets()
+
+if __name__ == '__main__':
+ from kivy.app import runTouchApp
+ from kivy.lang import Builder
+ runTouchApp(Builder.load_string('''
+Drawer:
+ Button:
+ Button
+'''))
\ No newline at end of file
diff --git a/electrum/gui/kivy/uix/gridview.py b/electrum/gui/kivy/uix/gridview.py
new file mode 100644
index 000000000..f8813ffc9
--- /dev/null
+++ b/electrum/gui/kivy/uix/gridview.py
@@ -0,0 +1,205 @@
+from kivy.uix.boxlayout import BoxLayout
+from kivy.adapters.dictadapter import DictAdapter
+from kivy.adapters.listadapter import ListAdapter
+from kivy.properties import ObjectProperty, ListProperty, AliasProperty
+from kivy.uix.listview import (ListItemButton, ListItemLabel, CompositeListItem,
+ ListView)
+from kivy.lang import Builder
+from kivy.metrics import dp, sp
+
+Builder.load_string('''
+
+ header_view: header_view
+ content_view: content_view
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '0dp', '2dp'
+ BoxLayout:
+ id: header_box
+ orientation: 'vertical'
+ size_hint: 1, None
+ height: '30dp'
+ ListView:
+ id: header_view
+ BoxLayout:
+ id: content_box
+ orientation: 'vertical'
+ ListView:
+ id: content_view
+
+<-HorizVertGrid>
+ header_view: header_view
+ content_view: content_view
+ ScrollView:
+ id: scrl
+ do_scroll_y: False
+ RelativeLayout:
+ size_hint_x: None
+ width: max(scrl.width, dp(sum(root.widths)))
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '0dp', '2dp'
+ BoxLayout:
+ id: header_box
+ orientation: 'vertical'
+ size_hint: 1, None
+ height: '30dp'
+ ListView:
+ id: header_view
+ BoxLayout:
+ id: content_box
+ orientation: 'vertical'
+ ListView:
+ id: content_view
+
+''')
+
+class GridView(BoxLayout):
+ """Workaround solution for grid view by using 2 list view.
+ Sometimes the height of lines is shown properly."""
+
+ def _get_hd_adpt(self):
+ return self.ids.header_view.adapter
+
+ header_adapter = AliasProperty(_get_hd_adpt, None)
+ '''
+ '''
+
+ def _get_cnt_adpt(self):
+ return self.ids.content_view.adapter
+
+ content_adapter = AliasProperty(_get_cnt_adpt, None)
+ '''
+ '''
+
+ headers = ListProperty([])
+ '''
+ '''
+
+ widths = ListProperty([])
+ '''
+ '''
+
+ data = ListProperty([])
+ '''
+ '''
+
+ getter = ObjectProperty(lambda item, i: item[i])
+ '''
+ '''
+ on_context_menu = ObjectProperty(None)
+
+ def __init__(self, **kwargs):
+ self._from_widths = False
+ super(GridView, self).__init__(**kwargs)
+ #self.on_headers(self, self.headers)
+
+ def on_widths(self, instance, value):
+ if not self.get_root_window():
+ return
+ self._from_widths = True
+ self.on_headers(instance, self.headers)
+ self._from_widths = False
+
+ def on_headers(self, instance, value):
+ if not self._from_widths:
+ return
+ if not (value and self.canvas and self.headers):
+ return
+ widths = self.widths
+ if len(self.widths) != len(value):
+ return
+ #if widths is not None:
+ # widths = ['%sdp' % i for i in widths]
+
+ def generic_args_converter(row_index,
+ item,
+ is_header=True,
+ getter=self.getter):
+ cls_dicts = []
+ _widths = self.widths
+ getter = self.getter
+ on_context_menu = self.on_context_menu
+
+ for i, header in enumerate(self.headers):
+ kwargs = {
+ 'padding': ('2dp','2dp'),
+ 'halign': 'center',
+ 'valign': 'middle',
+ 'size_hint_y': None,
+ 'shorten': True,
+ 'height': '30dp',
+ 'text_size': (_widths[i], dp(30)),
+ 'text': getter(item, i),
+ }
+
+ kwargs['font_size'] = '9sp'
+ if is_header:
+ kwargs['deselected_color'] = kwargs['selected_color'] =\
+ [0, 1, 1, 1]
+ else: # this is content
+ kwargs['deselected_color'] = 1, 1, 1, 1
+ if on_context_menu is not None:
+ kwargs['on_press'] = on_context_menu
+
+ if widths is not None: # set width manually
+ kwargs['size_hint_x'] = None
+ kwargs['width'] = widths[i]
+
+ cls_dicts.append({
+ 'cls': ListItemButton,
+ 'kwargs': kwargs,
+ })
+
+ return {
+ 'id': item[-1],
+ 'size_hint_y': None,
+ 'height': '30dp',
+ 'cls_dicts': cls_dicts,
+ }
+
+ def header_args_converter(row_index, item):
+ return generic_args_converter(row_index, item)
+
+ def content_args_converter(row_index, item):
+ return generic_args_converter(row_index, item, is_header=False)
+
+
+ self.ids.header_view.adapter = ListAdapter(data=[self.headers],
+ args_converter=header_args_converter,
+ selection_mode='single',
+ allow_empty_selection=False,
+ cls=CompositeListItem)
+
+ self.ids.content_view.adapter = ListAdapter(data=self.data,
+ args_converter=content_args_converter,
+ selection_mode='single',
+ allow_empty_selection=False,
+ cls=CompositeListItem)
+ self.content_adapter.bind_triggers_to_view(self.ids.content_view._trigger_reset_populate)
+
+class HorizVertGrid(GridView):
+ pass
+
+
+if __name__ == "__main__":
+ from kivy.app import App
+ class MainApp(App):
+
+ def build(self):
+ data = []
+ for i in range(90):
+ data.append((str(i), str(i)))
+ self.data = data
+ return Builder.load_string('''
+BoxLayout:
+ orientation: 'vertical'
+ HorizVertGrid:
+ on_parent: if args[1]: self.content_adapter.data = app.data
+ headers:['Address', 'Previous output']
+ widths: [400, 500]
+
+
+ font_size: '16sp'
+''')
+ MainApp().run()
diff --git a/electrum/gui/kivy/uix/menus.py b/electrum/gui/kivy/uix/menus.py
new file mode 100644
index 000000000..a7cdaefe2
--- /dev/null
+++ b/electrum/gui/kivy/uix/menus.py
@@ -0,0 +1,95 @@
+from functools import partial
+
+from kivy.animation import Animation
+from kivy.core.window import Window
+from kivy.clock import Clock
+from kivy.uix.bubble import Bubble, BubbleButton
+from kivy.properties import ListProperty
+from kivy.uix.widget import Widget
+
+from ..i18n import _
+
+class ContextMenuItem(Widget):
+ '''abstract class
+ '''
+
+class ContextButton(ContextMenuItem, BubbleButton):
+ pass
+
+class ContextMenu(Bubble):
+
+ buttons = ListProperty([_('ok'), _('cancel')])
+ '''List of Buttons to be displayed at the bottom'''
+
+ __events__ = ('on_press', 'on_release')
+
+ def __init__(self, **kwargs):
+ self._old_buttons = self.buttons
+ super(ContextMenu, self).__init__(**kwargs)
+ self.on_buttons(self, self.buttons)
+
+ def on_touch_down(self, touch):
+ if not self.collide_point(*touch.pos):
+ self.hide()
+ return
+ return super(ContextMenu, self).on_touch_down(touch)
+
+ def on_buttons(self, _menu, value):
+ if 'menu_content' not in self.ids.keys():
+ return
+ if value == self._old_buttons:
+ return
+ blayout = self.ids.menu_content
+ blayout.clear_widgets()
+ for btn in value:
+ ib = ContextButton(text=btn)
+ ib.bind(on_press=partial(self.dispatch, 'on_press'))
+ ib.bind(on_release=partial(self.dispatch, 'on_release'))
+ blayout.add_widget(ib)
+ self._old_buttons = value
+
+ def on_press(self, instance):
+ pass
+
+ def on_release(self, instance):
+ pass
+
+ def show(self, pos, duration=0):
+ Window.add_widget(self)
+ # wait for the bubble to adjust it's size according to text then animate
+ Clock.schedule_once(lambda dt: self._show(pos, duration))
+
+ def _show(self, pos, duration):
+ def on_stop(*l):
+ if duration:
+ Clock.schedule_once(self.hide, duration + .5)
+
+ self.opacity = 0
+ arrow_pos = self.arrow_pos
+ if arrow_pos[0] in ('l', 'r'):
+ pos = pos[0], pos[1] - (self.height/2)
+ else:
+ pos = pos[0] - (self.width/2), pos[1]
+
+ self.limit_to = Window
+
+ anim = Animation(opacity=1, pos=pos, d=.32)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
+
+
+ def hide(self, *dt):
+
+ def on_stop(*l):
+ Window.remove_widget(self)
+ anim = Animation(opacity=0, d=.25)
+ anim.bind(on_complete=on_stop)
+ anim.cancel_all(self)
+ anim.start(self)
+
+ def add_widget(self, widget, index=0):
+ if not isinstance(widget, ContextMenuItem):
+ super(ContextMenu, self).add_widget(widget, index)
+ return
+ menu_content.add_widget(widget, index)
diff --git a/electrum/gui/kivy/uix/qrcodewidget.py b/electrum/gui/kivy/uix/qrcodewidget.py
new file mode 100644
index 000000000..56e20cdd0
--- /dev/null
+++ b/electrum/gui/kivy/uix/qrcodewidget.py
@@ -0,0 +1,134 @@
+''' Kivy Widget that accepts data and displays qrcode
+'''
+
+from threading import Thread
+from functools import partial
+
+import qrcode
+from qrcode import exceptions
+
+from kivy.uix.floatlayout import FloatLayout
+from kivy.graphics.texture import Texture
+from kivy.properties import StringProperty
+from kivy.properties import ObjectProperty, StringProperty, ListProperty,\
+ BooleanProperty
+from kivy.lang import Builder
+from kivy.clock import Clock
+
+
+
+Builder.load_string('''
+
+ canvas.before:
+ # Draw white Rectangle
+ Color:
+ rgba: root.background_color
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ canvas.after:
+ Color:
+ rgba: root.foreground_color
+ Rectangle:
+ size: self.size
+ pos: self.pos
+ Image
+ id: qrimage
+ pos_hint: {'center_x': .5, 'center_y': .5}
+ allow_stretch: True
+ size_hint: None, None
+ size: root.width * .9, root.height * .9
+''')
+
+class QRCodeWidget(FloatLayout):
+
+ data = StringProperty(None, allow_none=True)
+ background_color = ListProperty((1, 1, 1, 1))
+ foreground_color = ListProperty((0, 0, 0, 0))
+
+ def __init__(self, **kwargs):
+ super(QRCodeWidget, self).__init__(**kwargs)
+ self.data = None
+ self.qr = None
+ self._qrtexture = None
+ self.failure_cb = None
+
+ def on_data(self, instance, value):
+ if not (self.canvas or value):
+ return
+ try:
+ self.update_qr()
+ except qrcode.exceptions.DataOverflowError:
+ if self.failure_cb:
+ self.failure_cb()
+ else:
+ raise
+
+ def set_data(self, data, failure_cb=None):
+ if self.data == data:
+ return
+ self.failure_cb = failure_cb
+ MinSize = 210 if len(data) < 128 else 500
+ self.setMinimumSize((MinSize, MinSize))
+ self.data = data
+ self.qr = None
+
+ def update_qr(self):
+ if not self.data and self.qr:
+ return
+ L = qrcode.constants.ERROR_CORRECT_L
+ data = self.data
+ self.qr = qr = qrcode.QRCode(
+ version=None,
+ error_correction=L,
+ box_size=10,
+ border=0,
+ )
+ qr.add_data(data)
+ qr.make(fit=True)
+ self.update_texture()
+
+ def setMinimumSize(self, size):
+ # currently unused, do we need this?
+ self._texture_size = size
+
+ def _create_texture(self, k):
+ self._qrtexture = texture = Texture.create(size=(k,k), colorfmt='rgb')
+ # don't interpolate texture
+ texture.min_filter = 'nearest'
+ texture.mag_filter = 'nearest'
+
+ def update_texture(self):
+ if not self.qr:
+ return
+ matrix = self.qr.get_matrix()
+ k = len(matrix)
+ # create the texture
+ self._create_texture(k)
+ buff = []
+ bext = buff.extend
+ cr, cg, cb, ca = self.background_color[:]
+ cr, cg, cb = cr*255, cg*255, cb*255
+ for r in range(k):
+ for c in range(k):
+ bext([0, 0, 0] if matrix[k-1-r][c] else [cr, cg, cb])
+ # then blit the buffer
+ buff = bytes(buff)
+ # update texture
+ self._upd_texture(buff)
+
+ def _upd_texture(self, buff):
+ texture = self._qrtexture
+ texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
+ img = self.ids.qrimage
+ img.anim_delay = -1
+ img.texture = texture
+ img.canvas.ask_update()
+
+if __name__ == '__main__':
+ from kivy.app import runTouchApp
+ import sys
+ data = str(sys.argv[1:])
+ runTouchApp(QRCodeWidget(data=data))
+
+
diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
new file mode 100644
index 000000000..3a4c6a8b1
--- /dev/null
+++ b/electrum/gui/kivy/uix/screens.py
@@ -0,0 +1,484 @@
+from weakref import ref
+from decimal import Decimal
+import re
+import datetime
+import traceback, sys
+
+from kivy.app import App
+from kivy.cache import Cache
+from kivy.clock import Clock
+from kivy.compat import string_types
+from kivy.properties import (ObjectProperty, DictProperty, NumericProperty,
+ ListProperty, StringProperty)
+
+from kivy.uix.recycleview import RecycleView
+from kivy.uix.label import Label
+
+from kivy.lang import Builder
+from kivy.factory import Factory
+from kivy.utils import platform
+
+from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
+from electrum import bitcoin
+from electrum.transaction import TxOutput
+from electrum.util import timestamp_to_datetime
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from electrum.plugin import run_hook
+
+from .context_menu import ContextMenu
+
+
+from electrum.gui.kivy.i18n import _
+
+class HistoryRecycleView(RecycleView):
+ pass
+
+class CScreen(Factory.Screen):
+ __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
+ action_view = ObjectProperty(None)
+ loaded = False
+ kvname = None
+ context_menu = None
+ menu_actions = []
+ app = App.get_running_app()
+
+ def _change_action_view(self):
+ app = App.get_running_app()
+ action_bar = app.root.manager.current_screen.ids.action_bar
+ _action_view = self.action_view
+
+ if (not _action_view) or _action_view.parent:
+ return
+ action_bar.clear_widgets()
+ action_bar.add_widget(_action_view)
+
+ def on_enter(self):
+ # FIXME: use a proper event don't use animation time of screen
+ Clock.schedule_once(lambda dt: self.dispatch('on_activate'), .25)
+ pass
+
+ def update(self):
+ pass
+
+ @profiler
+ def load_screen(self):
+ self.screen = Builder.load_file('electrum/gui/kivy/uix/ui_screens/' + self.kvname + '.kv')
+ self.add_widget(self.screen)
+ self.loaded = True
+ self.update()
+ setattr(self.app, self.kvname + '_screen', self)
+
+ def on_activate(self):
+ if self.kvname and not self.loaded:
+ self.load_screen()
+ #Clock.schedule_once(lambda dt: self._change_action_view())
+
+ def on_leave(self):
+ self.dispatch('on_deactivate')
+
+ def on_deactivate(self):
+ self.hide_menu()
+
+ def hide_menu(self):
+ if self.context_menu is not None:
+ self.remove_widget(self.context_menu)
+ self.context_menu = None
+
+ def show_menu(self, obj):
+ self.hide_menu()
+ self.context_menu = ContextMenu(obj, self.menu_actions)
+ self.add_widget(self.context_menu)
+
+
+# note: this list needs to be kept in sync with another in qt
+TX_ICONS = [
+ "unconfirmed",
+ "close",
+ "unconfirmed",
+ "close",
+ "clock1",
+ "clock2",
+ "clock3",
+ "clock4",
+ "clock5",
+ "confirmed",
+]
+
+class HistoryScreen(CScreen):
+
+ tab = ObjectProperty(None)
+ kvname = 'history'
+ cards = {}
+
+ def __init__(self, **kwargs):
+ self.ra_dialog = None
+ super(HistoryScreen, self).__init__(**kwargs)
+ self.menu_actions = [ ('Label', self.label_dialog), ('Details', self.show_tx)]
+
+ def show_tx(self, obj):
+ tx_hash = obj.tx_hash
+ tx = self.app.wallet.transactions.get(tx_hash)
+ if not tx:
+ return
+ self.app.tx_dialog(tx)
+
+ def label_dialog(self, obj):
+ from .dialogs.label_dialog import LabelDialog
+ key = obj.tx_hash
+ text = self.app.wallet.get_label(key)
+ def callback(text):
+ self.app.wallet.set_label(key, text)
+ self.update()
+ d = LabelDialog(_('Enter Transaction Label'), text, callback)
+ d.open()
+
+ def get_card(self, tx_hash, tx_mined_status, value, balance):
+ status, status_str = self.app.wallet.get_tx_status(tx_hash, tx_mined_status)
+ icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
+ label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
+ ri = {}
+ ri['screen'] = self
+ ri['tx_hash'] = tx_hash
+ ri['icon'] = icon
+ ri['date'] = status_str
+ ri['message'] = label
+ ri['confirmations'] = tx_mined_status.conf
+ if value is not None:
+ ri['is_mine'] = value < 0
+ if value < 0: value = - value
+ ri['amount'] = self.app.format_amount_and_units(value)
+ if self.app.fiat_unit:
+ fx = self.app.fx
+ fiat_value = value / Decimal(bitcoin.COIN) * self.app.wallet.price_at_timestamp(tx_hash, fx.timestamp_rate)
+ fiat_value = Fiat(fiat_value, fx.ccy)
+ ri['quote_text'] = str(fiat_value)
+ return ri
+
+ def update(self, see_all=False):
+ if self.app.wallet is None:
+ return
+ history = reversed(self.app.wallet.get_history())
+ history_card = self.screen.ids.history_container
+ history_card.data = [self.get_card(*item) for item in history]
+
+
+class SendScreen(CScreen):
+
+ kvname = 'send'
+ payment_request = None
+ payment_request_queued = None
+
+ def set_URI(self, text):
+ if not self.app.wallet:
+ self.payment_request_queued = text
+ return
+ import electrum
+ try:
+ uri = electrum.util.parse_URI(text, self.app.on_pr)
+ except:
+ self.app.show_info(_("Not a Bitcore URI"))
+ return
+ amount = uri.get('amount')
+ self.screen.address = uri.get('address', '')
+ self.screen.message = uri.get('message', '')
+ self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
+ self.payment_request = None
+ self.screen.is_pr = False
+
+ def update(self):
+ if self.app.wallet and self.payment_request_queued:
+ self.set_URI(self.payment_request_queued)
+ self.payment_request_queued = None
+
+ def do_clear(self):
+ self.screen.amount = ''
+ self.screen.message = ''
+ self.screen.address = ''
+ self.payment_request = None
+ self.screen.is_pr = False
+
+ def set_request(self, pr):
+ self.screen.address = pr.get_requestor()
+ amount = pr.get_amount()
+ self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
+ self.screen.message = pr.get_memo()
+ if pr.is_pr():
+ self.screen.is_pr = True
+ self.payment_request = pr
+ else:
+ self.screen.is_pr = False
+ self.payment_request = None
+
+ def do_save(self):
+ if not self.screen.address:
+ return
+ if self.screen.is_pr:
+ # it should be already saved
+ return
+ # save address as invoice
+ from electrum.paymentrequest import make_unsigned_request, PaymentRequest
+ req = {'address':self.screen.address, 'memo':self.screen.message}
+ amount = self.app.get_amount(self.screen.amount) if self.screen.amount else 0
+ req['amount'] = amount
+ pr = make_unsigned_request(req).SerializeToString()
+ pr = PaymentRequest(pr)
+ self.app.wallet.invoices.add(pr)
+ self.app.show_info(_("Invoice saved"))
+ if pr.is_pr():
+ self.screen.is_pr = True
+ self.payment_request = pr
+ else:
+ self.screen.is_pr = False
+ self.payment_request = None
+
+ def do_paste(self):
+ contents = self.app._clipboard.paste()
+ if not contents:
+ self.app.show_info(_("Clipboard is empty"))
+ return
+ self.set_URI(contents)
+
+ def do_send(self):
+ if self.screen.is_pr:
+ if self.payment_request.has_expired():
+ self.app.show_error(_('Payment request has expired'))
+ return
+ outputs = self.payment_request.get_outputs()
+ else:
+ address = str(self.screen.address)
+ if not address:
+ self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Bitcore address or a payment request'))
+ return
+ if not bitcoin.is_address(address):
+ self.app.show_error(_('Invalid Bitcore Address') + ':\n' + address)
+ return
+ try:
+ amount = self.app.get_amount(self.screen.amount)
+ except:
+ self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
+ return
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
+ message = self.screen.message
+ amount = sum(map(lambda x:x[2], outputs))
+ if self.app.electrum_config.get('use_rbf'):
+ from .dialogs.question import Question
+ d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))
+ d.open()
+ else:
+ self._do_send(amount, message, outputs, False)
+
+ def _do_send(self, amount, message, outputs, rbf):
+ # make unsigned transaction
+ config = self.app.electrum_config
+ coins = self.app.wallet.get_spendable_coins(None, config)
+ try:
+ tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
+ except NotEnoughFunds:
+ self.app.show_error(_("Not enough funds"))
+ return
+ except Exception as e:
+ traceback.print_exc(file=sys.stdout)
+ self.app.show_error(str(e))
+ return
+ if rbf:
+ tx.set_rbf(True)
+ fee = tx.get_fee()
+ msg = [
+ _("Amount to be sent") + ": " + self.app.format_amount_and_units(amount),
+ _("Mining fee") + ": " + self.app.format_amount_and_units(fee),
+ ]
+ x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx)
+ if x_fee:
+ x_fee_address, x_fee_amount = x_fee
+ msg.append(_("Additional fees") + ": " + self.app.format_amount_and_units(x_fee_amount))
+
+ if fee >= config.get('confirm_fee', 100000):
+ msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
+ msg.append(_("Enter your PIN code to proceed"))
+ self.app.protected('\n'.join(msg), self.send_tx, (tx, message))
+
+ def send_tx(self, tx, message, password):
+ if self.app.wallet.has_password() and password is None:
+ return
+ def on_success(tx):
+ if tx.is_complete():
+ self.app.broadcast(tx, self.payment_request)
+ self.app.wallet.set_label(tx.txid(), message)
+ else:
+ self.app.tx_dialog(tx)
+ def on_failure(error):
+ self.app.show_error(error)
+ if self.app.wallet.can_sign(tx):
+ self.app.show_info("Signing...")
+ self.app.sign_tx(tx, password, on_success, on_failure)
+ else:
+ self.app.tx_dialog(tx)
+
+
+class ReceiveScreen(CScreen):
+
+ kvname = 'receive'
+
+ def update(self):
+ if not self.screen.address:
+ self.get_new_address()
+ else:
+ status = self.app.wallet.get_request_status(self.screen.address)
+ self.screen.status = _('Payment received') if status == PR_PAID else ''
+
+ def clear(self):
+ self.screen.address = ''
+ self.screen.amount = ''
+ self.screen.message = ''
+
+ def get_new_address(self):
+ if not self.app.wallet:
+ return False
+ self.clear()
+ addr = self.app.wallet.get_unused_address()
+ if addr is None:
+ addr = self.app.wallet.get_receiving_address() or ''
+ b = False
+ else:
+ b = True
+ self.screen.address = addr
+ return b
+
+ def on_address(self, addr):
+ req = self.app.wallet.get_payment_request(addr, self.app.electrum_config)
+ self.screen.status = ''
+ if req:
+ self.screen.message = req.get('memo', '')
+ amount = req.get('amount')
+ self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
+ status = req.get('status', PR_UNKNOWN)
+ self.screen.status = _('Payment received') if status == PR_PAID else ''
+ Clock.schedule_once(lambda dt: self.update_qr())
+
+ def get_URI(self):
+ from electrum.util import create_URI
+ amount = self.screen.amount
+ if amount:
+ a, u = self.screen.amount.split()
+ assert u == self.app.base_unit
+ amount = Decimal(a) * pow(10, self.app.decimal_point())
+ return create_URI(self.screen.address, amount, self.screen.message)
+
+ @profiler
+ def update_qr(self):
+ uri = self.get_URI()
+ qr = self.screen.ids.qr
+ qr.set_data(uri)
+
+ def do_share(self):
+ uri = self.get_URI()
+ self.app.do_share(uri, _("Share Bitcore Request"))
+
+ def do_copy(self):
+ uri = self.get_URI()
+ self.app._clipboard.copy(uri)
+ self.app.show_info(_('Request copied to clipboard'))
+
+ def save_request(self):
+ addr = self.screen.address
+ if not addr:
+ return False
+ amount = self.screen.amount
+ message = self.screen.message
+ amount = self.app.get_amount(amount) if amount else 0
+ req = self.app.wallet.make_payment_request(addr, amount, message, None)
+ try:
+ self.app.wallet.add_payment_request(req, self.app.electrum_config)
+ added_request = True
+ except Exception as e:
+ self.app.show_error(_('Error adding payment request') + ':\n' + str(e))
+ added_request = False
+ finally:
+ self.app.update_tab('requests')
+ return added_request
+
+ def on_amount_or_message(self):
+ Clock.schedule_once(lambda dt: self.update_qr())
+
+ def do_new(self):
+ addr = self.get_new_address()
+ if not addr:
+ self.app.show_info(_('Please use the existing requests first.'))
+
+ def do_save(self):
+ if self.save_request():
+ self.app.show_info(_('Request was saved.'))
+
+
+class TabbedCarousel(Factory.TabbedPanel):
+ '''Custom TabbedPanel using a carousel used in the Main Screen
+ '''
+
+ carousel = ObjectProperty(None)
+
+ def animate_tab_to_center(self, value):
+ scrlv = self._tab_strip.parent
+ if not scrlv:
+ return
+ idx = self.tab_list.index(value)
+ n = len(self.tab_list)
+ if idx in [0, 1]:
+ scroll_x = 1
+ elif idx in [n-1, n-2]:
+ scroll_x = 0
+ else:
+ scroll_x = 1. * (n - idx - 1) / (n - 1)
+ mation = Factory.Animation(scroll_x=scroll_x, d=.25)
+ mation.cancel_all(scrlv)
+ mation.start(scrlv)
+
+ def on_current_tab(self, instance, value):
+ self.animate_tab_to_center(value)
+
+ def on_index(self, instance, value):
+ current_slide = instance.current_slide
+ if not hasattr(current_slide, 'tab'):
+ return
+ tab = current_slide.tab
+ ct = self.current_tab
+ try:
+ if ct.text != tab.text:
+ carousel = self.carousel
+ carousel.slides[ct.slide].dispatch('on_leave')
+ self.switch_to(tab)
+ carousel.slides[tab.slide].dispatch('on_enter')
+ except AttributeError:
+ current_slide.dispatch('on_enter')
+
+ def switch_to(self, header):
+ # we have to replace the functionality of the original switch_to
+ if not header:
+ return
+ if not hasattr(header, 'slide'):
+ header.content = self.carousel
+ super(TabbedCarousel, self).switch_to(header)
+ try:
+ tab = self.tab_list[-1]
+ except IndexError:
+ return
+ self._current_tab = tab
+ tab.state = 'down'
+ return
+
+ carousel = self.carousel
+ self.current_tab.state = "normal"
+ header.state = 'down'
+ self._current_tab = header
+ # set the carousel to load the appropriate slide
+ # saved in the screen attribute of the tab head
+ slide = carousel.slides[header.slide]
+ if carousel.current_slide != slide:
+ carousel.current_slide.dispatch('on_leave')
+ carousel.load_slide(slide)
+ slide.dispatch('on_enter')
+
+ def add_widget(self, widget, index=0):
+ if isinstance(widget, Factory.CScreen):
+ self.carousel.add_widget(widget)
+ return
+ super(TabbedCarousel, self).add_widget(widget, index=index)
diff --git a/electrum/gui/kivy/uix/ui_screens/about.kv b/electrum/gui/kivy/uix/ui_screens/about.kv
new file mode 100644
index 000000000..e542b557e
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/about.kv
@@ -0,0 +1,55 @@
+#:import VERSION electrum.version.ELECTRUM_VERSION
+
+Popup:
+ title: _("About Electrum")
+ BoxLayout:
+ orientation: 'vertical'
+ spacing: '10dp'
+ padding: '10dp'
+ GridLayout:
+ cols: 2
+ spacing: '10dp'
+ TopLabel:
+ text: _('Version')
+ size_hint_x: 0.4
+ TopLabel:
+ text: VERSION
+ size_hint_x: 0.6
+ TopLabel:
+ text: _('Licence')
+ size_hint_x: 0.4
+ TopLabel:
+ text: "MIT Licence"
+ size_hint_x: 0.6
+ TopLabel:
+ text: _('Homepage')
+ size_hint_x: 0.4
+ TopLabel:
+ markup: True
+ text: '[color=6666ff][ref=x]https://electrum.org[/ref][/color]'
+ on_ref_press:
+ import webbrowser
+ webbrowser.open("https://electrum.org")
+ size_hint_x: 0.6
+ TopLabel:
+ text: _('Developers')
+ size_hint_x: 0.4
+ TopLabel:
+ text: '\n'.join(['Thomas Voegtlin', 'Neil Booth', 'Akshay Arora'])
+ size_hint_x: 0.6
+ TopLabel:
+ text: _('Distributed by Electrum Technologies GmbH')
+ padding: '0dp', '20dp'
+ Widget:
+ size_hint: None, 0.5
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Widget:
+ size_hint: 0.5, None
+ height: '48dp'
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('Close')
+ on_release: root.dismiss()
diff --git a/electrum/gui/kivy/uix/ui_screens/history.kv b/electrum/gui/kivy/uix/ui_screens/history.kv
new file mode 100644
index 000000000..04988a70f
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/history.kv
@@ -0,0 +1,78 @@
+#:import _ electrum.gui.kivy.i18n._
+#:import Factory kivy.factory.Factory
+#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
+#:set btc_symbol chr(171)
+#:set mbtc_symbol chr(187)
+
+
+
+
+ color: 0.95, 0.95, 0.95, 1
+ size_hint: 1, None
+ text: ''
+ text_size: self.width, None
+ height: self.texture_size[1]
+ halign: 'left'
+ valign: 'top'
+
+
+
+ icon: 'atlas://electrum/gui/kivy/theming/light/important'
+ message: ''
+ is_mine: True
+ amount: '--'
+ action: _('Sent') if self.is_mine else _('Received')
+ amount_color: '#FF6657' if self.is_mine else '#2EA442'
+ confirmations: 0
+ date: ''
+ quote_text: ''
+ Image:
+ id: icon
+ source: root.icon
+ size_hint: None, 1
+ allow_stretch: True
+ width: self.height*1.5
+ mipmap: True
+ BoxLayout:
+ orientation: 'vertical'
+ Widget
+ CardLabel:
+ text:
+ u'[color={color}]{s}[/color]'.format(s='<<' if root.is_mine else '>>', color=root.amount_color)\
+ + ' ' + root.action + ' ' + (root.quote_text if app.is_fiat else root.amount)
+ font_size: '15sp'
+ CardLabel:
+ color: .699, .699, .699, 1
+ font_size: '14sp'
+ shorten: True
+ text: root.date + ' ' + root.message
+ Widget
+
+:
+ viewclass: 'HistoryItem'
+ RecycleBoxLayout:
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint: 1, None
+ height: self.minimum_height
+ orientation: 'vertical'
+
+
+HistoryScreen:
+ name: 'history'
+ content: history_container
+ BoxLayout:
+ orientation: 'vertical'
+ Button:
+ background_color: 0, 0, 0, 0
+ text: app.fiat_balance if app.is_fiat else app.balance
+ markup: True
+ color: .9, .9, .9, 1
+ font_size: '30dp'
+ bold: True
+ size_hint: 1, 0.25
+ on_release: app.is_fiat = not app.is_fiat if app.fx.is_enabled() else False
+ HistoryRecycleView:
+ id: history_container
+ scroll_type: ['bars', 'content']
+ bar_width: '25dp'
diff --git a/electrum/gui/kivy/uix/ui_screens/invoice.kv b/electrum/gui/kivy/uix/ui_screens/invoice.kv
new file mode 100644
index 000000000..d3d4a179a
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/invoice.kv
@@ -0,0 +1,89 @@
+#:import Decimal decimal.Decimal
+
+
+
+Popup:
+ id: popup
+ is_invoice: True
+ amount: 0
+ requestor: ''
+ exp: ''
+ description: ''
+ status: ''
+ signature: ''
+ isaddr: ''
+ fund: 0
+ pk: ''
+ title: _('Invoice') if popup.is_invoice else _('Request')
+ tx_hash: ''
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ GridLayout:
+ cols: 1
+ height: self.minimum_height
+ size_hint_y: None
+ padding: '10dp'
+ spacing: '10dp'
+ GridLayout:
+ cols: 1
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: '10dp'
+ BoxLabel:
+ text: (_('Status') if popup.amount or popup.is_invoice or popup.isaddr == 'y' else _('Amount received')) if root.status else ''
+ value: root.status
+ BoxLabel:
+ text: _('Request amount') if root.amount else ''
+ value: app.format_amount_and_units(root.amount) if root.amount else ''
+ BoxLabel:
+ text: _('Requestor') if popup.is_invoice else _('Address')
+ value: root.requestor
+ BoxLabel:
+ text: _('Signature') if root.signature else ''
+ value: root.signature
+ BoxLabel:
+ text: _('Expiration') if root.exp else ''
+ value: root.exp
+ BoxLabel:
+ text: _('Description') if root.description else ''
+ value: root.description
+ BoxLabel:
+ text: _('Balance') if popup.fund else ''
+ value: app.format_amount_and_units(root.fund) if root.fund else ''
+ TopLabel:
+ text: _('Private Key')
+ RefLabel:
+ id: pk_label
+ touched: True if not self.touched else True
+ data: root.pk
+
+ TopLabel:
+ text: _('Outputs') if popup.is_invoice else ''
+ OutputList:
+ id: output_list
+ TopLabel:
+ text: _('Transaction ID') if popup.tx_hash else ''
+ TxHashLabel:
+ data: popup.tx_hash
+ name: _('Transaction ID')
+ Widget:
+ size_hint: 1, 0.1
+
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Widget:
+ size_hint: 0.5, None
+ height: '48dp'
+ Button:
+ size_hint: 2, None
+ height: '48dp'
+ text: _('Close')
+ on_release: popup.dismiss()
+ Button:
+ size_hint: 2, None
+ height: '48dp'
+ text: _('Hide private key') if pk_label.data else _('Export private key')
+ on_release:
+ setattr(pk_label, 'data', '') if pk_label.data else popup.export(pk_label, popup.requestor)
diff --git a/electrum/gui/kivy/uix/ui_screens/network.kv b/electrum/gui/kivy/uix/ui_screens/network.kv
new file mode 100644
index 000000000..99fb8366d
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/network.kv
@@ -0,0 +1,53 @@
+Popup:
+ id: nd
+ title: _('Network')
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ GridLayout:
+ id: scrollviewlayout
+ cols:1
+ size_hint: 1, None
+ height: self.minimum_height
+ padding: '10dp'
+ SettingsItem:
+ value: _("{} connections.").format(app.num_nodes) if app.num_nodes else _("Not connected")
+ title: _("Status") + ': ' + self.value
+ description: _("Connections with Electrum servers")
+ action: lambda x: None
+
+ CardSeparator
+ SettingsItem:
+ title: _("Server") + ': ' + app.server_host
+ description: _("Server used to query your history.")
+ action: lambda x: app.popup_dialog('server')
+
+ CardSeparator
+ SettingsItem:
+ proxy: app.proxy_config.get('mode')
+ host: app.proxy_config.get('host')
+ port: app.proxy_config.get('port')
+ title: _("Proxy") + ': ' + ((self.host +':' + self.port) if self.proxy else _('None'))
+ description: _('Proxy configuration')
+ action: lambda x: app.popup_dialog('proxy')
+
+ CardSeparator
+ SettingsItem:
+ title: _("Auto-connect") + ': ' + ('ON' if app.auto_connect else 'OFF')
+ description: _("Select your server automatically")
+ action: app.toggle_auto_connect
+
+ CardSeparator
+ SettingsItem:
+ value: "%d blocks" % app.num_blocks
+ title: _("Blockchain") + ': ' + self.value
+ description: _('Verified block headers')
+ action: lambda x: x
+
+ CardSeparator
+ SettingsItem:
+ title: _('Fork detected at block {}').format(app.blockchain_forkpoint) if app.num_chains>1 else _('No fork detected')
+ fork_description: (_('You are following branch') if app.auto_connect else _("Your server is on branch")) + ' ' + app.blockchain_name
+ description: self.fork_description if app.num_chains>1 else _('Connected nodes are on the same chain')
+ action: app.choose_blockchain_dialog
+ disabled: app.num_chains == 1
diff --git a/electrum/gui/kivy/uix/ui_screens/proxy.kv b/electrum/gui/kivy/uix/ui_screens/proxy.kv
new file mode 100644
index 000000000..738a8ffd5
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/proxy.kv
@@ -0,0 +1,76 @@
+Popup:
+ id: nd
+ title: _('Proxy')
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '10dp'
+ spacing: '10dp'
+ GridLayout:
+ cols: 2
+ Label:
+ text: _('Proxy mode')
+ Spinner:
+ id: mode
+ height: '48dp'
+ size_hint_y: None
+ text: app.proxy_config.get('mode', 'none')
+ values: ['none', 'socks4', 'socks5', 'http']
+ Label:
+ text: _('Host')
+ TextInput:
+ id: host
+ multiline: False
+ height: '48dp'
+ size_hint_y: None
+ text: app.proxy_config.get('host', '')
+ disabled: mode.text == 'none'
+ Label:
+ text: _('Port')
+ TextInput:
+ id: port
+ multiline: False
+ input_type: 'number'
+ height: '48dp'
+ size_hint_y: None
+ text: app.proxy_config.get('port', '')
+ disabled: mode.text == 'none'
+ Label:
+ text: _('Username')
+ TextInput:
+ id: user
+ multiline: False
+ height: '48dp'
+ size_hint_y: None
+ text: app.proxy_config.get('user', '')
+ disabled: mode.text == 'none'
+ Label:
+ text: _('Password')
+ TextInput:
+ id: password
+ multiline: False
+ password: True
+ height: '48dp'
+ size_hint_y: None
+ text: app.proxy_config.get('password', '')
+ disabled: mode.text == 'none'
+ Widget:
+ size_hint: 1, 0.1
+ BoxLayout:
+ Widget:
+ size_hint: 0.5, None
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('OK')
+ on_release:
+ host, port, protocol, proxy, auto_connect = app.network.get_parameters()
+ proxy = {}
+ proxy['mode']=str(root.ids.mode.text).lower()
+ proxy['host']=str(root.ids.host.text)
+ proxy['port']=str(root.ids.port.text)
+ proxy['user']=str(root.ids.user.text)
+ proxy['password']=str(root.ids.password.text)
+ if proxy['mode']=='none': proxy = None
+ app.network.set_parameters(host, port, protocol, proxy, auto_connect)
+ app.proxy_config = proxy if proxy else {}
+ nd.dismiss()
diff --git a/electrum/gui/kivy/uix/ui_screens/receive.kv b/electrum/gui/kivy/uix/ui_screens/receive.kv
new file mode 100644
index 000000000..fe453d2a6
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/receive.kv
@@ -0,0 +1,142 @@
+#:import _ electrum.gui.kivy.i18n._
+#:import Decimal decimal.Decimal
+#:set btc_symbol chr(171)
+#:set mbtc_symbol chr(187)
+#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
+
+
+
+ReceiveScreen:
+ id: s
+ name: 'receive'
+
+ address: ''
+ amount: ''
+ message: ''
+ status: ''
+
+ on_address:
+ self.parent.on_address(self.address)
+ on_amount:
+ self.parent.on_amount_or_message()
+ on_message:
+ self.parent.on_amount_or_message()
+
+ BoxLayout
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ orientation: 'vertical'
+ size_hint: 1, 1
+ FloatLayout:
+ id: bl
+ QRCodeWidget:
+ id: qr
+ size_hint: None, 1
+ width: min(self.height, bl.width)
+ pos_hint: {'center': (.5, .5)}
+ shaded: False
+ foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)
+ on_touch_down:
+ touch = args[1]
+ if self.collide_point(*touch.pos): self.shaded = not self.shaded
+ Label:
+ text: root.status
+ opacity: 1 if root.status else 0
+ pos_hint: {'center': (.5, .5)}
+ size_hint: None, 1
+ width: min(self.height, bl.width)
+ bcolor: 0.3, 0.3, 0.3, 0.9
+ canvas.before:
+ Color:
+ rgba: self.bcolor
+ Rectangle:
+ pos: self.pos
+ size: self.size
+
+ SendReceiveBlueBottom:
+ id: blue_bottom
+ size_hint: 1, None
+ height: self.minimum_height
+ BoxLayout:
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/globe'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: address_label
+ text: s.address if s.address else _('Bitcore Address')
+ shorten: True
+ on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
+ CardSeparator:
+ opacity: message_selection.opacity
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/calculator'
+ opacity: 0.7
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: amount_label
+ default_text: _('Amount')
+ text: s.amount if s.amount else _('Amount')
+ on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, False))
+ CardSeparator:
+ opacity: message_selection.opacity
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ id: message_selection
+ opacity: 1
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/pen'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: description
+ text: s.message if s.message else _('Description')
+ on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ IconButton:
+ icon: 'atlas://electrum/gui/kivy/theming/light/save'
+ size_hint: 0.6, None
+ height: '48dp'
+ on_release: s.parent.do_save()
+ Button:
+ text: _('Requests')
+ size_hint: 1, None
+ height: '48dp'
+ on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
+ Button:
+ text: _('Copy')
+ size_hint: 1, None
+ height: '48dp'
+ on_release: s.parent.do_copy()
+ IconButton:
+ icon: 'atlas://electrum/gui/kivy/theming/light/share'
+ size_hint: 0.6, None
+ height: '48dp'
+ on_release: s.parent.do_share()
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Widget
+ size_hint: 2, 1
+ Button:
+ text: _('New')
+ size_hint: 1, None
+ height: '48dp'
+ on_release: Clock.schedule_once(lambda dt: s.parent.do_new())
diff --git a/electrum/gui/kivy/uix/ui_screens/send.kv b/electrum/gui/kivy/uix/ui_screens/send.kv
new file mode 100644
index 000000000..88cbcc3a9
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/send.kv
@@ -0,0 +1,127 @@
+#:import _ electrum.gui.kivy.i18n._
+#:import Decimal decimal.Decimal
+#:set btc_symbol chr(171)
+#:set mbtc_symbol chr(187)
+#:set font_light 'electrum/gui/kivy/data/fonts/Roboto-Condensed.ttf'
+
+
+SendScreen:
+ id: s
+ name: 'send'
+ address: ''
+ amount: ''
+ message: ''
+ is_pr: False
+ BoxLayout
+ padding: '12dp', '12dp', '12dp', '12dp'
+ spacing: '12dp'
+ orientation: 'vertical'
+ SendReceiveBlueBottom:
+ id: blue_bottom
+ size_hint: 1, None
+ height: self.minimum_height
+ BoxLayout:
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/globe'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: payto_e
+ text: s.address if s.address else _('Recipient')
+ shorten: True
+ 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.')))
+ #on_release: Clock.schedule_once(lambda dt: app.popup_dialog('contacts'))
+ CardSeparator:
+ opacity: int(not root.is_pr)
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/calculator'
+ opacity: 0.7
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: amount_e
+ default_text: _('Amount')
+ text: s.amount if s.amount else _('Amount')
+ disabled: root.is_pr
+ on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, True))
+ CardSeparator:
+ opacity: int(not root.is_pr)
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ id: message_selection
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/pen'
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: description
+ text: s.message if s.message else (_('No Description') if root.is_pr else _('Description'))
+ disabled: root.is_pr
+ on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
+ CardSeparator:
+ opacity: int(not root.is_pr)
+ color: blue_bottom.foreground_color
+ BoxLayout:
+ size_hint: 1, None
+ height: blue_bottom.item_height
+ spacing: '5dp'
+ Image:
+ source: 'atlas://electrum/gui/kivy/theming/light/star_big_inactive'
+ opacity: 0.7
+ size_hint: None, None
+ size: '22dp', '22dp'
+ pos_hint: {'center_y': .5}
+ BlueButton:
+ id: fee_e
+ default_text: _('Fee')
+ text: app.fee_status
+ on_release: Clock.schedule_once(lambda dt: app.fee_dialog(s, True))
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ IconButton:
+ size_hint: 0.6, 1
+ on_release: s.parent.do_save()
+ icon: 'atlas://electrum/gui/kivy/theming/light/save'
+ Button:
+ text: _('Invoices')
+ size_hint: 1, 1
+ on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s))
+ Button:
+ text: _('Paste')
+ on_release: s.parent.do_paste()
+ IconButton:
+ id: qr
+ size_hint: 0.6, 1
+ on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))
+ icon: 'atlas://electrum/gui/kivy/theming/light/camera'
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ text: _('Clear')
+ on_release: s.parent.do_clear()
+ Widget:
+ size_hint: 1, 1
+ Button:
+ text: _('Pay')
+ size_hint: 1, 1
+ on_release: s.parent.do_send()
+ Widget:
+ size_hint: 1, 1
+
+
diff --git a/electrum/gui/kivy/uix/ui_screens/server.kv b/electrum/gui/kivy/uix/ui_screens/server.kv
new file mode 100644
index 000000000..293d46452
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/server.kv
@@ -0,0 +1,63 @@
+Popup:
+ id: nd
+ title: _('Server')
+ BoxLayout:
+ orientation: 'vertical'
+ padding: '10dp'
+ spacing: '10dp'
+ TopLabel:
+ 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).")
+ font_size: '6pt'
+ Widget:
+ size_hint: 1, 0.8
+ GridLayout:
+ cols: 2
+ Label:
+ height: '36dp'
+ size_hint_x: 1
+ size_hint_y: None
+ text: _('Host') + ':'
+ TextInput:
+ id: host
+ multiline: False
+ height: '36dp'
+ size_hint_x: 3
+ size_hint_y: None
+ text: app.server_host
+ Label:
+ height: '36dp'
+ size_hint_x: 1
+ size_hint_y: None
+ text: _('Port') + ':'
+ TextInput:
+ id: port
+ multiline: False
+ input_type: 'number'
+ height: '36dp'
+ size_hint_x: 3
+ size_hint_y: None
+ text: app.server_port
+ Widget
+ Button:
+ id: chooser
+ text: _('Choose from peers')
+ height: '36dp'
+ size_hint_x: 0.5
+ size_hint_y: None
+ on_release:
+ app.choose_server_dialog(root)
+ Widget:
+ size_hint: 1, 0.1
+ BoxLayout:
+ Widget:
+ size_hint: 0.5, None
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('OK')
+ on_release:
+ host, port, protocol, proxy, auto_connect = app.network.get_parameters()
+ host = str(root.ids.host.text)
+ port = str(root.ids.port.text)
+ app.network.set_parameters(host, port, protocol, proxy, auto_connect)
+ nd.dismiss()
diff --git a/electrum/gui/kivy/uix/ui_screens/status.kv b/electrum/gui/kivy/uix/ui_screens/status.kv
new file mode 100644
index 000000000..67b21bf62
--- /dev/null
+++ b/electrum/gui/kivy/uix/ui_screens/status.kv
@@ -0,0 +1,84 @@
+Popup:
+ title: "Electrum"
+ confirmed: 0
+ unconfirmed: 0
+ unmatured: 0
+ watching_only: app.wallet.is_watching_only()
+ has_seed: app.wallet.has_seed()
+ on_parent:
+ self.confirmed, self.unconfirmed, self.unmatured = app.wallet.get_balance()
+ BoxLayout:
+ orientation: 'vertical'
+ ScrollView:
+ GridLayout:
+ cols: 1
+ height: self.minimum_height
+ size_hint_y: None
+ padding: '10dp'
+ spacing: '10dp'
+ padding: '10dp'
+ spacing: '10dp'
+ GridLayout:
+ cols: 1
+ size_hint_y: None
+ height: self.minimum_height
+ spacing: '10dp'
+ BoxLabel:
+ text: _('Wallet Name')
+ value: app.wallet_name()
+ BoxLabel:
+ text: _("Wallet type:")
+ value: app.wallet.wallet_type
+ BoxLabel:
+ text: _("Balance") + ':'
+ value: app.format_amount_and_units(root.confirmed + root.unconfirmed + root.unmatured)
+ BoxLabel:
+ text: _("Confirmed") + ':'
+ opacity: 1 if root.confirmed else 0
+ value: app.format_amount_and_units(root.confirmed)
+ opacity: 1 if root.confirmed else 0
+ BoxLabel:
+ text: _("Unconfirmed") + ':'
+ opacity: 1 if root.unconfirmed else 0
+ value: app.format_amount_and_units(root.unconfirmed)
+ BoxLabel:
+ text: _("Unmatured") + ':'
+ opacity: 1 if root.unmatured else 0
+ value: app.format_amount_and_units(root.unmatured)
+ opacity: 1 if root.unmatured else 0
+
+ GridLayout:
+ cols: 1
+ height: self.minimum_height
+ size_hint_y: None
+ padding: '10dp'
+ spacing: '10dp'
+ id: master_public_keys
+ TopLabel:
+ text: _('Master Public Key')
+ RefLabel:
+ data: app.wallet.get_master_public_key() or 'None'
+ name: _('Master Public Key')
+
+
+ TopLabel:
+ id: seed_label
+ text: _('This wallet is watching-only') if root.watching_only else ''
+
+ BoxLayout:
+ size_hint: 1, None
+ height: '48dp'
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: '' if not root.has_seed else (_('Hide seed') if seed_label.text else _('Show seed'))
+ disabled: not root.has_seed
+ on_release:
+ setattr(seed_label, 'text', '') if seed_label.text else app.show_seed(seed_label)
+ Button:
+ size_hint: 0.5, None
+ height: '48dp'
+ text: _('Delete')
+ on_release:
+ root.dismiss()
+ app.delete_wallet()
diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py
new file mode 100644
index 000000000..b49826c66
--- /dev/null
+++ b/electrum/gui/qt/__init__.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import signal
+import sys
+import traceback
+
+
+try:
+ import PyQt5
+except Exception:
+ sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
+
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+import PyQt5.QtCore as QtCore
+
+from electrum.i18n import _, set_language
+from electrum.plugin import run_hook
+from electrum.storage import WalletStorage
+from electrum.base_wizard import GoBack
+# from electrum.synchronizer import Synchronizer
+# from electrum.verifier import SPV
+# from electrum.util import DebugMem
+from electrum.util import (UserCancelled, print_error,
+ WalletFileException, BitcoinException)
+# from electrum.wallet import Abstract_Wallet
+
+from .installwizard import InstallWizard
+
+
+try:
+ from . import icons_rc
+except Exception as e:
+ print(e)
+ print("Error: Could not find icons file.")
+ print("Please run 'pyrcc5 icons.qrc -o electrum/gui/qt/icons_rc.py'")
+ sys.exit(1)
+
+from .util import * # * needed for plugins
+from .main_window import ElectrumWindow
+from .network_dialog import NetworkDialog
+
+
+class OpenFileEventFilter(QObject):
+ def __init__(self, windows):
+ self.windows = windows
+ super(OpenFileEventFilter, self).__init__()
+
+ def eventFilter(self, obj, event):
+ if event.type() == QtCore.QEvent.FileOpen:
+ if len(self.windows) >= 1:
+ self.windows[0].pay_to_URI(event.url().toEncoded())
+ return True
+ return False
+
+
+class QElectrumApplication(QApplication):
+ new_window_signal = pyqtSignal(str, object)
+
+
+class QNetworkUpdatedSignalObject(QObject):
+ network_updated_signal = pyqtSignal(str, object)
+
+
+class ElectrumGui:
+
+ def __init__(self, config, daemon, plugins):
+ set_language(config.get('language'))
+ # Uncomment this call to verify objects are being properly
+ # GC-ed when windows are closed
+ #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
+ # ElectrumWindow], interval=5)])
+ QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
+ if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
+ QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
+ if hasattr(QGuiApplication, 'setDesktopFileName'):
+ QGuiApplication.setDesktopFileName('electrum.desktop')
+ self.config = config
+ self.daemon = daemon
+ self.plugins = plugins
+ self.windows = []
+ self.efilter = OpenFileEventFilter(self.windows)
+ self.app = QElectrumApplication(sys.argv)
+ self.app.installEventFilter(self.efilter)
+ self.timer = Timer()
+ self.nd = None
+ self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
+ # init tray
+ self.dark_icon = self.config.get("dark_icon", False)
+ self.tray = QSystemTrayIcon(self.tray_icon(), None)
+ self.tray.setToolTip('Electrum')
+ self.tray.activated.connect(self.tray_activated)
+ self.build_tray_menu()
+ self.tray.show()
+ self.app.new_window_signal.connect(self.start_new_window)
+ self.set_dark_theme_if_needed()
+ run_hook('init_qt', self)
+
+ def set_dark_theme_if_needed(self):
+ use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
+ if use_dark_theme:
+ try:
+ import qdarkstyle
+ self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
+ except BaseException as e:
+ use_dark_theme = False
+ print_error('Error setting dark theme: {}'.format(e))
+ # Even if we ourselves don't set the dark theme,
+ # the OS/window manager/etc might set *a dark theme*.
+ # Hence, try to choose colors accordingly:
+ ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)
+
+ def build_tray_menu(self):
+ # Avoid immediate GC of old menu when window closed via its action
+ if self.tray.contextMenu() is None:
+ m = QMenu()
+ self.tray.setContextMenu(m)
+ else:
+ m = self.tray.contextMenu()
+ m.clear()
+ for window in self.windows:
+ submenu = m.addMenu(window.wallet.basename())
+ submenu.addAction(_("Show/Hide"), window.show_or_hide)
+ submenu.addAction(_("Close"), window.close)
+ m.addAction(_("Dark/Light"), self.toggle_tray_icon)
+ m.addSeparator()
+ m.addAction(_("Exit Electrum"), self.close)
+
+ def tray_icon(self):
+ if self.dark_icon:
+ return QIcon(':icons/electrum_dark_icon.png')
+ else:
+ return QIcon(':icons/electrum_light_icon.png')
+
+ def toggle_tray_icon(self):
+ self.dark_icon = not self.dark_icon
+ self.config.set_key("dark_icon", self.dark_icon, True)
+ self.tray.setIcon(self.tray_icon())
+
+ def tray_activated(self, reason):
+ if reason == QSystemTrayIcon.DoubleClick:
+ if all([w.is_hidden() for w in self.windows]):
+ for w in self.windows:
+ w.bring_to_top()
+ else:
+ for w in self.windows:
+ w.hide()
+
+ def close(self):
+ for window in self.windows:
+ window.close()
+
+ def new_window(self, path, uri=None):
+ # Use a signal as can be called from daemon thread
+ self.app.new_window_signal.emit(path, uri)
+
+ def show_network_dialog(self, parent):
+ if not self.daemon.network:
+ parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
+ return
+ if self.nd:
+ self.nd.on_update()
+ self.nd.show()
+ self.nd.raise_()
+ return
+ self.nd = NetworkDialog(self.daemon.network, self.config,
+ self.network_updated_signal_obj)
+ self.nd.show()
+
+ def create_window_for_wallet(self, wallet):
+ w = ElectrumWindow(self, wallet)
+ self.windows.append(w)
+ self.build_tray_menu()
+ # FIXME: Remove in favour of the load_wallet hook
+ run_hook('on_new_window', w)
+ return w
+
+ def start_new_window(self, path, uri, app_is_starting=False):
+ '''Raises the window for the wallet if it is open. Otherwise
+ opens the wallet and creates a new window for it'''
+ try:
+ wallet = self.daemon.load_wallet(path, None)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ d = QMessageBox(QMessageBox.Warning, _('Error'),
+ _('Cannot load wallet') + ' (1):\n' + str(e))
+ d.exec_()
+ if app_is_starting:
+ # do not return so that the wizard can appear
+ wallet = None
+ else:
+ return
+ if not wallet:
+ storage = WalletStorage(path, manual_upgrades=True)
+ wizard = InstallWizard(self.config, self.app, self.plugins, storage)
+ try:
+ wallet = wizard.run_and_get_wallet(self.daemon.get_wallet)
+ except UserCancelled:
+ pass
+ except GoBack as e:
+ print_error('[start_new_window] Exception caught (GoBack)', e)
+ except (WalletFileException, BitcoinException) as e:
+ traceback.print_exc(file=sys.stderr)
+ d = QMessageBox(QMessageBox.Warning, _('Error'),
+ _('Cannot load wallet') + ' (2):\n' + str(e))
+ d.exec_()
+ return
+ finally:
+ wizard.terminate()
+ if not wallet:
+ return
+
+ if not self.daemon.get_wallet(wallet.storage.path):
+ # wallet was not in memory
+ wallet.start_threads(self.daemon.network)
+ self.daemon.add_wallet(wallet)
+ try:
+ for w in self.windows:
+ if w.wallet.storage.path == wallet.storage.path:
+ w.bring_to_top()
+ return
+ w = self.create_window_for_wallet(wallet)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ d = QMessageBox(QMessageBox.Warning, _('Error'),
+ _('Cannot create window for wallet') + ':\n' + str(e))
+ d.exec_()
+ return
+ if uri:
+ w.pay_to_URI(uri)
+ w.bring_to_top()
+ w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
+
+ # this will activate the window
+ w.activateWindow()
+ return w
+
+ def close_window(self, window):
+ if window in self.windows:
+ self.windows.remove(window)
+ self.build_tray_menu()
+ # save wallet path of last open window
+ if not self.windows:
+ self.config.save_last_wallet(window.wallet)
+ run_hook('on_close_window', window)
+
+ def init_network(self):
+ # Show network dialog if config does not exist
+ if self.daemon.network:
+ if self.config.get('auto_connect') is None:
+ wizard = InstallWizard(self.config, self.app, self.plugins, None)
+ wizard.init_network(self.daemon.network)
+ wizard.terminate()
+
+ def main(self):
+ try:
+ self.init_network()
+ except UserCancelled:
+ return
+ except GoBack:
+ return
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ return
+ self.timer.start()
+ self.config.open_last_wallet()
+ path = self.config.get_wallet_path()
+ if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
+ return
+ signal.signal(signal.SIGINT, lambda *args: self.app.quit())
+
+ def quit_after_last_window():
+ # on some platforms, not only does exec_ not return but not even
+ # aboutToQuit is emitted (but following this, it should be emitted)
+ if self.app.quitOnLastWindowClosed():
+ self.app.quit()
+ self.app.lastWindowClosed.connect(quit_after_last_window)
+
+ def clean_up():
+ # Shut down the timer cleanly
+ self.timer.stop()
+ # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
+ event = QtCore.QEvent(QtCore.QEvent.Clipboard)
+ self.app.sendEvent(self.app.clipboard(), event)
+ self.tray.hide()
+ self.app.aboutToQuit.connect(clean_up)
+
+ # main loop
+ self.app.exec_()
+ # on some platforms the exec_ call may not return, so use clean_up()
diff --git a/electrum/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py
new file mode 100644
index 000000000..9afd50adc
--- /dev/null
+++ b/electrum/gui/qt/address_dialog.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.i18n import _
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+
+from .util import *
+from .history_list import HistoryList
+from .qrtextedit import ShowQRTextEdit
+
+
+class AddressDialog(WindowModalDialog):
+
+ def __init__(self, parent, address):
+ WindowModalDialog.__init__(self, parent, _("Address"))
+ self.address = address
+ self.parent = parent
+ self.config = parent.config
+ self.wallet = parent.wallet
+ self.app = parent.app
+ self.saved = True
+
+ self.setMinimumWidth(700)
+ vbox = QVBoxLayout()
+ self.setLayout(vbox)
+
+ vbox.addWidget(QLabel(_("Address:")))
+ self.addr_e = ButtonsLineEdit(self.address)
+ self.addr_e.addCopyButton(self.app)
+ icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
+ self.addr_e.addButton(icon, self.show_qr, _("Show QR Code"))
+ self.addr_e.setReadOnly(True)
+ vbox.addWidget(self.addr_e)
+
+ try:
+ pubkeys = self.wallet.get_public_keys(address)
+ except BaseException as e:
+ pubkeys = None
+ if pubkeys:
+ vbox.addWidget(QLabel(_("Public keys") + ':'))
+ for pubkey in pubkeys:
+ pubkey_e = ButtonsLineEdit(pubkey)
+ pubkey_e.addCopyButton(self.app)
+ vbox.addWidget(pubkey_e)
+
+ try:
+ redeem_script = self.wallet.pubkeys_to_redeem_script(pubkeys)
+ except BaseException as e:
+ redeem_script = None
+ if redeem_script:
+ vbox.addWidget(QLabel(_("Redeem Script") + ':'))
+ redeem_e = ShowQRTextEdit(text=redeem_script)
+ redeem_e.addCopyButton(self.app)
+ vbox.addWidget(redeem_e)
+
+ vbox.addWidget(QLabel(_("History")))
+ self.hw = HistoryList(self.parent)
+ self.hw.get_domain = self.get_domain
+ vbox.addWidget(self.hw)
+
+ vbox.addLayout(Buttons(CloseButton(self)))
+ self.format_amount = self.parent.format_amount
+ self.hw.update()
+
+ def get_domain(self):
+ return [self.address]
+
+ def show_qr(self):
+ text = self.address
+ try:
+ self.parent.show_qrcode(text, 'Address', parent=self)
+ except Exception as e:
+ self.show_message(str(e))
diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py
new file mode 100644
index 000000000..fce1004c2
--- /dev/null
+++ b/electrum/gui/qt/address_list.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import webbrowser
+
+from electrum.i18n import _
+from electrum.util import block_explorer_URL
+from electrum.plugin import run_hook
+from electrum.bitcoin import is_address
+
+from .util import *
+
+
+class AddressList(MyTreeWidget):
+ filter_columns = [0, 1, 2, 3] # Type, Address, Label, Balance
+
+ def __init__(self, parent=None):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [], 2)
+ self.refresh_headers()
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setSortingEnabled(True)
+ self.show_change = 0
+ self.show_used = 0
+ self.change_button = QComboBox(self)
+ self.change_button.currentIndexChanged.connect(self.toggle_change)
+ for t in [_('All'), _('Receiving'), _('Change')]:
+ self.change_button.addItem(t)
+ self.used_button = QComboBox(self)
+ self.used_button.currentIndexChanged.connect(self.toggle_used)
+ for t in [_('All'), _('Unused'), _('Funded'), _('Used')]:
+ self.used_button.addItem(t)
+
+ def get_toolbar_buttons(self):
+ return QLabel(_("Filter:")), self.change_button, self.used_button
+
+ def on_hide_toolbar(self):
+ self.show_change = 0
+ self.show_used = 0
+ self.update()
+
+ def save_toolbar_state(self, state, config):
+ config.set_key('show_toolbar_addresses', state)
+
+ def refresh_headers(self):
+ headers = [_('Type'), _('Address'), _('Label'), _('Balance')]
+ fx = self.parent.fx
+ if fx and fx.get_fiat_address_config():
+ headers.extend([_(fx.get_currency()+' Balance')])
+ headers.extend([_('Tx')])
+ self.update_headers(headers)
+
+ def toggle_change(self, state):
+ if state == self.show_change:
+ return
+ self.show_change = state
+ self.update()
+
+ def toggle_used(self, state):
+ if state == self.show_used:
+ return
+ self.show_used = state
+ self.update()
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ item = self.currentItem()
+ current_address = item.data(0, Qt.UserRole) if item else None
+ if self.show_change == 1:
+ addr_list = self.wallet.get_receiving_addresses()
+ elif self.show_change == 2:
+ addr_list = self.wallet.get_change_addresses()
+ else:
+ addr_list = self.wallet.get_addresses()
+ self.clear()
+ fx = self.parent.fx
+ for address in addr_list:
+ num = self.wallet.get_address_history_len(address)
+ label = self.wallet.labels.get(address, '')
+ c, u, x = self.wallet.get_addr_balance(address)
+ balance = c + u + x
+ is_used_and_empty = self.wallet.is_used(address) and balance == 0
+ if self.show_used == 1 and (balance or is_used_and_empty):
+ continue
+ if self.show_used == 2 and balance == 0:
+ continue
+ if self.show_used == 3 and not is_used_and_empty:
+ continue
+ balance_text = self.parent.format_amount(balance, whitespaces=True)
+ # create item
+ if fx and fx.get_fiat_address_config():
+ rate = fx.exchange_rate()
+ fiat_balance = fx.value_str(balance, rate)
+ address_item = SortableTreeWidgetItem(['', address, label, balance_text, fiat_balance, "%d"%num])
+ else:
+ address_item = SortableTreeWidgetItem(['', address, label, balance_text, "%d"%num])
+ # align text and set fonts
+ for i in range(address_item.columnCount()):
+ address_item.setTextAlignment(i, Qt.AlignVCenter)
+ if i not in (0, 2):
+ address_item.setFont(i, QFont(MONOSPACE_FONT))
+ if fx and fx.get_fiat_address_config():
+ address_item.setTextAlignment(4, Qt.AlignRight | Qt.AlignVCenter)
+ # setup column 0
+ if self.wallet.is_change(address):
+ address_item.setText(0, _('change'))
+ address_item.setBackground(0, ColorScheme.YELLOW.as_color(True))
+ else:
+ address_item.setText(0, _('receiving'))
+ address_item.setBackground(0, ColorScheme.GREEN.as_color(True))
+ address_item.setData(0, Qt.UserRole, address) # column 0; independent from address column
+ # setup column 1
+ if self.wallet.is_frozen(address):
+ address_item.setBackground(1, ColorScheme.BLUE.as_color(True))
+ if self.wallet.is_beyond_limit(address):
+ address_item.setBackground(1, ColorScheme.RED.as_color(True))
+ # add item
+ self.addChild(address_item)
+ if address == current_address:
+ self.setCurrentItem(address_item)
+
+ def create_menu(self, position):
+ from electrum.wallet import Multisig_Wallet
+ is_multisig = isinstance(self.wallet, Multisig_Wallet)
+ can_delete = self.wallet.can_delete_address()
+ selected = self.selectedItems()
+ multi_select = len(selected) > 1
+ addrs = [item.text(1) for item in selected]
+ if not addrs:
+ return
+ if not multi_select:
+ item = self.itemAt(position)
+ col = self.currentColumn()
+ if not item:
+ return
+ addr = addrs[0]
+ if not is_address(addr):
+ item.setExpanded(not item.isExpanded())
+ return
+
+ menu = QMenu()
+ if not multi_select:
+ column_title = self.headerItem().text(col)
+ copy_text = item.text(col)
+ menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(copy_text))
+ menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
+ if col in self.editable_columns:
+ menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, col))
+ menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
+ if self.wallet.can_export():
+ menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
+ if not is_multisig and not self.wallet.is_watching_only():
+ menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
+ menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
+ if can_delete:
+ menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
+ addr_URL = block_explorer_URL(self.config, 'addr', addr)
+ if addr_URL:
+ menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))
+
+ if not self.wallet.is_frozen(addr):
+ menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state([addr], True))
+ else:
+ menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state([addr], False))
+
+ coins = self.wallet.get_utxos(addrs)
+ if coins:
+ menu.addAction(_("Spend from"), lambda: self.parent.spend_coins(coins))
+
+ run_hook('receive_menu', menu, addrs, self.wallet)
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def on_permit_edit(self, item, column):
+ # labels for headings, e.g. "receiving" or "used" should not be editable
+ return item.childCount() == 0
diff --git a/electrum/gui/qt/amountedit.py b/electrum/gui/qt/amountedit.py
new file mode 100644
index 000000000..7d802a4b6
--- /dev/null
+++ b/electrum/gui/qt/amountedit.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+from decimal import Decimal
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame)
+
+from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
+ FEERATE_PRECISION, quantize_feerate)
+
+
+class MyLineEdit(QLineEdit):
+ frozen = pyqtSignal()
+
+ def setFrozen(self, b):
+ self.setReadOnly(b)
+ self.setFrame(not b)
+ self.frozen.emit()
+
+class AmountEdit(MyLineEdit):
+ shortcut = pyqtSignal()
+
+ def __init__(self, base_unit, is_int=False, parent=None):
+ QLineEdit.__init__(self, parent)
+ # This seems sufficient for hundred-BTC amounts with 8 decimals
+ self.setFixedWidth(140)
+ self.base_unit = base_unit
+ self.textChanged.connect(self.numbify)
+ self.is_int = is_int
+ self.is_shortcut = False
+ self.help_palette = QPalette()
+ self.extra_precision = 0
+
+ def decimal_point(self):
+ return 8
+
+ def max_precision(self):
+ return self.decimal_point() + self.extra_precision
+
+ def numbify(self):
+ text = self.text().strip()
+ if text == '!':
+ self.shortcut.emit()
+ return
+ pos = self.cursorPosition()
+ chars = '0123456789'
+ if not self.is_int: chars +='.'
+ s = ''.join([i for i in text if i in chars])
+ if not self.is_int:
+ if '.' in s:
+ p = s.find('.')
+ s = s.replace('.','')
+ s = s[:p] + '.' + s[p:p+self.max_precision()]
+ self.setText(s)
+ # setText sets Modified to False. Instead we want to remember
+ # if updates were because of user modification.
+ self.setModified(self.hasFocus())
+ self.setCursorPosition(pos)
+
+ def paintEvent(self, event):
+ QLineEdit.paintEvent(self, event)
+ if self.base_unit:
+ panel = QStyleOptionFrame()
+ self.initStyleOption(panel)
+ textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)
+ textRect.adjust(2, 0, -10, 0)
+ painter = QPainter(self)
+ painter.setPen(self.help_palette.brush(QPalette.Disabled, QPalette.Text).color())
+ painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, self.base_unit())
+
+ def get_amount(self):
+ try:
+ return (int if self.is_int else Decimal)(str(self.text()))
+ except:
+ return None
+
+ def setAmount(self, x):
+ self.setText("%d"%x)
+
+
+class BTCAmountEdit(AmountEdit):
+
+ def __init__(self, decimal_point, is_int=False, parent=None):
+ AmountEdit.__init__(self, self._base_unit, is_int, parent)
+ self.decimal_point = decimal_point
+
+ def _base_unit(self):
+ return decimal_point_to_base_unit_name(self.decimal_point())
+
+ def get_amount(self):
+ try:
+ x = Decimal(str(self.text()))
+ except:
+ return None
+ # scale it to max allowed precision, make it an int
+ power = pow(10, self.max_precision())
+ max_prec_amount = int(power * x)
+ # if the max precision is simply what unit conversion allows, just return
+ if self.max_precision() == self.decimal_point():
+ return max_prec_amount
+ # otherwise, scale it back to the expected unit
+ amount = Decimal(max_prec_amount) / pow(10, self.max_precision()-self.decimal_point())
+ return Decimal(amount) if not self.is_int else int(amount)
+
+ def setAmount(self, amount):
+ if amount is None:
+ self.setText(" ") # Space forces repaint in case units changed
+ else:
+ self.setText(format_satoshis_plain(amount, self.decimal_point()))
+
+
+class FeerateEdit(BTCAmountEdit):
+
+ def __init__(self, decimal_point, is_int=False, parent=None):
+ super().__init__(decimal_point, is_int, parent)
+ self.extra_precision = FEERATE_PRECISION
+
+ def _base_unit(self):
+ return 'sat/byte'
+
+ def get_amount(self):
+ sat_per_byte_amount = BTCAmountEdit.get_amount(self)
+ return quantize_feerate(sat_per_byte_amount)
+
+ def setAmount(self, amount):
+ amount = quantize_feerate(amount)
+ super().setAmount(amount)
diff --git a/electrum/gui/qt/completion_text_edit.py b/electrum/gui/qt/completion_text_edit.py
new file mode 100644
index 000000000..4709b03d8
--- /dev/null
+++ b/electrum/gui/qt/completion_text_edit.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+from .util import ButtonsTextEdit
+
+class CompletionTextEdit(ButtonsTextEdit):
+
+ def __init__(self, parent=None):
+ super(CompletionTextEdit, self).__init__(parent)
+ self.completer = None
+ self.moveCursor(QTextCursor.End)
+ self.disable_suggestions()
+
+ def set_completer(self, completer):
+ self.completer = completer
+ self.initialize_completer()
+
+ def initialize_completer(self):
+ self.completer.setWidget(self)
+ self.completer.setCompletionMode(QCompleter.PopupCompletion)
+ self.completer.activated.connect(self.insert_completion)
+ self.enable_suggestions()
+
+ def insert_completion(self, completion):
+ if self.completer.widget() != self:
+ return
+ text_cursor = self.textCursor()
+ extra = len(completion) - len(self.completer.completionPrefix())
+ text_cursor.movePosition(QTextCursor.Left)
+ text_cursor.movePosition(QTextCursor.EndOfWord)
+ if extra == 0:
+ text_cursor.insertText(" ")
+ else:
+ text_cursor.insertText(completion[-extra:] + " ")
+ self.setTextCursor(text_cursor)
+
+ def text_under_cursor(self):
+ tc = self.textCursor()
+ tc.select(QTextCursor.WordUnderCursor)
+ return tc.selectedText()
+
+ def enable_suggestions(self):
+ self.suggestions_enabled = True
+
+ def disable_suggestions(self):
+ self.suggestions_enabled = False
+
+ def keyPressEvent(self, e):
+ if self.isReadOnly():
+ return
+
+ if self.is_special_key(e):
+ e.ignore()
+ return
+
+ QPlainTextEdit.keyPressEvent(self, e)
+
+ ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
+ if self.completer is None or (ctrlOrShift and not e.text()):
+ return
+
+ if not self.suggestions_enabled:
+ return
+
+ eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
+ hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
+ completionPrefix = self.text_under_cursor()
+
+ if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0:
+ self.completer.popup().hide()
+ return
+
+ if completionPrefix != self.completer.completionPrefix():
+ self.completer.setCompletionPrefix(completionPrefix)
+ self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0))
+
+ cr = self.cursorRect()
+ cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width())
+ self.completer.complete(cr)
+
+ def is_special_key(self, e):
+ if self.completer != None and self.completer.popup().isVisible():
+ if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
+ return True
+ if e.key() in [Qt.Key_Tab, Qt.Key_Down, Qt.Key_Up]:
+ return True
+ return False
+
+if __name__ == "__main__":
+ app = QApplication([])
+ completer = QCompleter(["alabama", "arkansas", "avocado", "breakfast", "sausage"])
+ te = CompletionTextEdit()
+ te.set_completer(completer)
+ te.show()
+ app.exec_()
diff --git a/electrum/gui/qt/console.py b/electrum/gui/qt/console.py
new file mode 100644
index 000000000..d0b10a30d
--- /dev/null
+++ b/electrum/gui/qt/console.py
@@ -0,0 +1,359 @@
+
+# source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget
+
+import sys, os, re
+import traceback, platform
+from PyQt5 import QtCore
+from PyQt5 import QtGui
+from PyQt5 import QtWidgets
+from electrum import util
+from electrum.i18n import _
+
+
+if platform.system() == 'Windows':
+ MONOSPACE_FONT = 'Lucida Console'
+elif platform.system() == 'Darwin':
+ MONOSPACE_FONT = 'Monaco'
+else:
+ MONOSPACE_FONT = 'monospace'
+
+
+class OverlayLabel(QtWidgets.QLabel):
+ STYLESHEET = '''
+ QLabel, QLabel link {
+ color: rgb(0, 0, 0);
+ background-color: rgb(248, 240, 200);
+ border: 1px solid;
+ border-color: rgb(255, 114, 47);
+ padding: 2px;
+ }
+ '''
+ def __init__(self, text, parent):
+ super().__init__(text, parent)
+ self.setMinimumHeight(150)
+ self.setGeometry(0, 0, self.width(), self.height())
+ self.setStyleSheet(self.STYLESHEET)
+ self.setMargin(0)
+ parent.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setWordWrap(True)
+
+ def mousePressEvent(self, e):
+ self.hide()
+
+ def on_resize(self, w):
+ padding = 2 # px, from the stylesheet above
+ self.setFixedWidth(w - padding)
+
+
+class Console(QtWidgets.QPlainTextEdit):
+ def __init__(self, prompt='>> ', startup_message='', parent=None):
+ QtWidgets.QPlainTextEdit.__init__(self, parent)
+
+ self.prompt = prompt
+ self.history = []
+ self.namespace = {}
+ self.construct = []
+
+ self.setGeometry(50, 75, 600, 400)
+ self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere)
+ self.setUndoRedoEnabled(False)
+ self.document().setDefaultFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal))
+ self.showMessage(startup_message)
+
+ self.updateNamespace({'run':self.run_script})
+ self.set_json(False)
+
+ warning_text = "{} {} {}".format(
+ _("Warning!"),
+ _("Do not paste code here that you don't understand. Executing the wrong code could lead "
+ "to your coins being irreversibly lost."),
+ _("Click here to hide this message.")
+ )
+ self.messageOverlay = OverlayLabel(warning_text, self)
+
+ def resizeEvent(self, e):
+ self.messageOverlay.on_resize(self.width() - self.verticalScrollBar().width())
+
+
+ def set_json(self, b):
+ self.is_json = b
+
+ def run_script(self, filename):
+ with open(filename) as f:
+ script = f.read()
+
+ # eval is generally considered bad practice. use it wisely!
+ result = eval(script, self.namespace, self.namespace)
+
+
+
+ def updateNamespace(self, namespace):
+ self.namespace.update(namespace)
+
+ def showMessage(self, message):
+ self.appendPlainText(message)
+ self.newPrompt()
+
+ def clear(self):
+ self.setPlainText('')
+ self.newPrompt()
+
+ def newPrompt(self):
+ if self.construct:
+ prompt = '.' * len(self.prompt)
+ else:
+ prompt = self.prompt
+
+ self.completions_pos = self.textCursor().position()
+ self.completions_visible = False
+
+ self.appendPlainText(prompt)
+ self.moveCursor(QtGui.QTextCursor.End)
+
+ def getCommand(self):
+ doc = self.document()
+ curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()
+ curr_line = curr_line.rstrip()
+ curr_line = curr_line[len(self.prompt):]
+ return curr_line
+
+ def setCommand(self, command):
+ if self.getCommand() == command:
+ return
+
+ doc = self.document()
+ curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()
+ self.moveCursor(QtGui.QTextCursor.End)
+ for i in range(len(curr_line) - len(self.prompt)):
+ self.moveCursor(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor)
+
+ self.textCursor().removeSelectedText()
+ self.textCursor().insertText(command)
+ self.moveCursor(QtGui.QTextCursor.End)
+
+ def show_completions(self, completions):
+ if self.completions_visible:
+ self.hide_completions()
+
+ c = self.textCursor()
+ c.setPosition(self.completions_pos)
+
+ completions = map(lambda x: x.split('.')[-1], completions)
+ t = '\n' + ' '.join(completions)
+ if len(t) > 500:
+ t = t[:500] + '...'
+ c.insertText(t)
+ self.completions_end = c.position()
+
+ self.moveCursor(QtGui.QTextCursor.End)
+ self.completions_visible = True
+
+ def hide_completions(self):
+ if not self.completions_visible:
+ return
+ c = self.textCursor()
+ c.setPosition(self.completions_pos)
+ l = self.completions_end - self.completions_pos
+ for x in range(l): c.deleteChar()
+
+ self.moveCursor(QtGui.QTextCursor.End)
+ self.completions_visible = False
+
+ def getConstruct(self, command):
+ if self.construct:
+ prev_command = self.construct[-1]
+ self.construct.append(command)
+ if not prev_command and not command:
+ ret_val = '\n'.join(self.construct)
+ self.construct = []
+ return ret_val
+ else:
+ return ''
+ else:
+ if command and command[-1] == (':'):
+ self.construct.append(command)
+ return ''
+ else:
+ return command
+
+ def getHistory(self):
+ return self.history
+
+ def setHisory(self, history):
+ self.history = history
+
+ def addToHistory(self, command):
+ if command[0:1] == ' ':
+ return
+
+ if command and (not self.history or self.history[-1] != command):
+ self.history.append(command)
+ self.history_index = len(self.history)
+
+ def getPrevHistoryEntry(self):
+ if self.history:
+ self.history_index = max(0, self.history_index - 1)
+ return self.history[self.history_index]
+ return ''
+
+ def getNextHistoryEntry(self):
+ if self.history:
+ hist_len = len(self.history)
+ self.history_index = min(hist_len, self.history_index + 1)
+ if self.history_index < hist_len:
+ return self.history[self.history_index]
+ return ''
+
+ def getCursorPosition(self):
+ c = self.textCursor()
+ return c.position() - c.block().position() - len(self.prompt)
+
+ def setCursorPosition(self, position):
+ self.moveCursor(QtGui.QTextCursor.StartOfLine)
+ for i in range(len(self.prompt) + position):
+ self.moveCursor(QtGui.QTextCursor.Right)
+
+ def register_command(self, c, func):
+ methods = { c: func}
+ self.updateNamespace(methods)
+
+
+ def runCommand(self):
+ command = self.getCommand()
+ self.addToHistory(command)
+
+ command = self.getConstruct(command)
+
+ if command:
+ tmp_stdout = sys.stdout
+
+ class stdoutProxy():
+ def __init__(self, write_func):
+ self.write_func = write_func
+ self.skip = False
+
+ def flush(self):
+ pass
+
+ def write(self, text):
+ if not self.skip:
+ stripped_text = text.rstrip('\n')
+ self.write_func(stripped_text)
+ QtCore.QCoreApplication.processEvents()
+ self.skip = not self.skip
+
+ if type(self.namespace.get(command)) == type(lambda:None):
+ self.appendPlainText("'{}' is a function. Type '{}()' to use it in the Python console."
+ .format(command, command))
+ self.newPrompt()
+ return
+
+ sys.stdout = stdoutProxy(self.appendPlainText)
+ try:
+ try:
+ # eval is generally considered bad practice. use it wisely!
+ result = eval(command, self.namespace, self.namespace)
+ if result != None:
+ if self.is_json:
+ util.print_msg(util.json_encode(result))
+ else:
+ self.appendPlainText(repr(result))
+ except SyntaxError:
+ # exec is generally considered bad practice. use it wisely!
+ exec(command, self.namespace, self.namespace)
+ except SystemExit:
+ self.close()
+ except BaseException:
+ traceback_lines = traceback.format_exc().split('\n')
+ # Remove traceback mentioning this file, and a linebreak
+ for i in (3,2,1,-1):
+ traceback_lines.pop(i)
+ self.appendPlainText('\n'.join(traceback_lines))
+ sys.stdout = tmp_stdout
+ self.newPrompt()
+ self.set_json(False)
+
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Tab:
+ self.completions()
+ return
+
+ self.hide_completions()
+
+ if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
+ self.runCommand()
+ return
+ if event.key() == QtCore.Qt.Key_Home:
+ self.setCursorPosition(0)
+ return
+ if event.key() == QtCore.Qt.Key_PageUp:
+ return
+ elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace):
+ if self.getCursorPosition() == 0:
+ return
+ elif event.key() == QtCore.Qt.Key_Up:
+ self.setCommand(self.getPrevHistoryEntry())
+ return
+ elif event.key() == QtCore.Qt.Key_Down:
+ self.setCommand(self.getNextHistoryEntry())
+ return
+ elif event.key() == QtCore.Qt.Key_L and event.modifiers() == QtCore.Qt.ControlModifier:
+ self.clear()
+
+ super(Console, self).keyPressEvent(event)
+
+
+
+ def completions(self):
+ cmd = self.getCommand()
+ lastword = re.split(' |\(|\)',cmd)[-1]
+ beginning = cmd[0:-len(lastword)]
+
+ path = lastword.split('.')
+ ns = self.namespace.keys()
+
+ if len(path) == 1:
+ ns = ns
+ prefix = ''
+ else:
+ obj = self.namespace.get(path[0])
+ prefix = path[0] + '.'
+ ns = dir(obj)
+
+
+ completions = []
+ for x in ns:
+ if x[0] == '_':continue
+ xx = prefix + x
+ if xx.startswith(lastword):
+ completions.append(xx)
+ completions.sort()
+
+ if not completions:
+ self.hide_completions()
+ elif len(completions) == 1:
+ self.hide_completions()
+ self.setCommand(beginning + completions[0])
+ else:
+ # find common prefix
+ p = os.path.commonprefix(completions)
+ if len(p)>len(lastword):
+ self.hide_completions()
+ self.setCommand(beginning + p)
+ else:
+ self.show_completions(completions)
+
+
+welcome_message = '''
+ ---------------------------------------------------------------
+ Welcome to a primitive Python interpreter.
+ ---------------------------------------------------------------
+'''
+
+if __name__ == '__main__':
+ app = QtWidgets.QApplication(sys.argv)
+ console = Console(startup_message=welcome_message)
+ console.updateNamespace({'myVar1' : app, 'myVar2' : 1234})
+ console.show()
+ sys.exit(app.exec_())
diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py
new file mode 100644
index 000000000..b13ee9ecb
--- /dev/null
+++ b/electrum/gui/qt/contact_list.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import webbrowser
+
+from electrum.i18n import _
+from electrum.bitcoin import is_address
+from electrum.util import block_explorer_URL
+from electrum.plugin import run_hook
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import (
+ QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)
+from .util import MyTreeWidget, import_meta_gui, export_meta_gui
+
+
+class ContactList(MyTreeWidget):
+ filter_columns = [0, 1] # Key, Value
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Name'), _('Address')], 0, [0])
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setSortingEnabled(True)
+
+ def on_permit_edit(self, item, column):
+ # openalias items shouldn't be editable
+ return item.text(1) != "openalias"
+
+ def on_edited(self, item, column, prior):
+ if column == 0: # Remove old contact if renamed
+ self.parent.contacts.pop(prior)
+ self.parent.set_contact(item.text(0), item.text(1))
+
+ def import_contacts(self):
+ import_meta_gui(self.parent, _('contacts'), self.parent.contacts.import_file, self.on_update)
+
+ def export_contacts(self):
+ export_meta_gui(self.parent, _('contacts'), self.parent.contacts.export_file)
+
+ def create_menu(self, position):
+ menu = QMenu()
+ selected = self.selectedItems()
+ if not selected:
+ menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
+ menu.addAction(_("Import file"), lambda: self.import_contacts())
+ menu.addAction(_("Export file"), lambda: self.export_contacts())
+ else:
+ names = [item.text(0) for item in selected]
+ keys = [item.text(1) for item in selected]
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = '\n'.join([item.text(column) for item in selected])
+ menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
+ if column in self.editable_columns:
+ item = self.currentItem()
+ menu.addAction(_("Edit {}").format(column_title), lambda: self.editItem(item, column))
+ menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(keys))
+ URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]
+ if URLs:
+ menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs))
+
+ run_hook('create_contact_menu', menu, selected)
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def on_update(self):
+ item = self.currentItem()
+ current_key = item.data(0, Qt.UserRole) if item else None
+ self.clear()
+ for key in sorted(self.parent.contacts.keys()):
+ _type, name = self.parent.contacts[key]
+ item = QTreeWidgetItem([name, key])
+ item.setData(0, Qt.UserRole, key)
+ self.addTopLevelItem(item)
+ if key == current_key:
+ self.setCurrentItem(item)
+ run_hook('update_contacts_tab', self)
diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py
new file mode 100644
index 000000000..1774a043f
--- /dev/null
+++ b/electrum/gui/qt/exception_window.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import platform
+import sys
+import traceback
+
+from PyQt5.QtCore import QObject
+import PyQt5.QtCore as QtCore
+from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import *
+
+from electrum.i18n import _
+from electrum.base_crash_reporter import BaseCrashReporter
+from .util import MessageBoxMixin
+
+
+class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
+ _active_window = None
+
+ def __init__(self, main_window, exctype, value, tb):
+ BaseCrashReporter.__init__(self, exctype, value, tb)
+ self.main_window = main_window
+ QWidget.__init__(self)
+ self.setWindowTitle('Electrum - ' + _('An Error Occurred'))
+ self.setMinimumSize(600, 300)
+
+ main_box = QVBoxLayout()
+
+ heading = QLabel('' + BaseCrashReporter.CRASH_TITLE + ' ')
+ main_box.addWidget(heading)
+ main_box.addWidget(QLabel(BaseCrashReporter.CRASH_MESSAGE))
+
+ main_box.addWidget(QLabel(BaseCrashReporter.REQUEST_HELP_MESSAGE))
+
+ collapse_info = QPushButton(_("Show report contents"))
+ collapse_info.clicked.connect(
+ lambda: self.msg_box(QMessageBox.NoIcon,
+ self, _("Report contents"), self.get_report_string()))
+
+ main_box.addWidget(collapse_info)
+
+ main_box.addWidget(QLabel(BaseCrashReporter.DESCRIBE_ERROR_MESSAGE))
+
+ self.description_textfield = QTextEdit()
+ self.description_textfield.setFixedHeight(50)
+ main_box.addWidget(self.description_textfield)
+
+ main_box.addWidget(QLabel(BaseCrashReporter.ASK_CONFIRM_SEND))
+
+ buttons = QHBoxLayout()
+
+ report_button = QPushButton(_('Send Bug Report'))
+ report_button.clicked.connect(self.send_report)
+ report_button.setIcon(QIcon(":icons/tab_send.png"))
+ buttons.addWidget(report_button)
+
+ never_button = QPushButton(_('Never'))
+ never_button.clicked.connect(self.show_never)
+ buttons.addWidget(never_button)
+
+ close_button = QPushButton(_('Not Now'))
+ close_button.clicked.connect(self.close)
+ buttons.addWidget(close_button)
+
+ main_box.addLayout(buttons)
+
+ self.setLayout(main_box)
+ self.show()
+
+ def send_report(self):
+ try:
+ response = BaseCrashReporter.send_report(self)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.main_window.show_critical(_('There was a problem with the automatic reporting:') + '\n' +
+ str(e) + '\n' +
+ _("Please report this issue manually."))
+ return
+ QMessageBox.about(self, _("Crash report"), response.text)
+ self.close()
+
+ def on_close(self):
+ Exception_Window._active_window = None
+ sys.__excepthook__(*self.exc_args)
+ self.close()
+
+ def show_never(self):
+ self.main_window.config.set_key(BaseCrashReporter.config_key, False)
+ self.close()
+
+ def closeEvent(self, event):
+ self.on_close()
+ event.accept()
+
+ def get_user_description(self):
+ return self.description_textfield.toPlainText()
+
+ def get_wallet_type(self):
+ return self.main_window.wallet.wallet_type
+
+ def get_os_version(self):
+ return platform.platform()
+
+
+def _show_window(*args):
+ if not Exception_Window._active_window:
+ Exception_Window._active_window = Exception_Window(*args)
+
+
+class Exception_Hook(QObject):
+ _report_exception = QtCore.pyqtSignal(object, object, object, object)
+
+ def __init__(self, main_window, *args, **kwargs):
+ super(Exception_Hook, self).__init__(*args, **kwargs)
+ if not main_window.config.get(BaseCrashReporter.config_key, default=True):
+ return
+ self.main_window = main_window
+ sys.excepthook = self.handler
+ self._report_exception.connect(_show_window)
+
+ def handler(self, *args):
+ self._report_exception.emit(self.main_window, *args)
diff --git a/electrum/gui/qt/fee_slider.py b/electrum/gui/qt/fee_slider.py
new file mode 100644
index 000000000..04911d878
--- /dev/null
+++ b/electrum/gui/qt/fee_slider.py
@@ -0,0 +1,78 @@
+from electrum.i18n import _
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import QSlider, QToolTip
+
+import threading
+
+class FeeSlider(QSlider):
+
+ def __init__(self, window, config, callback):
+ QSlider.__init__(self, Qt.Horizontal)
+ self.config = config
+ self.window = window
+ self.callback = callback
+ self.dyn = False
+ self.lock = threading.RLock()
+ self.update()
+ self.valueChanged.connect(self.moved)
+ self._active = True
+
+ def moved(self, pos):
+ with self.lock:
+ if self.dyn:
+ fee_rate = self.config.depth_to_fee(pos) if self.config.use_mempool_fees() else self.config.eta_to_fee(pos)
+ else:
+ fee_rate = self.config.static_fee(pos)
+ tooltip = self.get_tooltip(pos, fee_rate)
+ QToolTip.showText(QCursor.pos(), tooltip, self)
+ self.setToolTip(tooltip)
+ self.callback(self.dyn, pos, fee_rate)
+
+ def get_tooltip(self, pos, fee_rate):
+ mempool = self.config.use_mempool_fees()
+ target, estimate = self.config.get_fee_text(pos, self.dyn, mempool, fee_rate)
+ if self.dyn:
+ return _('Target') + ': ' + target + '\n' + _('Current rate') + ': ' + estimate
+ else:
+ return _('Fixed rate') + ': ' + target + '\n' + _('Estimate') + ': ' + estimate
+
+ def update(self):
+ with self.lock:
+ self.dyn = self.config.is_dynfee()
+ mempool = self.config.use_mempool_fees()
+ maxp, pos, fee_rate = self.config.get_fee_slider(self.dyn, mempool)
+ self.setRange(0, maxp)
+ self.setValue(pos)
+ tooltip = self.get_tooltip(pos, fee_rate)
+ self.setToolTip(tooltip)
+
+ def activate(self):
+ self._active = True
+ self.setStyleSheet('')
+
+ def deactivate(self):
+ self._active = False
+ # TODO it would be nice to find a platform-independent solution
+ # that makes the slider look as if it was disabled
+ self.setStyleSheet(
+ """
+ QSlider::groove:horizontal {
+ border: 1px solid #999999;
+ height: 8px;
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #B1B1B1);
+ margin: 2px 0;
+ }
+
+ QSlider::handle:horizontal {
+ background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);
+ border: 1px solid #5c5c5c;
+ width: 12px;
+ margin: -2px 0;
+ border-radius: 3px;
+ }
+ """
+ )
+
+ def is_active(self):
+ return self._active
diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py
new file mode 100644
index 000000000..79fe4d3ab
--- /dev/null
+++ b/electrum/gui/qt/history_list.py
@@ -0,0 +1,436 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import webbrowser
+import datetime
+
+from electrum.address_synchronizer import TX_HEIGHT_LOCAL
+from .util import *
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, profiler, print_error, TxMinedStatus
+
+try:
+ from electrum.plot import plot_history, NothingToPlotException
+except:
+ print_error("qt/history_list: could not import electrum.plot. This feature needs matplotlib to be installed.")
+ plot_history = None
+
+# note: this list needs to be kept in sync with another in kivy
+TX_ICONS = [
+ "unconfirmed.png",
+ "warning.png",
+ "unconfirmed.png",
+ "offline_tx.png",
+ "clock1.png",
+ "clock2.png",
+ "clock3.png",
+ "clock4.png",
+ "clock5.png",
+ "confirmed.png",
+]
+
+
+class HistoryList(MyTreeWidget, AcceptFileDragDrop):
+ filter_columns = [2, 3, 4] # Date, Description, Amount
+
+ def __init__(self, parent=None):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [], 3)
+ AcceptFileDragDrop.__init__(self, ".txn")
+ self.refresh_headers()
+ self.setColumnHidden(1, True)
+ self.setSortingEnabled(True)
+ self.sortByColumn(0, Qt.AscendingOrder)
+ self.start_timestamp = None
+ self.end_timestamp = None
+ self.years = []
+ self.create_toolbar_buttons()
+ self.wallet = None
+
+ def format_date(self, d):
+ return str(datetime.date(d.year, d.month, d.day)) if d else _('None')
+
+ def refresh_headers(self):
+ headers = ['', '', _('Date'), _('Description'), _('Amount'), _('Balance')]
+ fx = self.parent.fx
+ if fx and fx.show_history():
+ headers.extend(['%s '%fx.ccy + _('Value')])
+ self.editable_columns |= {6}
+ if fx.get_history_capital_gains_config():
+ headers.extend(['%s '%fx.ccy + _('Acquisition price')])
+ headers.extend(['%s '%fx.ccy + _('Capital Gains')])
+ else:
+ self.editable_columns -= {6}
+ self.update_headers(headers)
+
+ def get_domain(self):
+ '''Replaced in address_dialog.py'''
+ return self.wallet.get_addresses()
+
+ def on_combo(self, x):
+ s = self.period_combo.itemText(x)
+ x = s == _('Custom')
+ self.start_button.setEnabled(x)
+ self.end_button.setEnabled(x)
+ if s == _('All'):
+ self.start_timestamp = None
+ self.end_timestamp = None
+ self.start_button.setText("-")
+ self.end_button.setText("-")
+ else:
+ try:
+ year = int(s)
+ except:
+ return
+ start_date = datetime.datetime(year, 1, 1)
+ end_date = datetime.datetime(year+1, 1, 1)
+ self.start_timestamp = time.mktime(start_date.timetuple())
+ self.end_timestamp = time.mktime(end_date.timetuple())
+ self.start_button.setText(_('From') + ' ' + self.format_date(start_date))
+ self.end_button.setText(_('To') + ' ' + self.format_date(end_date))
+ self.update()
+
+ def create_toolbar_buttons(self):
+ self.period_combo = QComboBox()
+ self.start_button = QPushButton('-')
+ self.start_button.pressed.connect(self.select_start_date)
+ self.start_button.setEnabled(False)
+ self.end_button = QPushButton('-')
+ self.end_button.pressed.connect(self.select_end_date)
+ self.end_button.setEnabled(False)
+ self.period_combo.addItems([_('All'), _('Custom')])
+ self.period_combo.activated.connect(self.on_combo)
+
+ def get_toolbar_buttons(self):
+ return self.period_combo, self.start_button, self.end_button
+
+ def on_hide_toolbar(self):
+ self.start_timestamp = None
+ self.end_timestamp = None
+ self.update()
+
+ def save_toolbar_state(self, state, config):
+ config.set_key('show_toolbar_history', state)
+
+ def select_start_date(self):
+ self.start_timestamp = self.select_date(self.start_button)
+ self.update()
+
+ def select_end_date(self):
+ self.end_timestamp = self.select_date(self.end_button)
+ self.update()
+
+ def select_date(self, button):
+ d = WindowModalDialog(self, _("Select date"))
+ d.setMinimumSize(600, 150)
+ d.date = None
+ vbox = QVBoxLayout()
+ def on_date(date):
+ d.date = date
+ cal = QCalendarWidget()
+ cal.setGridVisible(True)
+ cal.clicked[QDate].connect(on_date)
+ vbox.addWidget(cal)
+ vbox.addLayout(Buttons(OkButton(d), CancelButton(d)))
+ d.setLayout(vbox)
+ if d.exec_():
+ if d.date is None:
+ return None
+ date = d.date.toPyDate()
+ button.setText(self.format_date(date))
+ return time.mktime(date.timetuple())
+
+ def show_summary(self):
+ h = self.summary
+ if not h:
+ self.parent.show_message(_("Nothing to summarize."))
+ return
+ start_date = h.get('start_date')
+ end_date = h.get('end_date')
+ format_amount = lambda x: self.parent.format_amount(x.value) + ' ' + self.parent.base_unit()
+ d = WindowModalDialog(self, _("Summary"))
+ d.setMinimumSize(600, 150)
+ vbox = QVBoxLayout()
+ grid = QGridLayout()
+ grid.addWidget(QLabel(_("Start")), 0, 0)
+ grid.addWidget(QLabel(self.format_date(start_date)), 0, 1)
+ grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/BTC'), 0, 2)
+ grid.addWidget(QLabel(_("Initial balance")), 1, 0)
+ grid.addWidget(QLabel(format_amount(h['start_balance'])), 1, 1)
+ grid.addWidget(QLabel(str(h.get('start_fiat_balance'))), 1, 2)
+ grid.addWidget(QLabel(_("End")), 2, 0)
+ grid.addWidget(QLabel(self.format_date(end_date)), 2, 1)
+ grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/BTC'), 2, 2)
+ grid.addWidget(QLabel(_("Final balance")), 4, 0)
+ grid.addWidget(QLabel(format_amount(h['end_balance'])), 4, 1)
+ grid.addWidget(QLabel(str(h.get('end_fiat_balance'))), 4, 2)
+ grid.addWidget(QLabel(_("Income")), 5, 0)
+ grid.addWidget(QLabel(format_amount(h.get('income'))), 5, 1)
+ grid.addWidget(QLabel(str(h.get('fiat_income'))), 5, 2)
+ grid.addWidget(QLabel(_("Expenditures")), 6, 0)
+ grid.addWidget(QLabel(format_amount(h.get('expenditures'))), 6, 1)
+ grid.addWidget(QLabel(str(h.get('fiat_expenditures'))), 6, 2)
+ grid.addWidget(QLabel(_("Capital gains")), 7, 0)
+ grid.addWidget(QLabel(str(h.get('capital_gains'))), 7, 2)
+ grid.addWidget(QLabel(_("Unrealized gains")), 8, 0)
+ grid.addWidget(QLabel(str(h.get('unrealized_gains', ''))), 8, 2)
+ vbox.addLayout(grid)
+ vbox.addLayout(Buttons(CloseButton(d)))
+ d.setLayout(vbox)
+ d.exec_()
+
+ def plot_history_dialog(self):
+ if plot_history is None:
+ self.parent.show_message(
+ _("Can't plot history.") + '\n' +
+ _("Perhaps some dependencies are missing...") + " (matplotlib?)")
+ return
+ try:
+ plt = plot_history(self.transactions)
+ plt.show()
+ except NothingToPlotException as e:
+ self.parent.show_message(str(e))
+
+ @profiler
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ fx = self.parent.fx
+ r = self.wallet.get_full_history(domain=self.get_domain(), from_timestamp=self.start_timestamp, to_timestamp=self.end_timestamp, fx=fx)
+ self.transactions = r['transactions']
+ self.summary = r['summary']
+ if not self.years and self.transactions:
+ from datetime import date
+ start_date = self.transactions[0].get('date') or date.today()
+ end_date = self.transactions[-1].get('date') or date.today()
+ self.years = [str(i) for i in range(start_date.year, end_date.year + 1)]
+ self.period_combo.insertItems(1, self.years)
+ item = self.currentItem()
+ current_tx = item.data(0, Qt.UserRole) if item else None
+ self.clear()
+ if fx: fx.history_used_spot = False
+ blue_brush = QBrush(QColor("#1E1EFF"))
+ red_brush = QBrush(QColor("#BC1E1E"))
+ monospace_font = QFont(MONOSPACE_FONT)
+ for tx_item in self.transactions:
+ tx_hash = tx_item['txid']
+ height = tx_item['height']
+ conf = tx_item['confirmations']
+ timestamp = tx_item['timestamp']
+ value = tx_item['value'].value
+ balance = tx_item['balance'].value
+ label = tx_item['label']
+ tx_mined_status = TxMinedStatus(height, conf, timestamp, None)
+ status, status_str = self.wallet.get_tx_status(tx_hash, tx_mined_status)
+ has_invoice = self.wallet.invoices.paid.get(tx_hash)
+ icon = self.icon_cache.get(":icons/" + TX_ICONS[status])
+ v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True)
+ balance_str = self.parent.format_amount(balance, whitespaces=True)
+ entry = ['', tx_hash, status_str, label, v_str, balance_str]
+ fiat_value = None
+ if value is not None and fx and fx.show_history():
+ fiat_value = tx_item['fiat_value'].value
+ value_str = fx.format_fiat(fiat_value)
+ entry.append(value_str)
+ # fixme: should use is_mine
+ if value < 0:
+ entry.append(fx.format_fiat(tx_item['acquisition_price'].value))
+ entry.append(fx.format_fiat(tx_item['capital_gain'].value))
+ item = SortableTreeWidgetItem(entry)
+ item.setIcon(0, icon)
+ item.setToolTip(0, str(conf) + " confirmation" + ("s" if conf != 1 else ""))
+ item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf))
+ if has_invoice:
+ item.setIcon(3, self.icon_cache.get(":icons/seal"))
+ for i in range(len(entry)):
+ if i>3:
+ item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter)
+ if i!=2:
+ item.setFont(i, monospace_font)
+ if value and value < 0:
+ item.setForeground(3, red_brush)
+ item.setForeground(4, red_brush)
+ if fiat_value and not tx_item['fiat_default']:
+ item.setForeground(6, blue_brush)
+ if tx_hash:
+ item.setData(0, Qt.UserRole, tx_hash)
+ self.insertTopLevelItem(0, item)
+ if current_tx == tx_hash:
+ self.setCurrentItem(item)
+
+ def on_edited(self, item, column, prior):
+ '''Called only when the text actually changes'''
+ key = item.data(0, Qt.UserRole)
+ text = item.text(column)
+ # fixme
+ if column == 3:
+ self.parent.wallet.set_label(key, text)
+ self.update_labels()
+ self.parent.update_completions()
+ elif column == 6:
+ self.parent.wallet.set_fiat_value(key, self.parent.fx.ccy, text)
+ self.on_update()
+
+ def on_doubleclick(self, item, column):
+ if self.permit_edit(item, column):
+ super(HistoryList, self).on_doubleclick(item, column)
+ else:
+ tx_hash = item.data(0, Qt.UserRole)
+ tx = self.wallet.transactions.get(tx_hash)
+ self.parent.show_transaction(tx)
+
+ def update_labels(self):
+ root = self.invisibleRootItem()
+ child_count = root.childCount()
+ for i in range(child_count):
+ item = root.child(i)
+ txid = item.data(0, Qt.UserRole)
+ label = self.wallet.get_label(txid)
+ item.setText(3, label)
+
+ def update_item(self, tx_hash, tx_mined_status):
+ if self.wallet is None:
+ return
+ conf = tx_mined_status.conf
+ status, status_str = self.wallet.get_tx_status(tx_hash, tx_mined_status)
+ icon = self.icon_cache.get(":icons/" + TX_ICONS[status])
+ items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
+ if items:
+ item = items[0]
+ item.setIcon(0, icon)
+ item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf))
+ item.setText(2, status_str)
+
+ def create_menu(self, position):
+ self.selectedIndexes()
+ item = self.currentItem()
+ if not item:
+ return
+ column = self.currentColumn()
+ tx_hash = item.data(0, Qt.UserRole)
+ if not tx_hash:
+ return
+ if column is 0:
+ column_title = "ID"
+ column_data = tx_hash
+ else:
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+ tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
+ height = self.wallet.get_tx_height(tx_hash).height
+ tx = self.wallet.transactions.get(tx_hash)
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
+ is_unconfirmed = height <= 0
+ pr_key = self.wallet.invoices.paid.get(tx_hash)
+ menu = QMenu()
+ if height == TX_HEIGHT_LOCAL:
+ menu.addAction(_("Remove"), lambda: self.remove_local_tx(tx_hash))
+ menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
+ for c in self.editable_columns:
+ menu.addAction(_("Edit {}").format(self.headerItem().text(c)),
+ lambda bound_c=c: self.editItem(item, bound_c))
+ menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
+ if is_unconfirmed and tx:
+ # note: the current implementation of RBF *needs* the old tx fee
+ rbf = is_mine and not tx.is_final() and fee is not None
+ if rbf:
+ menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx))
+ else:
+ child_tx = self.wallet.cpfp(tx, 0)
+ if child_tx:
+ menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx))
+ if pr_key:
+ menu.addAction(self.icon_cache.get(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(pr_key))
+ if tx_URL:
+ menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def remove_local_tx(self, delete_tx):
+ to_delete = {delete_tx}
+ to_delete |= self.wallet.get_depending_transactions(delete_tx)
+ question = _("Are you sure you want to remove this transaction?")
+ if len(to_delete) > 1:
+ question = _(
+ "Are you sure you want to remove this transaction and {} child transactions?".format(len(to_delete) - 1)
+ )
+ answer = QMessageBox.question(self.parent, _("Please confirm"), question, QMessageBox.Yes, QMessageBox.No)
+ if answer == QMessageBox.No:
+ return
+ for tx in to_delete:
+ self.wallet.remove_transaction(tx)
+ self.wallet.save_transactions(write=True)
+ # need to update at least: history_list, utxo_list, address_list
+ self.parent.need_update.set()
+
+ def onFileAdded(self, fn):
+ try:
+ with open(fn) as f:
+ tx = self.parent.tx_from_text(f.read())
+ self.parent.save_transaction_into_wallet(tx)
+ except IOError as e:
+ self.parent.show_error(e)
+
+ def export_history_dialog(self):
+ d = WindowModalDialog(self, _('Export History'))
+ d.setMinimumSize(400, 200)
+ vbox = QVBoxLayout(d)
+ defaultname = os.path.expanduser('~/electrum-history.csv')
+ select_msg = _('Select file to export your wallet transactions to')
+ hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
+ vbox.addLayout(hbox)
+ vbox.addStretch(1)
+ hbox = Buttons(CancelButton(d), OkButton(d, _('Export')))
+ vbox.addLayout(hbox)
+ #run_hook('export_history_dialog', self, hbox)
+ self.update()
+ if not d.exec_():
+ return
+ filename = filename_e.text()
+ if not filename:
+ return
+ try:
+ self.do_export_history(self.wallet, filename, csv_button.isChecked())
+ except (IOError, os.error) as reason:
+ export_error_label = _("Electrum was unable to produce a transaction export.")
+ self.parent.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export history"))
+ return
+ self.parent.show_message(_("Your wallet history has been successfully exported."))
+
+ def do_export_history(self, wallet, fileName, is_csv):
+ history = self.transactions
+ lines = []
+ for item in history:
+ if is_csv:
+ lines.append([item['txid'], item.get('label', ''), item['confirmations'], item['value'], item['date']])
+ else:
+ lines.append(item)
+ with open(fileName, "w+", encoding='utf-8') as f:
+ if is_csv:
+ import csv
+ transaction = csv.writer(f, lineterminator='\n')
+ transaction.writerow(["transaction_hash","label", "confirmations", "value", "timestamp"])
+ for line in lines:
+ transaction.writerow(line)
+ else:
+ from electrum.util import json_encode
+ f.write(json_encode(history))
diff --git a/electrum/gui/qt/icons_rc.py b/electrum/gui/qt/icons_rc.py
new file mode 100644
index 000000000..dcaab5a70
--- /dev/null
+++ b/electrum/gui/qt/icons_rc.py
@@ -0,0 +1,14540 @@
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created by: The Resource Compiler for PyQt5 (Qt v5.9.1)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x06\xeb\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x32\x00\x00\x00\x32\x08\x06\x00\x00\x00\x1e\x3f\x88\xb1\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x11\x4d\x00\x00\x11\x4d\
+\x01\xc0\x39\x60\x63\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x06\x68\x49\x44\
+\x41\x54\x68\x81\xed\x9a\x5b\x6c\x53\x47\x1a\xc7\xff\xf3\xcd\xf1\
+\x3d\x76\x12\xec\x90\x84\x55\x9a\x02\x49\x50\x41\xa4\x14\x08\x18\
+\xaf\x84\xa8\xd2\x02\x5d\xf5\xa1\x2d\x6d\x1f\xbb\xfb\xd4\xdd\xb7\
+\x95\x56\x6a\xa1\xa9\xb6\x4a\xb7\xa5\x6d\x90\x76\x9f\xf7\xf2\xb6\
+\xea\x4b\x5b\xf5\x26\x55\x6d\xa1\x45\xec\x03\x24\xe1\x92\x0a\xd2\
+\x0b\x24\x81\x96\xcb\x12\x08\xb1\xe3\xc4\x76\x7c\x39\x3e\xe7\x9b\
+\x7d\x48\x0c\x0e\x31\x89\xbd\x3e\xc4\xdb\x68\x7f\x6f\xe7\x9b\x39\
+\x33\xdf\x7f\xce\x99\x6f\xbe\x73\x66\x80\x65\x82\xb0\xb2\xb1\xdd\
+\x6f\x9e\x6a\x82\x34\x43\x20\xb4\x48\x21\xda\x00\xb1\x02\x80\x77\
+\xb6\x38\x0e\xc5\x11\x56\x18\x56\x0a\x97\x24\x89\x13\x5f\xec\x0f\
+\xfe\xdb\xaa\xbe\xcb\x16\xf2\xab\x77\x7a\x37\x32\xf0\x82\x90\xf4\
+\x1c\x9b\xaa\x99\x48\x98\x55\x2e\x57\xd6\x5b\xe5\x74\x38\x6c\x9a\
+\xd0\x34\x09\x00\xc8\x1a\x26\xf4\xac\xa1\xe2\x89\x74\x26\x91\x4a\
+\xd9\x98\x95\x24\xa2\xcb\xcc\xfc\x3e\x20\xff\x79\xf8\xc0\xb6\xef\
+\x97\x5e\x88\x52\x62\x77\x4f\xff\x93\x92\xe8\x35\x66\xde\xea\xf5\
+\xb8\x32\xcd\x8d\x01\x47\x60\x85\x17\x35\x3e\x0f\xc4\x22\xcd\x2a\
+\xa5\x30\x19\x9f\x46\x38\x1a\xc7\x95\xeb\xe3\x7a\x3c\x99\xb6\x13\
+\xd1\x69\x66\xbc\x7e\x78\xff\xb6\xcf\x21\x84\xba\xef\x42\xf6\x1c\
+\xea\xdb\x4c\x10\xff\x60\xc6\x23\xab\xea\x6b\xb9\xad\xb9\x51\xd6\
+\xfa\x3c\xa5\x36\x33\x87\x68\x2c\x81\x91\x2b\x37\xcd\xeb\x63\x51\
+\x22\x29\xce\x18\xca\x78\xf1\xab\x97\x7f\x79\xb6\x94\x36\x8a\x16\
+\xb2\xab\xfb\x98\xe6\x70\x39\xdf\x14\x4a\xbc\xb4\xd2\xef\x35\xdb\
+\xd7\x3d\x68\xab\x72\x3b\x4b\xf7\x7a\x01\x12\xc9\x14\xce\x0d\x5d\
+\xc9\x8e\x47\xe2\x92\x05\x7a\xaa\x57\x5f\xfd\xe3\x07\xcf\x3f\x6f\
+\x16\x73\x6f\x51\x42\x3a\x0f\xf6\xd7\x3b\x6c\xe2\x53\x21\xb0\x65\
+\xf3\x86\xb5\xda\x2f\x56\xd6\x96\xe7\xf1\x22\x5c\x1f\x9b\xc0\x37\
+\x3f\xfc\x64\x28\x56\xa7\x94\x99\x79\xea\x8b\xae\x9d\xe3\x8b\xdd\
+\xb3\xa8\x90\xc7\xde\x3e\xbd\xc6\x2e\xf9\x98\xd7\xeb\x6e\x08\x6e\
+\x6c\xb1\xbb\x9c\x76\x6b\xbc\x5d\x84\x64\x3a\x83\x93\x83\x17\xf5\
+\x78\x3c\x75\xd3\x20\xec\x3a\xf2\xd2\xf6\x9f\x16\xaa\xbf\xa0\x90\
+\x27\x0e\x9d\x58\x0b\xc8\x93\xfe\x5a\x6f\xf5\x8e\xf6\x36\x4d\x4a\
+\xb2\xd6\xdb\x45\x30\x4c\xc6\xa9\xc1\x91\x6c\x38\x1a\x9f\xd2\x4d\
+\xda\xfe\xf5\x2b\x1d\x3f\xde\xab\xee\x3d\x85\xec\x39\x78\xa6\x51\
+\xda\x8d\xd3\x81\x9a\xea\x86\xe0\xa6\x56\x49\xc2\xd2\x25\xa7\x68\
+\x58\x29\xf4\x9d\x1d\x36\xc2\x93\xf1\x1b\xba\xae\x3a\x8e\xbe\x1a\
+\x1c\x2b\x54\xaf\xe0\x10\x6f\xf9\xdb\x19\x9b\xb4\x19\x9f\x78\x3d\
+\xee\xba\x6d\xed\x2d\x15\x13\x01\x00\x24\x04\xb6\xb7\xb7\x6a\x5e\
+\x8f\xab\xde\x6e\x13\x1f\xef\xea\x3e\xa6\x15\xac\x57\xc8\x58\x37\
+\x65\xbc\x25\x88\x1e\x09\xb6\xb7\xda\xb5\x25\x7e\x9d\x0a\xa1\x49\
+\xc2\x8e\xf6\x16\x3b\x91\xd8\xea\x74\xba\xde\x28\x54\x67\xde\x50\
+\x3f\x7e\xe8\xc4\x26\xc9\x72\x60\xdb\xc3\x2d\xb4\xaa\xae\xbc\xe8\
+\x64\x97\xc0\xda\xda\x99\x2e\x2e\x45\x15\xf4\xa2\x02\xe9\xbd\x19\
+\xbd\x15\xc5\xa9\xc1\x8b\x4c\xe0\x4d\x9f\x1f\x08\x7d\x9b\x5f\x36\
+\x6f\xb8\x35\xa1\xfd\xbd\x3e\x50\x6d\x5a\x21\x62\xdf\x43\x84\x9d\
+\xcd\x02\x3b\x9b\x05\xf6\x3d\x44\xb0\xcb\xb2\x9a\xc4\xaa\x95\xb5\
+\xa8\xf3\xfb\x4c\x48\xf9\xd7\xbb\xcb\xe6\x08\xd9\xfb\x4e\xef\x1e\
+\xc5\x6a\x4b\xfb\xba\x07\x6c\xe5\x75\x39\xf3\x24\x7c\x8e\x3b\xd7\
+\x3e\x07\xb0\xa6\xb6\xfc\xb9\xf6\xf0\xba\x66\x9b\x69\xaa\x1d\x7b\
+\x7b\x7a\x3b\xf3\xed\x73\x84\x10\xc9\xee\xa6\x86\x80\xf2\xb8\xca\
+\x5f\xb1\x0b\xc5\x07\x2b\x42\x46\x95\xdb\x89\xa6\x06\x3f\x13\xe4\
+\x9f\xf2\xed\xb7\x85\x3c\xf1\x56\xff\x7a\x93\x39\xd8\xda\x5c\x5f\
+\xe6\x0b\x30\xc3\xc5\x09\x85\x58\xe6\xce\x75\x2c\x33\x33\x4f\xac\
+\xa0\xed\xc1\x06\x69\x2a\x0e\xed\xed\x39\xbe\x2e\x67\xbb\x1d\xca\
+\x4c\x89\xdf\xd4\x78\x5c\xba\xaf\xca\x6d\xc9\xd2\xad\x9b\xc0\x87\
+\xe7\xd9\xd2\xc9\x9e\xc3\x57\xe5\x86\xcf\xe3\xd2\xa7\x12\xe9\x5f\
+\x03\xe8\x02\xf2\x9e\x88\x4d\x88\x67\x9b\x1a\x02\x96\xe6\x1f\xba\
+\x09\x9c\x0f\x2b\x9c\x0f\x5b\x27\x22\x47\x53\xa3\xdf\xae\x49\xf1\
+\x6c\xee\x9a\x00\xe0\xf1\x9e\xe3\xab\x0c\xe6\xd5\x75\x7e\x9f\xb5\
+\xbd\xdd\x47\xea\x56\xf8\x60\x32\xb7\x74\x1e\xec\xaf\x07\x66\x85\
+\x90\xd2\x3a\x24\x91\x51\xed\x75\x57\xd6\xbb\x12\xa8\xf1\x7a\x40\
+\x24\x58\xda\xb8\x03\x98\x9d\x23\x42\xf0\xfa\x2a\x8f\x27\x2b\x20\
+\x0a\x2e\xff\xa5\x12\x6a\x12\x08\xb8\xe6\xc7\xa8\xa1\x88\xc2\x50\
+\xc4\x9a\x09\x2f\x84\x40\x95\xcb\x99\x8d\x4d\xa7\xd6\x03\xf8\x4c\
+\x03\x00\x05\x6a\xf5\xba\x5d\x8e\x45\xee\x2d\x9a\x48\x12\x08\xb8\
+\x80\x46\xef\x5c\xfb\x68\xc2\xaa\x1e\x66\xf0\x7a\xdd\xb6\xd8\x74\
+\xaa\x05\xc8\xbd\x5a\x80\xdf\x61\xd7\x2c\x4b\xaa\x86\x22\x0a\xa3\
+\x09\x6b\x46\x7e\x21\x1c\x36\x29\x05\x89\x95\xc0\xac\x10\x41\xa2\
+\x3a\xf7\xb7\xe3\xe7\x84\x26\x35\x10\x44\x0d\x70\x27\xfc\x56\x2e\
+\x4f\xb7\x08\x02\x00\xc5\x6a\xd2\x30\x2c\x0e\xf4\x4b\x80\x61\x1a\
+\x50\x4a\x45\x81\x59\x21\x0c\x44\x32\xba\xc1\x95\x75\xab\x74\x32\
+\x59\xd3\x64\xa5\xc6\x81\xdc\xab\xc5\x6a\x28\x96\x48\x66\x16\xbc\
+\xeb\x7f\x90\x78\x2c\xa9\x2b\x88\x61\x20\x27\x44\x88\x0b\xd3\xa9\
+\x8c\x4d\xe1\xfe\x47\x1a\xab\x50\x4a\x21\x91\x4e\xdb\x41\x7c\x01\
+\x98\x15\x62\xb0\x38\x6e\x2a\x96\x93\xb1\xe9\xca\x7a\x57\x02\xd1\
+\xa9\x04\x4c\xa5\x48\x68\xe8\x05\x66\x85\x1c\xed\xda\x1e\x21\xa2\
+\x4b\xe1\x89\x78\x65\xbd\x2b\x81\xf0\x64\x1c\x36\x21\x87\x0e\xff\
+\x21\x34\x01\xe4\x65\xbf\x6c\xf0\x7b\x57\x6f\x86\x53\x95\x73\xad\
+\x34\xae\x8d\x46\x52\x06\xf3\xfb\xb9\xeb\xdb\x42\x94\x52\xef\xc6\
+\x12\x29\x57\x2c\x91\xac\x8c\x67\x25\x10\x4b\x24\x11\x4b\xa6\x9c\
+\x4c\x78\x37\x67\xbb\x2d\xe4\x48\x57\xe8\x02\x11\xf5\x8d\x5c\xbe\
+\x61\x54\xc6\xbd\xe2\x19\xbe\x3c\x6a\x90\x10\x27\xbe\x7a\x39\x38\
+\x92\xb3\xcd\xc9\xaf\x14\x9b\xaf\x5f\x1b\x9b\xa0\xe9\x54\x7a\xe9\
+\xbd\x2b\x92\x44\x32\x8d\x6b\x37\xa3\x52\x81\xbb\xf3\xed\x73\x84\
+\x7c\x79\x20\x74\x58\x90\x18\x18\x1c\xba\x9a\x2d\xa7\xb3\x50\x93\
+\x40\x9b\x7f\x7e\xd6\xd3\xe6\x17\x08\x35\x95\x97\x0d\x9d\x1b\xba\
+\x92\x95\x52\xf4\x7d\xb9\x3f\x74\x34\xdf\x3e\xef\xfb\xc3\x50\xc6\
+\x8b\x63\xe1\xa9\x81\xd1\xf1\x28\xfe\xdb\x7f\x5b\x91\x24\xa0\x9b\
+\x85\xd7\xa4\x78\x19\xcb\xee\xe8\xad\x28\xc6\x23\x31\x49\xe0\xdf\
+\xdd\x5d\x56\x70\x78\x76\xf7\xf4\x1f\xb2\x4b\xfa\x7d\x67\x70\xe3\
+\x92\x6d\x23\x2c\x46\x2a\x9d\xc1\xd7\x7d\xdf\xe9\x59\x83\xff\x7c\
+\xe4\x95\x60\xd7\xdd\xe5\x05\xbf\x41\x22\x35\xda\xab\xcc\x7c\xb6\
+\x7f\x70\x44\x37\xcc\xca\xa7\x60\x86\xc9\xe8\x1b\xbc\xa8\xb3\x52\
+\x03\x7a\x26\xf5\x5a\xa1\x3a\x05\x85\x0c\xfc\x76\x6b\xd6\xcc\x6a\
+\x4f\xc5\x12\xc9\x5b\x27\xcf\x0d\x67\x59\x55\x2e\x75\x61\xa5\xd0\
+\x3f\x38\xa2\xc7\x13\xc9\x1b\x7a\x56\x3d\xfd\xaf\xee\x47\x0b\x46\
+\xd5\xe5\xbf\xd1\x93\x63\x59\x6c\xbd\xe5\xe8\x3c\xd8\x5f\x6f\xd3\
+\xc4\x47\x92\xc4\xd6\xcd\x1b\xd6\xd8\x97\x68\x33\x54\x37\x99\x4f\
+\x0a\x53\xdf\x67\xc9\x66\x68\x8e\x65\xb1\x3d\x9d\xcf\xcf\xfe\xc0\
+\xc0\x1c\x96\xc3\x11\x8e\xbb\xc9\x1d\xaa\x21\x92\xcf\x98\xcc\xab\
+\x89\x04\x17\x79\xa8\x86\x24\xd1\x8f\x26\xf3\x87\x95\x3b\x54\x73\
+\x0f\xf6\xfc\xa5\x77\x85\xd0\xd1\xc1\xa0\x8d\x1a\x61\x83\x82\x08\
+\x20\xef\x98\x93\x80\x0a\x1b\x8c\xef\x09\xfc\xad\xb2\xe3\x74\xee\
+\xa3\xe8\xff\xe4\xf1\x1f\x46\x8e\x00\x94\x1c\x1f\x46\xe0\x00\x00\
+\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x06\x0e\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x05\xd5\x49\x44\x41\x54\x78\x5e\xed\x9a\x5b\x4c\x1c\x55\
+\x18\xc7\xff\x67\x77\xe9\xee\x02\x5b\xca\xb5\x16\x7a\x87\x6e\x0b\
+\x14\xa4\x58\x4a\x52\xed\xbd\xc6\x14\x95\x4a\xdb\x68\xb4\x49\xed\
+\x05\xfa\xe0\x5d\xeb\x83\x89\x0f\x45\x13\x1f\x4c\x9a\x28\xc6\x04\
+\x13\x1f\x7c\xab\xa6\xa9\xd1\x68\x62\x8d\xb1\xe5\x62\x4c\xa4\x84\
+\x36\x60\x02\xbd\x40\x0b\xc5\x52\x60\x4b\x85\x72\xdf\xcb\x8c\x39\
+\x23\x4b\x97\x65\x77\xe6\xcc\xec\x99\x76\x17\x76\x5e\x78\x98\x33\
+\xe7\x7c\xbf\xff\xf9\x7f\xe7\xf2\x2d\x04\xf3\xfc\x21\xf3\x9c\x1f\
+\x51\x01\xa2\x0e\x98\xe7\x0a\x44\x53\x60\x9e\x1b\x20\xba\x08\xce\
+\xd9\x14\xf8\xac\xd1\xb5\x5d\x10\x3c\x07\x4f\x14\x5b\x2a\xe4\x5c\
+\x3e\xe7\x04\xf8\xe2\xa2\x6b\x9b\x00\x4f\x25\x40\xb6\x0b\xc0\xc7\
+\xef\x6d\x32\x9f\x9c\x17\x02\xf8\x82\x7b\x81\x8d\x06\x64\xbf\xb9\
+\xd1\x7c\x65\x4e\x0b\x10\x08\x7c\x0a\xf8\xea\x3b\x9b\xcc\xeb\x94\
+\x16\xf9\x88\x4d\x01\x7f\x70\x41\x10\x71\x77\x70\x1c\x69\x49\xb1\
+\x12\xb3\x28\xe2\xa3\x77\x8b\xcd\x95\x73\x4e\x80\x40\x33\x3e\x3a\
+\xee\xc6\xe5\xb6\x5e\x3c\xb9\x61\x29\xc8\xd4\x94\xb2\xd8\x9f\x8a\
+\x13\x31\x0e\x08\x66\xf5\xbe\x81\x31\xd4\x34\x76\x61\xff\xee\xb5\
+\x88\x31\x19\xbc\x13\xce\x64\xff\x88\x10\x40\x26\xc7\xd1\xda\x31\
+\x80\xda\xa6\x5b\x38\x5c\x9a\x87\x58\x8b\x69\xda\xed\xac\xf6\x0f\
+\x6b\x01\xe4\xc0\x45\x11\x38\xdf\xd0\x89\xe6\xab\x0e\x1c\x2d\xcb\
+\x43\xe2\x42\xcb\x8c\x54\x67\xb5\x7f\x58\x0a\x20\x07\x4e\x03\x9e\
+\x98\x74\xe3\xec\xef\x57\xd1\xdd\x3b\x8c\x43\xcf\xe5\x22\x63\xb1\
+\xcd\x7f\x9d\x63\xb6\x7f\x58\x09\xa0\x04\x4e\x83\x1d\x18\x1c\xc7\
+\xe9\x73\xad\xb8\x3f\xe2\xc4\x81\xa7\xed\x58\xbb\x32\x79\xd6\x22\
+\xaf\xc6\xfe\x61\x21\x00\x0b\x38\x0d\xf4\xfa\xad\x7f\xf1\xc3\xf9\
+\x6b\x70\xb9\x05\xec\x28\x5a\x81\xcd\x05\xe9\x01\x77\x38\x35\xf6\
+\x7f\xa4\x02\xb0\x82\xd3\x20\xff\xb8\xf4\x0f\xea\x9b\xba\x25\xe0\
+\x0d\xeb\xd2\x50\xb2\x25\x33\xd8\xf6\xae\xca\xfe\x8f\x44\x00\x35\
+\xe0\x4e\x97\x47\x9a\xf5\xf6\xee\x41\x09\x78\x65\x7a\x02\x5e\x29\
+\xc9\x06\xf1\x6e\xf6\x7e\x32\xa8\xb5\xff\x43\x15\x40\x0d\x38\x0d\
+\x6c\x68\x78\x52\xca\xf7\x7b\x43\x13\x12\x66\x6a\x62\x2c\x0e\xef\
+\x5d\x8f\x05\x31\xc6\xa0\x87\x3b\xb5\xf6\x7f\x28\x02\xa8\x05\xa7\
+\x41\x75\xde\x1e\x92\x56\xfa\x49\xa7\x47\x82\x8d\x8f\x5d\x80\x63\
+\x65\x79\xd2\x5f\x99\x47\xb5\xfd\x75\x15\x40\x0b\x38\x0d\xe8\xaf\
+\x96\x3b\xb8\xd0\xd0\x09\x71\x8a\x94\xce\xf8\x91\x17\xf2\x90\xb2\
+\xc8\x2a\x07\xcf\x7c\xf6\xf7\xef\x84\xfb\x51\x58\x2b\xb8\xdb\x23\
+\xe0\xa7\xda\x76\xb4\xdd\x18\x98\x8e\xd1\x40\x08\x0e\x3e\x9b\x83\
+\xe5\x4b\x16\xca\xc2\xd3\x97\x5a\xec\xcf\xd5\x01\x5a\xc1\x69\x10\
+\xc3\x63\x4e\x7c\x77\xae\x0d\xfd\xf7\xc6\x66\x80\x96\xed\x5c\x83\
+\x9c\xcc\x14\x45\x78\x00\x9a\xec\xcf\x45\x80\x50\xc0\x69\x00\xb7\
+\xfb\x86\x71\xe6\xb7\x2b\x18\x9b\x70\xcf\x00\xdd\xfa\xc4\x32\x6c\
+\x29\x5c\xca\x02\xaf\xd9\xfe\x21\x09\x10\x2a\x38\x1d\xfc\xef\xeb\
+\x0e\xfc\x5c\xd7\x4e\x01\x66\x3c\x79\x6b\x52\x51\xba\x3d\x8b\x09\
+\x3e\x14\xfb\x6b\x12\x80\x07\x38\x2d\x5e\xfc\xfa\xe7\x0d\x5c\xbe\
+\xd2\x3f\x0b\x92\xee\xf5\x2f\xef\xc9\x86\xc1\xc0\xbc\x3c\x69\xb6\
+\xbf\x2a\x01\x78\x80\xd3\x01\x47\xc7\x5d\x92\xe5\x7b\xfa\x47\x66\
+\xc1\x27\x27\x58\x70\xb4\x2c\x5f\x76\xaf\xf7\xff\x48\xcb\xe1\xc7\
+\xb7\x0f\x45\x99\x79\x81\xd3\x41\xfb\x06\x46\xa5\xc5\x6e\x64\xdc\
+\x35\x0b\x3e\xce\x1a\x23\xed\xf5\xb6\x38\x33\xb3\xf5\x43\xb5\x3f\
+\x93\x03\xaa\x1a\x9c\xaf\x0a\x10\xab\x09\x81\xfc\x46\xac\x10\x76\
+\x6b\xc7\x5d\x69\x9b\xf3\x08\x7e\x09\x0f\x48\x95\x1c\x7a\xca\x4b\
+\x4b\x8a\x53\x05\x1f\xca\xea\xef\x1d\x48\xd1\x01\xb4\x61\x55\xe3\
+\xe4\x7a\x41\xc4\x69\x02\xe4\xa9\x8d\x50\x2a\x5e\x5c\xec\x42\x43\
+\x4b\x4f\xc0\x4f\xe9\xb1\x9e\xe6\xfc\xaa\x8c\x45\x6a\xbb\x0e\x69\
+\xf5\x57\x25\x00\x6d\xfc\xcd\x4d\xd1\x32\xe4\x70\x56\x03\x38\xcc\
+\x1a\xa9\xb7\x78\xd1\xd5\x73\x3f\xe8\x27\xcf\x6f\xcb\x44\xbe\x3d\
+\x8d\xb5\xcb\x19\xed\xb4\x1e\x7e\x54\xad\x01\xfe\x91\xb1\xa6\x84\
+\x6f\xf1\x22\x18\xdd\xe6\x82\x0c\xec\x28\x5a\xae\x09\x9e\x87\xfd\
+\x99\xd6\x80\x40\xd1\x29\xa5\x84\x6f\xf1\x22\x18\x5d\xf6\xea\x64\
+\xec\xdb\x65\xd7\x0a\xcf\xc5\xfe\x9a\x05\x90\x4b\x09\xdf\xe2\x45\
+\x30\xba\x65\x8f\xd9\xa4\x33\xbe\xd1\x30\x5d\xc6\x56\x2d\x04\x0f\
+\xfb\x87\x24\x80\x37\x62\x6f\x4a\xb8\xdc\x1e\xab\x6f\xf1\x22\x18\
+\x11\xdd\xeb\xe9\xed\xce\xbc\xe0\x41\x19\x5b\x35\x7d\x08\x67\x7f\
+\xff\xb1\x98\x76\x01\xa5\x00\x69\x4a\x7c\xfd\x7d\x4b\x43\xdf\xc0\
+\xe8\xff\xbf\x4b\x05\x79\xac\x66\x23\x8e\xed\x2b\x40\x42\xbc\xec\
+\xbd\x5e\x69\x38\x6e\xf6\xe7\xe2\x00\x6f\xb4\xa9\xc7\x6b\x7f\x81\
+\x88\x3d\xc1\xa2\x37\x19\x0d\x38\x54\x9a\x8b\x25\x29\xf1\x8a\x80\
+\x4a\x0d\x78\xd9\x9f\xab\x00\x29\xe5\x75\x27\x08\x11\x4f\x05\x0b\
+\xfe\xa5\x67\xd6\x21\x6b\x79\xa2\x12\x1b\xcb\xfb\x90\xce\xfe\xba\
+\xa4\x00\xed\x34\xe5\x78\x7d\x21\x11\x85\xa6\x40\x04\x7b\x9e\x5a\
+\x8d\xc2\xec\xc5\x2c\x70\x8a\x6d\x42\x3d\xfb\xeb\x26\x00\xed\x38\
+\xb5\xa2\x76\x98\x96\xf0\x7c\x07\x29\xce\x4f\xc7\xee\xe2\x15\x8a\
+\x60\xac\x0d\x78\xda\x9f\x6b\x0a\x48\x2e\x28\xaf\xf9\x91\x10\xb2\
+\xf7\x01\x0c\x71\x14\xed\xde\x98\xb8\x73\x85\xd1\x64\x62\xbf\xde\
+\xca\x69\xc1\xd5\xfe\xdc\x05\x48\xad\xa8\x7b\x1b\x10\x3f\xf7\x12\
+\x90\x38\x5b\x7d\x9c\xdd\xbe\x35\xd9\x4a\x70\x60\x6d\xcc\x2d\x8b\
+\x09\x9a\x8f\x7d\xb4\x4f\xde\xf6\xe7\x2e\x40\x62\x79\x7d\xbe\x89\
+\x08\xcd\x5e\x01\xcc\x59\xf6\x6b\x31\x36\x9b\x5d\x14\x51\x75\xb2\
+\x24\xfe\x03\xb5\x77\x09\x7f\x2b\xf0\xb6\x3f\x77\x01\xa4\x34\xa8\
+\xa8\xbd\x47\x80\x44\x91\x90\x6e\x5b\x41\xe1\x32\x88\xa8\xbf\xe9\
+\x88\xdf\x85\x4a\x22\x15\xfd\x58\xef\x12\x01\xf2\x80\xbb\xfd\x75\
+\x11\x20\xb5\xa2\xe6\x2c\x40\xf6\x1b\x13\x92\x2e\x58\x57\xaf\xca\
+\x1a\x77\x63\x63\xef\x5b\x36\x87\x2f\x90\xd2\x5d\x22\xd0\x22\xa0\
+\x87\xfd\x75\x11\x20\xb9\xbc\xee\x75\x03\x11\xbf\xb4\xe6\xe6\x76\
+\x10\x53\xec\x8b\x5d\x6f\xc4\x5d\x0a\x04\xa4\xf6\x7a\xad\x87\xfd\
+\x75\x11\x20\xe9\x48\x7d\x8e\xc1\x4c\xbe\x8d\xcf\x2f\xa8\xee\x7c\
+\xcd\xf6\x95\xd2\xf6\xc6\x98\x12\xba\xd8\x5f\x17\x01\x68\xa7\x4b\
+\xde\x6f\xfe\xf0\xce\xa9\xc7\x3f\x51\x82\xf7\xbe\x57\x4a\x09\xbd\
+\xec\xaf\x9b\x00\xac\xe0\xbe\xed\xe4\x52\x42\x2f\xfb\x87\x95\x00\
+\xd3\x6e\x98\x5d\x84\xd5\xcd\xfe\x61\x29\x80\xb4\x55\xfa\x14\x61\
+\xf5\xb4\x7f\xd8\x0a\x40\x03\xf3\xa6\x84\xd1\x80\x4f\x95\xfe\xe1\
+\x59\x4b\xca\x4d\x9f\x56\x43\xf9\x78\x2e\x7c\xcb\xa5\x22\x14\xc9\
+\x42\x44\x05\x88\xe4\xd9\xe3\x11\x7b\xd4\x01\x3c\x54\x8c\xe4\x3e\
+\xa2\x0e\x88\xe4\xd9\xe3\x11\xfb\x7f\x6a\xe3\x79\x5f\xcd\xb0\xcb\
+\x22\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x08\x9e\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x3c\x00\x00\x00\x4e\x08\x03\x00\x00\x00\x4b\xc8\x85\x8a\
+\x00\x00\x01\x6e\x50\x4c\x54\x45\x4c\x69\x71\x0f\x9f\xd3\x11\xa1\
+\xd3\x5b\x5b\x5b\x59\x5e\x62\x0d\x9f\xd2\x0f\xa0\xd2\x43\x8e\xa7\
+\x11\xa0\xd3\x4b\xa3\xce\x0b\x9e\xd2\x0f\xa0\xd2\x12\xa0\xd3\x0f\
+\xa0\xd5\x5c\x5c\x5c\x17\xa3\xd3\x0e\xa0\xd2\x10\xa1\xd3\x11\xa0\
+\xd3\x11\xa0\xd4\x95\xd5\xed\xa4\xd2\xe2\x11\xa1\xd3\x0d\x9f\xd2\
+\x0d\x9f\xd2\xbd\xd4\xd6\x0f\xa0\xd3\xb8\xb8\xb8\x5d\x5d\x5d\x5d\
+\x5d\x5d\x11\xa1\xd3\x0a\x9d\xd1\x11\xa0\xd3\x58\x58\x58\x5b\x5d\
+\x5f\xb4\xcc\xd7\x10\xa0\xd2\x10\xa0\xd2\x5a\x5a\x5a\x57\x5c\x60\
+\x5d\x5d\x5d\x12\xa0\xd3\x10\xa0\xd3\x05\x9c\xd1\x11\xa1\xd3\x5b\
+\x5b\x5b\x97\xd6\xec\x54\x53\x53\x0d\x9f\xd2\x04\x9b\xd0\x5e\x5e\
+\x5e\x5d\x5d\x5d\x12\xa1\xd3\x05\x9b\xd0\x60\x5b\x59\xba\xe3\xf4\
+\x5b\x5b\x5b\xb8\xb8\xb8\x5e\x5e\x5e\x53\x53\x53\x5d\x5d\x5d\x12\
+\xa0\xd3\xb6\xe2\xf0\x42\xb4\xdc\x95\xd7\xec\x5d\x5d\x5d\x08\x9b\
+\xcf\x46\xb5\xdd\xb8\xb8\xb8\x12\xa1\xd3\xff\xff\xff\x04\x9c\xd1\
+\x08\x9d\xd2\x0b\x9f\xd2\x0f\xa0\xd3\x11\xa1\xd3\x0d\x9f\xd2\x02\
+\x9b\xd0\x35\xaf\xda\xfb\xfe\xff\x46\xb6\xde\x2d\xac\xd9\x26\xa9\
+\xd7\x1a\xa5\xd5\x20\xa7\xd6\xc2\xe7\xf4\xfd\xfe\xff\x6b\xc4\xe4\
+\x4e\xb9\xdf\x15\xa3\xd4\x5c\xbf\xe2\xb3\xe1\xf2\x94\xd5\xec\x82\
+\xce\xe8\xf9\xfd\xfe\xd5\xef\xf8\x70\xc7\xe5\x3b\xb2\xdb\xe7\xf6\
+\xfb\xf0\xfa\xfd\xf5\xfb\xfe\xa2\xdb\xee\x40\xb4\xdc\xcc\xeb\xf6\
+\x99\xd7\xed\x8e\xd3\xeb\xc7\xe9\xf5\xde\xf2\xf9\x87\xd0\xe9\xda\
+\xf1\xf9\xd0\xed\xf7\xec\xf8\xfc\x56\xbc\xe0\xa8\xdd\xef\xae\xdf\
+\xf1\xbd\xe6\xf3\xba\xe4\xf3\x62\xc1\xe3\xe4\xf5\xfb\xab\xde\xf0\
+\x7a\xcb\xe7\x5e\x5e\x5e\x17\x48\x13\x72\x00\x00\x00\x45\x74\x52\
+\x4e\x53\x00\x25\xf2\xa2\x3b\xcc\x2d\x03\xf8\x07\xa0\xac\x68\x0e\
+\x10\x1e\x53\x71\x5c\x16\x71\x28\xbd\x7c\xe9\x0e\x4a\x40\xcd\x66\
+\xdf\x8e\xd7\xc2\x2e\x1a\xb7\x87\x46\x50\x7e\x43\x3c\xd6\x97\xd7\
+\x4e\xf9\xe4\xee\xec\xe6\xc5\xf7\xfd\x45\xaf\x74\x8b\xe9\xef\x36\
+\x3a\xdd\x3a\xe1\x3b\xae\x62\x9c\x34\x2b\x73\x00\x00\x06\x9a\x49\
+\x44\x41\x54\x78\x01\xb4\x97\x85\x77\xeb\x3e\x12\x85\x9b\x3e\x2a\
+\x33\xff\x76\xb7\x8f\x99\xf1\xc0\xf2\xee\xf5\x48\xb2\x93\x3a\xd4\
+\x30\x33\x33\xfc\xf9\x6b\xd9\x71\xe2\x57\x6e\xcf\xd9\x5b\x56\xf5\
+\x59\xd2\xcc\x9d\x89\x32\xf7\xff\xd3\xe1\xe1\x9d\xd1\x87\xfb\xef\
+\x3e\x1f\x3f\xbc\x0b\xb9\xbd\xf5\x7c\x01\x86\x16\xf6\xb6\x56\x6f\
+\x49\x6e\x2c\x7d\x96\xa4\x2a\x60\xe8\xf1\xe6\xfa\x4d\xc1\x47\xae\
+\x95\xe5\x03\x48\x69\xa4\x09\xd2\x60\x68\x7e\x79\x63\xfb\x5a\x70\
+\xd5\xb5\xf5\xfe\xc1\x02\xa4\x04\x23\x7f\xb4\x5f\x0d\x05\x88\xa9\
+\x26\xbf\xf7\x7e\xeb\xcd\xfa\xb9\x47\x2c\x6e\xbc\x79\xb8\xbe\xfe\
+\x70\x63\xe5\xf8\xf9\x5f\x2c\x50\x92\x11\x3d\x5e\x53\x0c\x65\xcb\
+\x19\x4e\x02\x96\xe6\x0f\xf6\x9e\xbf\xdf\x5c\xd9\x7a\xeb\x5a\x5f\
+\x7f\x64\xc2\xff\x06\x16\x16\x2c\x4a\x4a\xe5\x44\x7e\xbd\x92\x52\
+\x6c\x9d\xf4\xa2\x7e\x22\xae\x62\x2a\x09\xcc\x1f\x9b\xf0\xc6\x8c\
+\x13\x06\x28\xbc\x83\x42\x43\xf9\x5d\xb5\x72\xd4\x2b\x98\x9b\x18\
+\xd7\x54\x55\x85\xa9\x07\x26\x7c\x0c\x80\x73\x46\x6e\xe2\x7e\x4f\
+\x28\x5e\xca\x2b\x17\x29\x57\x1a\x25\x42\x45\x4f\x40\xd3\xe4\x4c\
+\x01\xbc\x5b\x94\xf0\x12\x00\x6f\x46\x1f\x24\xe2\xd5\xc6\xa9\x72\
+\xb5\x4e\x86\xa5\x6a\xa7\x9e\x18\xfb\x04\x7e\xec\x4a\x78\x19\x9a\
+\xaf\xa5\xdc\x4e\x03\x82\xfb\x9f\x32\xd8\xef\xc0\xbd\xc9\x5b\xc2\
+\x15\x37\xdc\x1f\xa5\xf9\x0f\xc0\x8a\x27\xb7\x84\xe3\x06\xbc\x63\
+\xc0\xcf\xbe\x80\x06\xca\x2d\x55\x26\x0b\xde\xfd\x01\x0a\x29\xe7\
+\x95\x6c\x04\x83\xfd\x51\x3f\xd8\xbc\x28\xfa\x23\xa6\x9a\xf0\xdf\
+\xd9\x05\x70\xaa\xd2\xf6\xfa\x39\x67\x8c\x73\x78\xc3\xf5\x92\x72\
+\x46\x41\xcd\x82\x7f\x9d\x87\xab\xed\x08\x31\x2e\xa4\x1f\x54\x55\
+\x70\xe6\x16\xc5\xf2\xef\xeb\x97\x84\xb8\x18\xce\x86\x84\x65\x66\
+\xa1\x71\x69\x29\x69\x3d\x46\x99\xe0\x6f\x8f\x17\xb3\x95\xd3\x8e\
+\xf1\x98\xcf\x2d\x51\x4e\xcc\xef\xf3\xe8\xde\x80\x46\x4c\xfe\xcd\
+\x44\xfc\xfa\x6d\xd7\x39\x97\x53\x99\x37\xdd\x6d\xb4\x92\x4a\xae\
+\xd5\xab\x17\xcd\x9d\x68\x54\x76\xc2\x17\x05\xac\x2b\xe7\x09\xca\
+\x14\x72\xb3\x99\xa7\xd5\xb6\xc6\x0d\x5a\x0b\x3a\xe1\xf3\xa9\x6a\
+\xf9\x38\x20\x44\xe5\xac\xe3\xfa\x72\x9c\xf4\xe9\x70\x97\xa9\xd2\
+\x61\x0e\x93\x58\xb6\x03\x58\x42\x39\xa7\x52\x44\x40\x20\xe5\x34\
+\xc9\x3f\x27\xf6\x0c\xdb\x83\x3a\x83\x63\x8e\x53\x61\x06\x50\xc7\
+\x69\xcf\x5d\x0b\xe6\x9e\xc9\x7e\x4e\x3c\x1c\x50\x79\xf0\x02\xb8\
+\xc8\x01\x36\x9a\x86\x95\x4c\xd8\xaa\xaa\xbc\x03\x06\x6b\x9f\x2f\
+\xb2\x02\x57\x21\x22\x35\xfb\xcf\x28\xc3\x97\x67\x93\x7a\x0e\x0c\
+\x9d\x30\x28\x3a\x3c\xe3\xf3\x78\x44\x93\xc3\xa7\x8e\xe3\xd9\x9d\
+\x44\x6e\xd4\x09\x83\x05\x12\x0d\x87\xe5\x3a\x45\xe9\x13\xee\x9f\
+\x8e\x9d\x14\xf9\xa4\x87\x6d\x02\x2c\xe6\x80\x85\xe9\x08\x7f\x38\
+\x51\xae\x36\x9b\xcd\x51\x3d\x1a\x60\x0c\x00\x43\x7f\xf6\xb8\x80\
+\xc0\x92\xdd\x3d\x69\x3c\x83\x55\xbf\x60\x2a\x20\x9b\xa5\x26\x84\
+\xf1\x83\x69\x00\x84\xdb\x1b\x1c\xd6\xed\xea\x48\x41\x60\xdf\x84\
+\x5d\x0b\xa0\xe8\x0c\xa6\x71\x3f\xc3\xc8\xaa\x07\x29\xf9\x53\xa3\
+\x48\x3a\xab\x44\xdd\xb1\x09\xdc\x61\x2a\x8e\x4c\x78\xf5\x2f\xb2\
+\x89\xcd\xe0\x84\x92\xec\x46\x03\xdc\x6d\x56\xa5\x10\x9c\x11\xf3\
+\xa5\x53\x06\xc1\xa9\x30\xcb\xd4\x82\x6b\xce\xd4\x73\x88\x48\x6a\
+\x0a\x73\x4f\x4b\x9e\x2a\x56\x19\x84\xbd\x7e\x1e\xf1\x67\xda\x89\
+\x98\x1c\x29\x44\xb8\x28\xcd\x1c\xf3\xf8\x91\x05\xbf\x00\x68\x34\
+\x85\xc1\x8a\xb1\x49\x9e\x73\xb5\x7e\x6a\x68\xfd\x5e\x1a\x70\x8d\
+\x7b\xf2\x76\x05\x68\x32\x5e\xa6\x8e\x00\x4a\x3b\x4d\xc2\x32\xe5\
+\x86\xe2\x50\x2e\xd6\x56\x49\x55\xa9\x30\xab\x29\xac\x4c\xe0\xf5\
+\x79\xf0\xcc\xc9\x0c\x96\x8d\xc3\xaf\x27\x62\x8d\x9c\xa1\x56\xb0\
+\x30\xf0\x32\x26\x20\xdc\xd3\xda\x4b\x10\xf0\x70\x02\x3f\xfa\x0e\
+\x15\xa9\x29\xcc\xad\x4c\xc9\x4e\x62\x28\xa0\x59\xaf\x90\x9c\x8d\
+\xf3\x29\xdb\x22\x0c\x8f\x4d\x7f\x49\xed\x03\x14\xb7\x61\xde\x8e\
+\x72\xbb\x87\x49\x99\xb9\xe2\xe4\x2b\x28\x65\xde\x34\xe1\x46\x44\
+\xe0\xfd\x9c\xad\xb7\x00\xd3\x6d\x98\x2a\x4a\xcf\xea\x9e\x30\xa5\
+\x6a\x9c\x98\x2f\x91\x55\xb2\x3e\xb7\x75\xe8\x38\xc1\xcc\xb2\xa5\
+\xc5\xbf\x40\xb5\xd2\x10\x25\xb0\xa8\x7c\x78\x3c\xec\x05\x63\x44\
+\x8c\xf1\x40\x26\x1d\xcb\x19\x51\xd3\x89\xf7\xec\x5d\x7f\x76\xdc\
+\x90\x5e\xd8\xf1\x8e\x91\xaa\x8a\xb8\x99\x91\x7c\xa9\x1f\x8b\x57\
+\x3a\xbd\xe0\xd0\x8c\x65\x2f\x43\x22\x90\xb5\x7a\xb6\x8a\x63\xe7\
+\x75\x6d\x61\xf2\x32\x7b\x12\x26\x08\xe6\xa9\x9c\x2d\xc9\x58\x98\
+\x33\xb8\xad\x7c\xa6\x09\xf8\xdb\x9c\x43\x7b\x80\x95\xc5\x5a\x80\
+\x01\x9c\x02\xd1\x4e\x2d\x69\x97\x50\x70\xec\x91\x19\x20\x6f\xd6\
+\x9c\xe1\x17\xf8\xee\x64\xa5\x4f\x98\xe5\x9f\x6a\x80\x20\x4b\x92\
+\x45\x32\xe1\x74\xbd\x32\x8e\xea\x01\x4d\xe6\xca\x60\x1b\xd3\x85\
+\xb7\x9c\xac\x4c\xb5\xcc\x96\x54\x4a\x27\x99\x68\x55\x56\x04\xb9\
+\x89\x31\x4d\xfe\xc5\x28\x6c\xf5\xa0\x66\x44\x58\x49\x76\x68\x03\
+\xd0\xfc\xd6\xbf\x93\x85\x0c\x27\x2e\xe0\xbc\x5c\xf1\x4c\xc1\x6a\
+\x41\xa7\x61\x7b\x61\xe7\xd2\xf2\xd4\x6d\x39\x41\xe2\xbd\x90\x57\
+\x25\x62\xa6\xe4\xe5\x2a\xd4\x4b\xce\x1a\x36\x9e\x3f\xb2\x29\x47\
+\xc0\x41\x75\xc5\x56\xbe\x59\x4e\x84\x75\x43\xe1\xb1\xf3\x72\x55\
+\x8d\x68\x98\x77\x39\xb0\x99\x47\x85\xd6\x55\xae\x54\xd6\xc7\x20\
+\xeb\xe9\x9c\x16\xf7\x00\x0d\xb1\xab\xd8\x96\x4e\xc0\xd2\xc5\x97\
+\xec\xc7\x00\xbf\x8a\xce\x16\x09\xd8\x73\x44\xda\xa9\xb7\xf3\x92\
+\xee\x5c\xc6\x36\x3d\x04\x3c\xbe\xf4\xde\xfe\xc6\xa0\x35\x36\x4e\
+\x5e\xc8\x76\xa5\xf7\x1e\x58\xc1\xba\x78\xed\x03\x40\x90\xde\xbc\
+\xe0\xb8\x69\xc6\x81\xef\x57\xde\xf7\x5d\x0f\x00\xb0\x48\x22\x7f\
+\x86\x1d\x79\x49\x00\xcb\xab\xd7\xbc\x51\x58\x32\x9d\xed\xe9\x9c\
+\x3a\xd0\x54\x5b\x2e\xbb\xb0\x39\x77\xad\x56\x0e\x00\x70\xa6\xc7\
+\x6c\xb4\x91\x8e\x90\x0a\x3c\x7e\x33\x77\x03\xb9\x96\xcd\x06\xca\
+\xf5\xae\x8c\x5c\x35\xe4\x97\x3b\x5e\x78\x71\xd3\x77\x57\x5b\x8f\
+\x4d\x9c\x79\x2a\xdd\xb0\x90\x28\x96\xff\xd7\x7e\x59\x2d\xba\x09\
+\x03\x60\xf8\x6f\x13\x48\x91\x21\xf5\x36\x49\xcf\x08\x7a\x04\x38\
+\xee\xde\xf6\x66\xf2\x40\xbc\xfd\x90\xb9\xcb\xed\x3e\x5c\xbe\x78\
+\x42\xd0\xf1\xdb\xbc\xf0\x57\x6d\x6f\xaa\xfb\x45\xab\xee\xe1\x8f\
+\x18\xcd\xba\x5f\xba\x3a\xc1\xd6\xee\x5f\xfc\x4c\xfa\x5a\x6d\xaf\
+\x62\x1d\x7f\x85\xb1\xdc\xf9\xf6\x27\xf4\x3f\x8b\x34\x4d\xd2\x84\
+\xe1\x0b\x98\xf1\xe9\xdc\x21\x0a\x58\xfa\x77\x1f\x6a\x61\xb5\xc4\
+\x47\xc2\x40\x54\x01\xc1\x97\x50\x81\x4f\xf2\x79\x0a\xb3\x7f\xba\
+\x8d\xd1\x62\xf8\x3d\x7c\x02\xea\x98\xe1\x5a\x5e\x3a\x19\x85\xcc\
+\x40\x2f\x45\xc6\x48\x45\x28\xbb\xae\x4f\x20\x05\x39\xa7\xfa\xeb\
+\x5d\xcc\x22\xd6\xb3\x34\x1f\x46\xac\x63\x66\x4f\x23\x17\x2d\xf4\
+\xd8\x61\xb7\xc7\xd7\xce\x89\x04\xc9\xf1\x14\x24\xc4\x91\xb7\xa9\
+\x73\x2d\xe8\x49\xaa\xaa\xac\x96\xed\x39\x6a\x8c\x97\x53\x7b\xe0\
+\x19\x5b\x0f\x5a\xdf\x3f\x5c\x7f\x92\x8f\x25\x90\xa7\x20\x02\x32\
+\x27\x0a\xc9\x09\x90\x0b\x92\x67\x99\x80\xf1\x41\x76\x07\x3d\x58\
+\x26\xeb\x2f\xa1\x1d\x20\x7e\xf8\x24\x9f\x24\xc0\x93\x44\x10\x40\
+\xa9\xac\xa2\xf4\x16\x10\x97\x4a\x52\x79\x02\xd5\x24\xfb\x0e\xf6\
+\xa9\x3b\xd8\xab\xb5\x56\x9e\x61\xe7\x83\x5c\x3a\xec\x26\x01\x48\
+\x19\x94\x41\xed\x93\x52\x39\xe5\x25\x4d\x6f\xc8\x4d\xe2\xdc\xe4\
+\x79\x99\xc2\x3c\xd3\xde\x9a\x30\x8f\xb4\x23\xdd\x78\xeb\x61\xff\
+\x93\xbc\xe0\x0c\x7c\x01\x18\x9c\x4f\x38\x16\x9b\xab\x09\xc0\xaf\
+\xc2\x7a\xe3\x40\x78\xc5\x79\x08\x14\xe3\x02\x60\xc5\x78\x02\xa3\
+\x08\xf1\x38\xc1\xe4\x1e\xff\xc6\x3b\xa3\xb2\x3c\x34\x38\x5c\x11\
+\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x07\x9e\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\
+\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x2e\x23\x00\x00\
+\x2e\x23\x01\x78\xa5\x3f\x76\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe1\x0c\x02\x0f\x19\x10\xf3\xde\xb9\x4c\x00\x00\x07\x2b\x49\x44\
+\x41\x54\x78\x5e\xed\x9d\x5b\x68\x1c\x55\x18\xc7\x7f\x9b\xc4\x34\
+\xbd\x44\xdb\x54\xab\x22\x9a\x15\xa9\x22\xde\xa0\x5b\x2b\x1a\x6d\
+\x46\x51\x04\xad\x17\x2a\x3e\xa8\x88\x51\xa8\xf1\xa1\x16\xb4\x20\
+\x5e\x43\x1e\x14\x7d\x28\x2a\x5e\x90\x06\x2f\x55\x34\x88\x60\x45\
+\x0b\x5e\x8b\x9b\xa8\xe0\xa5\xa9\xb6\xd5\x22\x44\xed\xa6\x15\xb7\
+\x68\x69\x63\x93\xb4\x35\x6d\xb3\x9f\x0f\x5f\x36\xcd\x6e\x66\xf6\
+\xcc\xec\xcc\xec\xce\xb6\xfb\x83\xef\xa5\x73\xe6\x9c\xcc\x7f\xbf\
+\xef\xdc\xcf\x29\x54\xf1\x45\xcc\x94\x20\x44\x1a\x80\x66\xe0\x22\
+\xe0\x02\xe0\x6c\xe0\x54\xe0\x78\x60\x1a\x70\x18\x18\x01\x76\x01\
+\xdb\x81\xad\xc0\x26\xe0\x57\x60\xaf\x4d\x7e\x65\xa1\x1c\x02\xce\
+\x01\x5a\x81\x25\x40\x0b\x2a\xe2\x34\xa0\xa6\xc0\x3b\x02\x1c\x02\
+\x76\x03\x3f\x00\xeb\x81\x4f\x80\x6d\xe3\xcf\x8e\x09\xa6\x03\xb7\
+\x00\xef\x03\x7b\x80\x0c\xfa\xf1\x5e\x2d\x03\x8c\x02\xdf\x01\x2b\
+\x81\xd3\x38\x06\x38\x07\x78\x1e\xf8\x87\xe2\x85\xb3\xb3\x03\xc0\
+\x87\xc0\x75\x40\x2d\x47\x29\xd7\x02\x9f\x02\x63\x98\x05\x29\xc6\
+\x32\xc0\x6f\xc0\xfd\x68\x55\x70\xd4\x10\x43\x43\xf6\x27\x82\xf5\
+\x3a\x27\xdb\x03\x74\x00\x33\x39\x4a\xb8\x19\x6d\x35\x4b\x21\x5e\
+\xd6\x86\x81\x47\x80\x7a\x2a\x9c\x45\xc0\x37\x94\x56\xbc\xac\xed\
+\x02\x96\x51\x9e\x1e\x46\x20\xcc\x05\xde\xa2\x3c\xe2\x65\x6d\x13\
+\x70\x39\x25\x20\xe8\x96\x2b\x06\xdc\x03\xac\xc0\x7b\x18\x1d\x04\
+\xfe\x05\x76\x8e\xdb\x20\xda\x99\xae\x01\xea\xf0\xe6\x51\x27\xa3\
+\xef\x25\xd1\x2e\x4f\x68\xd4\x99\x12\x78\xe4\x2c\xe0\x76\xb4\xcf\
+\xe7\x06\x01\x86\x80\xaf\x81\x2f\x80\x2d\xa8\x78\xa3\xe8\x8f\x3b\
+\x17\x1d\xa1\x5c\x09\x5c\x85\x76\xba\xdd\x08\x19\x43\xeb\xe0\xcf\
+\x80\x77\x0d\x69\x23\xc5\x0a\xf4\xe3\x4d\x21\x26\x68\x88\x7f\x09\
+\xdc\x01\x34\xd9\x65\x36\x89\x69\xc0\x15\xc0\x6b\xc0\x7e\xcc\x79\
+\x67\xf3\x7f\x0f\x1d\x1a\x56\x04\x27\x01\x9f\xe3\xae\xee\x1b\x03\
+\xde\x41\xc7\xc0\x5e\x98\x03\x3c\x86\x86\xba\xa9\x0c\x01\xd2\xc0\
+\x62\xdb\x9c\x22\xc8\x62\x74\xa4\x61\xfa\xa8\x0c\xf0\x11\x70\xae\
+\x7d\x36\x46\xa6\x03\x4f\xa1\x75\xa6\xa9\xac\xc3\xc0\xe3\x14\x1e\
+\x67\x47\x82\x18\xf0\x00\x3a\xe0\x37\x7d\xd4\x5f\xc0\xf5\xf6\xd9\
+\xb8\xe6\x74\x74\x74\x63\xf2\xf6\x0c\xf0\x01\x30\xcb\x3e\x1b\xff\
+\x04\xf5\xcb\xd4\x01\xe7\xe3\x2e\xbf\xf5\x68\xeb\xe8\x87\x3f\xd1\
+\x49\x09\x53\x0b\x1b\x03\xe6\xa3\xd5\x4b\x28\xb8\xf9\x60\x37\x4c\
+\x47\xbd\xc2\xd4\x42\x1e\x40\xc5\xdb\x6f\x48\xe7\x86\xaf\x80\x1d\
+\xa6\x44\xc0\x3c\xe0\x44\x53\xa2\x62\x09\x4a\xc0\x06\xb4\xb5\x33\
+\x09\x38\x8c\x4e\x88\x06\xc1\xdf\x68\x75\x60\xa2\x01\x68\x34\x25\
+\x2a\x96\xa0\x04\xac\xc7\xdd\x4c\xc8\x21\xb4\xdf\x17\x04\x07\xd1\
+\x19\x6b\x13\x35\xa8\x88\xa1\x10\x94\x80\x31\xcc\xde\x07\x47\x2a\
+\xf6\xa0\x70\x9b\x57\xd0\x23\xae\x09\x82\x12\xf0\x98\xa5\x2a\xa0\
+\x4f\xaa\x02\xfa\xc4\xcb\x64\x42\xa1\x7a\xc4\x4b\x1d\x53\x87\xb7\
+\xf4\x4e\xd4\xe2\xae\xde\x0d\x15\x2f\x02\xae\xc4\x79\xd0\xdf\x88\
+\xae\xe9\x9a\x38\x01\x9d\x70\x18\x34\x25\x74\x41\x1d\xee\x86\x83\
+\xb5\xe8\xba\xcc\x3c\x87\xe7\x19\xb4\x4f\xf9\x87\xc3\xf3\xc0\xd8\
+\x86\x16\xe6\x64\xa6\x21\x5c\xb6\x05\x0e\xda\x4c\x65\x0a\xda\xe5\
+\x19\x75\xb0\xbd\xe8\x8c\x50\x51\x78\xf1\x40\xb7\x5d\x95\x42\xf8\
+\x7d\xbf\x58\x8e\x2b\xf0\x2c\x83\x8f\xb6\xa0\xe8\x17\xab\x28\x55\
+\x01\x7d\x52\x15\xd0\x27\x55\x01\x7d\x52\x15\xd0\x27\x55\x01\x7d\
+\xe2\xa5\x1b\xe3\x05\xb7\xb3\x24\x61\x12\x44\xb7\xcb\x48\x18\x02\
+\xee\x07\xd6\xa2\x2b\x62\xe5\xa2\x01\x5d\x17\x3e\xc3\x94\xd0\x2f\
+\x61\x08\x38\x02\xac\x06\xbe\x35\x25\x0c\x91\x26\x74\x8d\xa6\x22\
+\x05\x04\x5d\xf7\x1d\x33\x25\x0a\x91\x92\x95\x5d\x6d\x44\x7c\x52\
+\x15\xd0\x27\x61\x85\x70\xd9\xb1\x2c\xab\x31\x91\x48\x60\x59\x16\
+\xad\xad\xad\x34\x36\xe6\x2e\xcc\xf5\xf5\xf5\xd1\xdb\xdb\x4b\x6f\
+\x6f\x6f\xcd\xe6\xcd\x9b\x1b\x76\xec\x70\xb3\x42\xea\x8f\x14\xe6\
+\x69\x23\x41\x97\x1b\x2f\x75\xc8\x23\x54\x44\x24\x21\x22\xdd\x63\
+\x63\x63\xc3\xe2\x9d\x0d\x22\x72\x9b\x88\x84\xb6\x8b\x21\xb2\x02\
+\xca\xb8\x70\x22\x32\x54\x50\x22\x77\x84\x26\x64\x24\x05\x14\x91\
+\x76\x11\x49\x17\xd6\xa4\x28\x56\x8b\x88\x71\x96\xbd\x62\xeb\x40\
+\x11\x49\xa0\xcb\x0c\x4b\x70\xb3\xf3\x60\x60\x40\x0d\x20\x1e\x57\
+\x2b\xcc\xbd\xc0\x02\x11\x79\x16\x58\x17\x8b\xc5\xdc\x2c\xe2\x17\
+\x24\x32\x1e\x28\x1a\xb2\xfd\x05\xfd\x27\x99\x14\x69\x6b\x13\x89\
+\xc7\x45\xc0\xde\xe2\x71\x4d\x93\x4c\x16\xcc\x4a\x44\x96\x49\x00\
+\x21\x1d\x09\x01\xc5\x24\x5e\x32\x29\x62\x59\xce\xa2\x39\x99\x65\
+\x99\x84\xf4\x2d\x62\x54\x04\xec\x12\xbb\xc6\x22\x95\x12\xe9\xec\
+\x2c\xec\x71\x26\x8b\xc7\x35\x8f\x54\x6a\x4a\xf6\xa2\x8d\xcb\x7c\
+\xd3\xdf\x57\x88\xb2\x0b\x28\x4e\x0d\x46\xb1\x5e\xe7\x64\xce\xde\
+\xe8\xaa\x61\x71\xa2\xac\x02\x8a\x53\xe8\x06\x2d\x9e\x59\xc4\xa2\
+\x43\xb9\xdc\x02\x4e\x0d\xdd\x54\x4a\x1b\x01\x93\x18\xc5\x5a\x5b\
+\x9b\x5d\x38\xe7\x84\x72\x45\x8c\x85\x45\xbb\x2c\x16\xf9\xdd\x95\
+\xde\x5e\xe8\xe9\xb1\x79\x23\x20\x7a\x7a\xb4\x8c\x5c\x16\x02\x56\
+\x31\x5e\x58\x36\x0f\xb4\xf5\xbe\xb0\x42\x37\xdf\xec\x43\x79\xc2\
+\x0b\x2b\xc2\x03\x81\x04\xa5\xf6\xbe\x2c\xce\x5e\xd8\x08\x15\x20\
+\xe0\x78\xf8\xe6\x8a\xd7\xd3\x53\x1a\xf1\xb2\xd8\x97\x97\x10\x91\
+\x59\x91\x17\x10\xfd\xb5\x73\xeb\x9b\xed\xdb\x8f\x0c\xcb\x4a\xc1\
+\xc0\x80\x96\x99\xcb\x42\xa0\xb1\x12\x04\x4c\x90\x2f\xe0\xe4\x71\
+\x6d\x29\xb0\x2f\x6f\x01\x50\x11\x1e\x38\x35\x84\xa3\x41\xc5\x78\
+\x60\x2e\xa5\xf6\xbe\x2c\x0e\xe5\x56\x9e\x80\x11\x23\x2c\x01\x43\
+\x3b\xd8\x12\x35\xc2\x10\xb0\x09\xb8\x0f\xbd\xf2\xc4\x74\x90\xda\
+\x3b\xee\x26\x43\x83\xc7\xa1\xdc\x30\x04\xac\x03\x6e\x05\x5e\x05\
+\xde\x00\xee\xc6\xdf\xf5\x4c\x1b\xd1\x33\x76\x51\xa3\x0f\x18\x0e\
+\x43\x40\xd0\x4d\x3d\xb3\x81\x1b\x80\x97\x81\xb7\x81\xe5\xe8\x9d\
+\x0a\x5e\xcb\xdc\x48\xfe\x99\xb8\x52\x7b\xa1\x7d\x79\x3f\x02\x23\
+\x5e\x3f\xc6\x2b\x31\xf4\x28\xac\x05\xac\x02\xba\x81\x47\x81\x0b\
+\x71\x7f\x56\xa4\x8f\x7c\x01\x9b\x9b\x4b\x2f\x60\x73\x73\xfe\xbf\
+\xf6\xe1\x31\x32\x52\x38\x4f\x20\xb8\xb5\x0c\x7a\x62\xf3\x17\xe0\
+\x69\xe0\x32\x5c\x9c\xf2\x14\x91\x8d\xf9\xa3\x79\xe9\xec\x34\x4f\
+\x04\x04\x65\x9d\x9d\x53\x8a\x17\x91\x05\xe0\x3d\x9c\xfc\x12\x43\
+\xeb\xc8\xf3\x80\x87\xd0\x8b\x27\x9e\x03\xae\x06\x66\x38\xbc\x53\
+\x9b\x4e\xa7\xb7\x8d\x8e\x8e\xe6\x9e\x4e\x6f\x6d\x05\xcb\xb2\x7f\
+\x23\x48\x2c\x4b\xcb\xca\xc5\xb3\xf7\x41\x78\x97\x88\x65\xd0\xbd\
+\x84\x6f\x02\x4b\xd1\x9b\x39\x40\xcf\x20\x2f\x02\x9e\x6c\x69\x69\
+\xe9\xef\xef\xb7\x59\x47\x5a\xb3\xc6\xdf\x1a\x88\xc9\xe2\x71\x2d\
+\x63\x2a\x45\xcd\x4a\xdf\x84\x7e\x64\x9a\x70\xae\xb2\xcb\xa0\x37\
+\xb0\xad\x05\x1e\x04\x5e\x42\x8f\x5f\x1d\x06\xa4\xab\xab\x4b\x86\
+\x86\xf2\xd6\x92\x22\x30\x23\xed\x95\x99\xe8\x0d\x42\x2f\x00\xbf\
+\x13\x8e\x90\x82\xee\x72\xcd\xf1\xf6\x44\x22\x21\xb6\x5e\x18\xd6\
+\xc4\x6a\x08\x6b\x22\x93\xa9\x07\x2e\x41\x1b\x82\x2d\xb8\xbb\xc3\
+\xc5\xb7\xb5\xb7\xb7\x4b\x3a\x6d\xb3\x8b\x23\x68\x11\x43\x5a\x95\
+\xb3\xa3\x16\x6d\x10\x1e\x46\xb7\xf4\x1e\x20\x9c\x7a\x72\xc2\x6c\
+\x43\x59\xa4\x22\xd6\x85\x0b\x51\x03\x9c\x89\x0e\xe1\x3e\x46\x5b\
+\x28\xa3\x18\xc5\x98\x63\x28\x67\x29\xd6\x1b\x4b\xb0\x33\xc1\x2d\
+\xf3\x80\x3b\xd1\x8b\x71\x76\x13\x82\x47\x1a\x45\x14\x89\xe4\xde\
+\x18\xaf\xcc\x06\x6e\x04\x5e\x47\x5b\xee\x40\x85\x4c\x24\x12\xd2\
+\xdd\xdd\x6d\x1f\xce\x76\xa4\x52\x2a\x54\x32\xe9\x14\xa2\x76\x84\
+\xb6\x57\xd0\x0b\x33\xd0\x8e\xf2\x8b\x4c\xea\x96\x04\x60\x19\x60\
+\xb0\xa3\xa3\x63\xd3\xbe\x7d\xfb\x06\x4d\x4a\x14\x41\xe0\x0d\x86\
+\x5f\xea\x81\x8b\xd1\x9b\xd7\x7e\xc6\xdd\x45\x65\x4e\xc2\xed\x44\
+\xaf\x19\x5d\x0a\x34\x49\x85\xec\x50\x0d\x8a\xec\x45\x65\xd9\x96\
+\xfb\x3f\xcc\xa2\x09\xea\xb9\x29\xe0\x15\xe0\x1a\x6c\xae\x3b\x16\
+\x7f\x42\x46\x5e\xb8\x7c\x62\x68\xcb\xbd\x1c\xbd\xd1\x6d\x84\xa9\
+\xf5\x64\x06\x15\x6e\x2b\xf0\x0c\xba\xe3\xc1\x38\xf9\x00\x13\x62\
+\xae\x14\x91\x75\x62\x2f\xe8\x06\x11\x59\x25\x22\x4b\x2a\x49\x34\
+\x3b\x62\xc0\x29\xc0\x5d\xe8\x30\x6e\x10\x15\x6e\x14\xf8\x1e\x78\
+\x02\xfd\x1f\x1f\x22\xb9\x1d\xf9\x7f\xa1\x2d\xb6\xce\xcc\x96\xf3\
+\x8e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x04\xf4\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\
+\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\
+\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\
+\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x01\x98\
+\x50\x4c\x54\x45\xff\xff\xff\x00\x00\x00\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\
+\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\
+\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\
+\xdd\xdb\xcf\xdd\xdb\xcf\xdd\xdb\xcf\xff\xff\xff\x5e\xd8\xde\x8c\
+\x00\x00\x00\x86\x74\x52\x4e\x53\x00\x00\x2c\xb0\xf5\xfa\xd6\x87\
+\x23\x2e\xf1\xed\x92\x86\xb5\xfb\x8b\x0b\xc5\xe8\x1c\x1b\xa1\xdc\
+\x25\x75\x72\xfe\x5a\x3c\xe4\x0c\x28\xee\x7b\x04\x11\xe0\xef\x5b\
+\x45\x68\x33\x67\xa2\xd2\xfd\x99\xea\xd1\xae\x53\x40\x3b\x02\x6e\
+\xec\xad\x29\x17\xc0\x01\x0f\x3a\xeb\x9f\xa5\xf9\x07\xcd\xab\xe6\
+\x36\x7c\x54\xc8\xdd\xb1\x0d\xf7\x7a\x7e\xda\x20\x0e\xf8\x4f\x5d\
+\x10\x08\xcc\x62\xac\x34\x15\xc2\xbd\x9e\xa6\x26\x74\xf0\xaa\x1a\
+\xf2\x9b\xd3\x51\x46\x6d\x32\x60\xa0\xcf\x7f\xdf\x44\x73\x2a\x2d\
+\xf6\xe1\x70\xc6\x19\x9a\xde\x82\xb3\xfc\x91\xdb\x90\x24\xe7\xc8\
+\x3e\x7d\x00\x00\x00\x01\x62\x4b\x47\x44\x00\x88\x05\x1d\x48\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
+\x00\x9a\x9c\x18\x00\x00\x02\x37\x49\x44\x41\x54\x38\xcb\xa5\x53\
+\x57\x5b\xea\x40\x10\xcd\x22\x11\x1b\x22\x0a\x06\xc1\x80\x14\x41\
+\xc5\x0a\x82\x88\x11\x51\xec\x8a\x62\xc7\x5e\xb1\xf7\xde\xbb\xe7\
+\x77\xbb\x21\x51\xc2\xe7\xc3\x7d\xb8\xe7\x61\xe7\xec\xcc\xf9\x26\
+\x9b\x29\x0c\xf3\x2f\x10\x09\xaa\x1c\x35\x9b\xab\xc9\x13\x69\x9e\
+\x26\x97\x55\xe7\xa8\xe4\x80\x2c\xc8\x2f\x28\x2c\xd2\x16\xeb\xd8\
+\x12\xbd\xbe\x84\xd5\x15\x6b\x8b\x0a\x0b\xf2\xb3\x04\xa5\x65\x06\
+\x7a\x1a\xcb\xc1\x71\x28\x37\x52\x6a\x28\x2b\x55\x0a\x4c\xa8\x90\
+\xae\x66\x8b\xc5\x2c\xb1\x0a\x98\x14\x82\x4a\x48\x09\x79\xb5\xd5\
+\xaa\xe6\xa5\x8f\xa2\x52\x21\x30\xc3\x26\x9a\x2a\x3b\x67\x30\x70\
+\xf6\x2a\x91\xdb\x60\x56\x08\x1c\x70\xd2\xd3\x55\xed\xf6\xd4\xd4\
+\x78\xdc\xd5\x2e\x7a\x71\xc2\xa1\x10\xd4\xa2\xce\x5b\xdf\xd0\x08\
+\x19\x8d\x0d\xf5\xde\x3a\xd4\x66\x04\xb6\x26\xea\x6d\x6e\x81\xcf\
+\xdf\x1a\x08\xb4\xfa\x7d\x68\x69\xa6\x8e\x26\x9b\x2c\x08\xb6\x85\
+\x80\xf6\x70\x87\x5b\xe8\x94\x7e\xa0\x53\x70\x77\x84\xdb\x81\x50\
+\x5b\x50\x14\x44\x04\x08\x5d\xd1\x6e\xd2\x13\x0b\xcb\xc5\x23\xe1\
+\x58\x0f\xe9\x8e\x76\xd1\x40\x84\x30\xbd\x7d\xac\x97\x90\xfe\x81\
+\x41\x68\xc8\x2f\xb4\x18\x1c\xe8\x27\xc4\xcb\xf6\xf5\x32\x43\x18\
+\xa6\x1e\x0f\x46\x42\x91\x8c\x20\x12\x1a\x81\x87\xda\x61\x0c\x31\
+\xa3\x71\xd1\x33\x36\x8e\x04\x51\x20\x81\xf1\x31\xd1\xc6\x47\x19\
+\x4c\xa4\x3d\x93\x98\x52\x0a\xa6\x30\x99\xb6\x13\x90\x33\x4c\xcf\
+\xc8\x75\x91\xe1\xc0\xcc\xb4\x9c\x21\x81\x59\x4a\xe6\xe8\x1b\x92\
+\x99\x78\x92\xbe\x61\x8e\xda\x59\x24\x98\xf9\x05\x76\x91\x90\x25\
+\xeb\x32\xb4\xca\xbf\x58\xb6\x2e\x11\xb2\xc8\x2e\xcc\x33\x64\x65\
+\x15\x6b\xbc\x7d\x9d\x6c\xc4\x36\x7f\xe2\x9b\xb1\x0d\xb2\x6e\xe7\
+\xd7\xb0\xba\x22\x56\xd2\xb9\x95\x02\xb6\x55\x3b\xbb\x42\x50\x8a\
+\x07\x85\xdd\x1d\xd5\x36\x90\xda\x72\xca\xbd\x48\xee\xd1\xd2\x47\
+\xf7\xe1\x3b\x38\xe4\xf9\xc3\x03\x1f\xf6\xa3\xd4\xb1\x97\xcc\x74\
+\xf3\x08\xc7\x27\xa7\x67\xba\x9f\x6e\xea\xce\x4e\x4f\x8e\x71\xa4\
+\x68\xf7\x39\xc4\xd3\x75\x11\xf2\x5f\x5e\xfa\x43\x17\xe2\x3c\x10\
+\x9c\x2b\x04\x57\xd2\x44\x5d\xa7\x38\xa3\x91\x4b\x5d\x4b\x13\x75\
+\x95\x35\x93\x37\x69\x1b\xb8\xbd\xbb\xbb\x0d\xa4\xe9\x4d\xd6\x4c\
+\x9a\x64\x39\xb9\xb7\x58\xee\x89\x9c\x54\x39\xd5\xe4\x21\xbd\x17\
+\x8f\x4f\x96\xe7\x67\xcb\xd3\x63\x7a\x2f\x1e\xfe\x6c\xd6\xcb\x2b\
+\xfb\xf6\xae\xd7\xbf\xbf\xb1\xaf\x2f\x7f\x36\x8b\xa8\xe2\x6a\x7c\
+\x7c\x7e\x89\xf4\xeb\xf3\x03\xea\xf8\xef\x6e\xfe\x37\xbe\x01\xff\
+\x79\x87\x6d\x8b\x61\x54\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x28\x52\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x00\x48\x00\x00\
+\x00\x48\x00\x46\xc9\x6b\x3e\x00\x00\x00\x09\x76\x70\x41\x67\x00\
+\x00\x00\x80\x00\x00\x00\x80\x00\x30\xe1\x31\x9a\x00\x00\x27\x7b\
+\x49\x44\x41\x54\x78\xda\xed\x9d\x79\x9c\x5d\x55\x95\xef\xbf\x67\
+\xba\xf7\x9e\x3b\xdf\x5b\x75\x6b\xae\xa4\x92\xca\x40\x26\x48\x42\
+\x48\x40\x06\xc9\x53\x1c\xd0\xf6\xbd\x6e\xf5\xa3\xa1\xb5\x45\x6c\
+\xf5\xd1\x74\x7f\x9e\xdd\xad\x4f\x51\xbb\x1f\x8e\xb4\xb6\x8d\x43\
+\xab\xad\xd2\x4d\x23\x22\xd1\x16\x15\x71\x40\x51\x90\xc6\x20\x06\
+\x08\x84\x24\x86\x0c\x90\x2a\x52\xf3\x78\xe7\xf1\xdc\x73\xce\xfb\
+\x63\xdf\xb1\x86\x10\x20\xa9\x4a\x55\xf2\xfb\x7c\xce\x27\xa9\x33\
+\xdf\xb3\x7f\x7b\xad\xb5\xd7\x5a\x7b\x6d\xc9\xb6\x6d\x4e\x27\x24\
+\x49\x3a\xe5\xb7\x04\x14\x40\x7d\xf7\x27\x82\xdb\x55\x55\x0a\xde\
+\xfa\xb1\xe8\x4f\x00\x03\x28\x02\xa7\xf7\x07\xcd\x01\x4e\x77\x9b\
+\xd4\x42\x9d\xef\x1f\xfb\x22\xa1\xbc\xed\x83\x9e\x15\xad\x4b\x7d\
+\xff\xa8\x39\xa4\x37\xca\xb2\xe4\x1f\x3a\x9e\xfe\x14\xf0\x00\x90\
+\x02\x2c\xc0\x44\x90\xa4\xbc\x95\x61\xd7\x6c\xe7\x50\xc2\x42\x22\
+\x80\x7a\xc3\x17\x22\xef\x71\xfb\xd4\xcf\x2f\xe9\x0e\xf8\xb3\x29\
+\x83\x9e\x23\xf1\xbd\x77\x7c\x2a\xfe\x73\x20\x50\xf3\x7b\xec\x77\
+\x7e\xdc\x7f\x45\xd1\xb0\x64\x00\xcb\xb4\xa5\x74\xd2\x88\xff\xec\
+\x9b\x85\x7d\x08\x72\xd8\x08\xa2\xcc\xb4\x9d\x75\xe4\x90\x16\x88\
+\x0a\x50\x6e\xf8\x42\xe4\x2f\x43\x8d\xae\x6f\x5c\x78\x79\x1b\x00\
+\x7f\x78\xb0\x8f\xc7\xff\x7b\xe8\xdb\xfe\xa0\x53\x6d\xef\xf2\xad\
+\x71\xe9\x4a\xb3\xea\x90\xdb\x01\xfc\x41\x27\x9a\x26\x57\x2e\x4e\
+\xc4\x0a\x18\x86\x09\x80\x65\xd9\x89\x42\xde\x7a\x26\x97\x2e\xfe\
+\x31\x9d\x34\x0e\x8c\x0e\xa6\x9f\xbe\xff\x76\xf3\x29\x84\x0a\x31\
+\x10\x24\x29\x13\x65\x5e\x30\x97\x2a\x60\x21\x10\x40\x7a\xdb\x07\
+\x3d\xab\x96\xac\xf4\x3f\x76\xc5\xeb\xbb\xfc\xba\x57\x65\xd7\x2f\
+\xfb\xc8\xa6\x0b\xf8\x83\x4e\x9a\x3b\x7c\xf8\x83\x0e\x74\x8f\x86\
+\x3f\xe4\x7c\xc1\x9b\x25\xa2\x79\x8a\x86\xc5\xc4\x68\x96\x44\x2c\
+\xc7\xe4\x48\x96\x4c\xca\x18\x4c\xc6\xf3\xf7\x1f\xdd\x1f\xdd\xf9\
+\xd0\xf7\x78\x02\xc8\x03\x05\x04\x11\xac\xd3\xfa\x81\x66\xc0\x39\
+\x1b\xa0\x0a\x09\x50\xc3\x11\xfd\xcd\x4b\xba\x03\x7e\x7f\xc8\x49\
+\x36\x55\xa0\xa1\xc9\xc5\x9a\xcd\x9d\x68\x9a\x32\xe3\x45\x86\x61\
+\x62\xe4\x4c\xf2\x39\x0b\xcb\xb4\x31\x0a\xe6\xb4\x73\x7c\x7e\x07\
+\xfe\xa0\x93\x65\xab\x42\xe4\xb2\xc5\xb6\xfe\x9e\xc4\xb5\xcd\xed\
+\xde\x6b\xd7\x6f\xcd\x3d\x71\x64\xdf\xe4\xbf\xdc\x7f\xbb\xf9\x1b\
+\x20\x43\x95\x08\x8b\x52\x3d\x9c\xc9\x12\x40\xde\xbe\x43\x8a\xac\
+\xd9\xd4\x70\x93\xcb\xad\xfe\xef\xb5\x17\x36\xd1\xb5\x3a\x30\x6b\
+\xa3\x9b\x45\x8b\x74\xd2\x20\x97\x2d\x12\x9b\xc8\x33\xdc\x9f\x44\
+\x91\x25\x42\x11\xbd\xee\x1d\x42\x0d\xae\x59\x1f\x68\x9a\x36\xc3\
+\x7d\x49\xc6\x86\x33\x0c\xf5\x25\xbf\x7b\xfb\x4d\xf1\x1b\x81\x04\
+\x82\x08\x73\x36\xc2\x38\xdb\x55\x80\x04\x28\xef\xbf\xb9\xe1\x2d\
+\x7e\x7f\xe0\x9b\x2b\x96\x6f\xf0\xaf\xdf\x12\xc6\x74\x1d\xc7\x66\
+\x7a\x4f\xce\x65\x8b\xf4\x3f\x97\xe0\xf9\xe7\x12\x64\x52\x05\x32\
+\x29\x03\xdd\xa3\xb2\x62\x6d\x18\xcd\xa1\x70\x78\xdf\x38\xd9\xac\
+\x91\x9d\x18\xce\xf7\xab\x9a\xa4\xfb\x43\x8e\x0e\xcd\xa1\xe0\x76\
+\x6b\x44\xda\xdc\x34\xb5\xb8\x51\xa7\x90\x2a\x9b\x29\x72\x78\xff\
+\x04\xc9\x78\xe1\xe8\x57\x3f\x36\xf2\x76\x32\xf4\x22\x46\x19\x06\
+\x73\x40\x82\xb3\x99\x00\x12\xa0\x5e\xff\xf9\xc6\x2f\x2b\x9a\x72\
+\xfd\xeb\x5e\x77\x15\xcb\x96\xb7\x03\x90\xb1\x06\xc8\x14\xfb\x2b\
+\x27\x4e\x8c\xa4\x39\x7a\x20\xc6\xf3\xcf\xc6\xc8\xa6\x8d\xbe\xe3\
+\xcf\x26\x9e\x3c\xf2\xb4\xb5\xbf\x6f\x1f\x07\x3e\x7c\x6b\xdb\xf7\
+\xae\x7a\x73\x37\x9a\xa6\xd0\xdf\x13\x67\xdf\xee\x11\xbe\x78\xe3\
+\xe0\xa7\x0b\xe3\x3c\x03\x64\x43\xcb\x58\xba\xe9\x12\xe9\xfc\x86\
+\x56\xf7\x86\x48\xab\x7b\x4b\x63\x8b\x87\xe5\xab\x83\xe8\xee\x7a\
+\x8d\x78\xe8\xe9\x09\xfa\x7b\x12\x7b\x6f\xfd\xd8\xc4\xdb\x80\x71\
+\x20\xc9\x1c\x48\x82\xb3\x95\x00\xd2\xf6\x1d\x52\xd3\x86\xad\x4d\
+\xbf\xd6\x5c\xf2\x86\xed\x7f\xd2\x45\x5b\xc3\x1a\x9c\x72\x04\x80\
+\x94\xf9\x1c\x39\x73\x9c\xc9\xd1\x2c\x47\xf7\x8f\xd3\xf3\x6c\x3c\
+\x7b\x78\xef\xe4\xaf\x1f\xdc\x69\x3d\x88\xc9\xf3\x40\x9a\x92\xa8\
+\xbe\xf6\xa6\xc0\x4d\xe7\x6f\x6b\x7e\xdd\xf9\xdb\x9a\x2b\x24\x78\
+\xf2\x77\x43\x99\xdb\xff\x65\xf8\xa3\x13\xc7\xd8\x53\x3a\x17\xc0\
+\xa9\x85\xe8\x7e\xe3\x9f\xbb\xde\xd6\xda\xe5\xfb\x93\x96\x4e\x2f\
+\xdd\xab\x83\x15\x89\x50\x34\x4c\x9e\x7c\x74\x98\x63\x87\xa2\xb7\
+\xff\xf8\xcb\xb9\x4f\x03\x63\xa5\x6b\xcd\x93\xfd\x51\x2f\x05\x67\
+\x23\x01\xa4\xed\x3b\xa4\xa6\xb5\x17\x35\xfe\xa6\xa1\xc9\xb3\x7e\
+\xdb\xf6\x36\xbc\x7e\x07\x00\x2e\xa5\x11\xd3\x2a\x90\x29\x44\x79\
+\xe6\xc9\x31\x7a\x0e\xc5\xb2\xfb\x1f\x1b\xfd\xcd\xaf\xef\xb0\xee\
+\x01\x86\x11\x3a\x3a\x81\x68\x98\x1c\x60\xe3\xa6\xe5\xbd\x1f\x6f\
+\xf8\x52\x73\x9b\xfb\xf2\xae\xd5\x21\xba\x56\x07\x38\xba\x7f\x92\
+\x03\x8f\x8f\xf6\x7c\xed\x83\x63\xef\x03\x7a\x10\x64\x91\x00\x17\
+\xe0\x75\x45\x58\xfd\xf6\xeb\x83\xff\xd8\xd0\xaa\xaf\xdf\x7c\x49\
+\x4b\x85\x04\xd1\x89\x1c\x4f\x3d\x3a\x9c\xfe\xe6\x87\xc7\xff\x27\
+\xf0\x0c\x30\x89\x18\x25\x9c\xb6\x0f\x77\xb6\x11\xa0\xae\xf1\xb7\
+\x5c\xde\x42\x20\xec\xc2\x30\x4c\x6c\x13\x14\x55\x22\x3e\x99\xe7\
+\xe9\xdd\x23\x3c\x7b\x60\x72\xef\x9d\x9f\x4d\x7c\x1d\x18\x04\xa2\
+\x40\x8c\xaa\x91\x56\x40\xe8\x68\x10\x8d\xda\x78\xd9\x5b\x78\xeb\
+\x05\x17\x47\xde\xe7\x0d\x68\xcb\x3a\x96\xf9\x19\xe9\x4f\xf3\xdc\
+\xa1\xe8\xdd\x3b\xff\x29\xf5\x71\x84\x48\xcf\x52\x43\x02\x20\xb8\
+\xe3\xc3\xde\xcf\xb7\x75\xf9\x5e\xb3\xf5\x8a\xf6\xca\x0b\x3e\xf1\
+\xfb\x61\x0e\x3c\x3a\xf2\xcd\xfb\x6e\x33\xbe\x08\x0c\x71\x9a\xa5\
+\xc0\xd9\x44\x00\xe9\x35\xd7\x2a\x91\x95\xe7\x87\x1f\x68\x68\xf2\
+\xac\xdf\xb8\xad\x99\x70\xb3\x13\x49\xaa\x3a\x71\xca\x3a\xfc\xfe\
+\xbb\x07\xff\xf3\xa9\x5f\xf1\x73\x60\x02\xd1\x78\x31\x84\x61\x96\
+\xa7\xea\xc0\xb1\x29\xd9\x11\x80\x1b\x08\x02\xe1\xd6\xb5\x5c\xb4\
+\xf5\x4a\xe7\x95\x1d\xcb\xfd\x17\x7b\x03\xda\xb2\x27\x1e\x1e\xfe\
+\xdb\x07\xbe\x63\xed\x04\xe2\x08\xe2\xc8\x80\x03\xf0\x00\x0d\xef\
+\xfe\x44\x70\xe7\xaa\x0d\x0d\x1b\x97\xaf\x0e\x01\x30\xd8\x97\xe4\
+\xe9\x3f\x0c\x3f\x76\xfb\x4d\xf1\x1b\x80\x3e\x84\x14\x30\x38\x4d\
+\x38\x5b\xfc\x00\x12\xa0\xae\xbc\x20\xfc\xa3\x40\xc0\xb5\x7e\xfd\
+\x85\x11\xfc\x0d\x8e\xba\xc6\x3f\x7a\x60\x92\x83\x7b\x46\xb3\x5f\
+\xff\xc4\xf0\xe7\x72\x63\x3c\x05\x8c\x20\x1a\x3f\x8a\xe8\xbd\x05\
+\xa6\xbb\x70\x6d\x84\xa1\x56\x1e\xba\x65\x87\x0e\x12\xfb\xc9\xc1\
+\xfc\xa3\x30\xa6\x03\xac\xdf\x4e\x27\xa2\xd1\xcb\xec\x2c\x22\x08\
+\x54\x04\xcc\xe1\xe3\xe9\xff\x68\x68\x76\xff\x6b\x99\x00\x3e\xbf\
+\x13\x7f\xd0\xb5\x1c\xe2\x1e\x04\x51\x14\x4e\x23\x01\xe6\x12\xf2\
+\xcb\xbf\xc5\x4b\x86\xf2\x57\xff\x1c\xf9\xa4\xcb\xa5\x5e\xba\x6e\
+\x73\x23\xbe\x80\x56\x37\xc6\x2f\x37\xfe\x37\x3e\x3d\x7c\x73\x6e\
+\x8c\xc7\x81\x7e\x60\x00\x18\x45\x58\xe3\x39\x66\x77\xd0\xd8\x88\
+\x06\xca\x22\xc8\x32\x5a\xba\xb6\x0f\x18\x3c\xf0\x5b\x7e\x5f\x3a\
+\x66\xcd\x70\x4d\xfa\xbe\xff\x30\x7e\x94\x49\x56\xdb\xb7\xb9\xc3\
+\x83\xee\x51\x1a\x01\x27\xa0\xcd\xf3\x77\x3b\xa5\x98\x2f\x09\x20\
+\xef\xf8\x88\xff\x55\xd9\x4c\xf1\x23\x2b\xd6\x84\x71\xea\x2a\x6e\
+\x9f\xa3\x72\xb0\xbf\x27\x5e\x69\xfc\xcc\x30\x7b\x10\x3a\x7f\x14\
+\x21\xf6\xf3\x9c\xbc\xfe\x2d\x07\x79\x8a\x08\xc2\x28\xa5\x8d\x9a\
+\xfd\x53\xa5\x87\x79\xf5\x7b\xb5\x55\x0e\x5d\x9c\xa6\x39\x14\xe2\
+\x93\xb9\xda\xef\xa5\x70\x8e\x00\x2f\x0b\x12\xa0\x6a\x9a\x74\x6b\
+\x73\xbb\x17\xcd\xa9\xa0\x3a\xab\x3d\x3f\x11\xcd\xb3\x6f\xf7\x08\
+\x25\xb1\xff\x24\xa2\xf1\x47\xa8\x36\xfe\x4b\xf1\xcd\xdb\x54\x83\
+\x3c\xb5\xef\x31\x55\x7a\x48\x80\xb2\x61\xf3\xb2\xcf\xac\x5d\xb3\
+\x01\xbf\x27\x8f\x1e\x4e\xd0\xfb\x48\x94\x74\xca\x98\x98\x87\x6f\
+\x75\xda\x31\x1f\x4c\x96\x6f\xb8\x25\xf2\x09\xdd\xad\x75\xb6\x2f\
+\xf5\xa1\x39\x64\x5c\x4e\xc1\x43\xc3\x30\xd9\xb3\x6b\x88\x47\x7e\
+\x35\xf8\xfd\x92\xce\x1f\xa6\xbe\xe7\x9f\xca\xc0\xcc\x4c\xaa\x43\
+\xbd\xe1\x0b\x91\xdb\x02\xfe\x86\x57\x2c\x5b\xd1\x41\x4b\x73\x27\
+\x92\x24\x53\xcc\x9b\xc4\x27\xf2\xcf\x53\xb5\x15\xe6\x3c\x40\x74\
+\xba\x30\xd7\x12\x40\xda\xbe\x43\x6a\xb4\x4d\xfe\x6a\xe3\x25\xcd\
+\xe8\x6e\x8d\x86\x66\x1d\xdd\x2b\x5e\x63\xdf\xee\x11\x8e\xec\x9f\
+\x7c\x7a\xd7\xdd\xdc\x8d\x68\xf8\x51\x84\xa5\x7e\xaa\x1b\x7f\xc6\
+\x6f\x71\xfd\xe7\x1b\xff\xde\x86\x6b\xc2\x1d\x19\x32\xc5\x7e\xac\
+\x5c\x0a\xa9\xf4\x85\x9e\x79\x2a\xb1\x87\xea\x88\xe3\x1c\x01\x5e\
+\x22\xe4\xf3\x36\x36\xfc\xad\x27\xe8\xf4\x87\x23\x3a\xb2\x2a\xe1\
+\x0f\x8b\x10\xee\xe4\x68\x96\xfe\xe7\x12\xd9\xbb\x6e\x4e\x7c\x0d\
+\x61\xe9\x8f\x21\x1a\x3f\xc7\x1c\x34\xfe\xfb\x3e\xdb\xf0\x97\x8a\
+\xa6\xdc\xbc\x7a\x7d\x03\xbe\x80\x4a\x3c\xfd\x3c\x39\x4b\x25\x14\
+\x11\xc1\xa3\x91\x3e\x46\x10\x04\x28\xce\xc1\xfb\xcc\x19\xe6\x52\
+\x05\x48\xdb\x77\x48\x8d\xb2\x22\x5d\xdf\xbd\x3a\x08\x88\xe1\x55\
+\x19\x4f\xef\x1e\xe1\xb1\x87\x86\xef\x45\x58\xeb\xb5\x43\xbd\xd3\
+\xea\x76\x05\x94\xeb\x3f\xd7\xf8\x1e\x87\xae\xfe\xdb\xc6\x6d\xcd\
+\x74\x76\xfb\x51\x35\x31\x3a\x94\x6b\x62\x44\x6d\x5d\x34\xb0\x08\
+\x33\x87\xe6\x92\x00\xf2\x8a\xf5\xc1\x3f\x73\x79\x54\x7f\xa8\xc1\
+\x85\x24\x4b\xb8\x3c\xe2\xf1\xfd\x3d\x71\xc6\x47\xd2\x13\xbb\xee\
+\xe6\x47\x08\x27\x4b\x94\xea\x38\xfe\x74\x42\x79\xff\xcd\x0d\x6f\
+\x55\x1c\xca\x37\x36\x6e\x6b\xa6\x63\x99\x9f\x50\xc4\x85\x3f\xe4\
+\xa8\x6c\x00\xcd\x9d\x5e\xd6\x5f\x14\xba\x12\x31\x04\xd4\xa8\x8e\
+\x24\x16\x3c\xe6\x92\x00\x8a\x43\x57\x6f\xec\x58\xe6\x07\xc0\xe1\
+\x54\x2a\x4e\x9f\xa3\x07\xa2\x3c\x7a\xff\xd8\x3d\x08\x63\x2f\x8a\
+\x18\xe7\x9f\xee\xd0\xab\xfc\xee\x9b\x82\x5b\x14\x55\xfe\x66\xf7\
+\x9a\x20\x8d\x2d\xee\x8a\x2d\xe2\xf6\x39\xe8\x3d\x1a\x67\xa0\x37\
+\x09\x40\xfb\x32\x3f\x9d\xdd\x81\x35\xaf\xbf\x4e\x7b\x27\xe0\x43\
+\x78\x19\x1d\x73\xfc\xfd\x4e\x0b\xe6\xca\x06\x90\xde\xfe\x21\xef\
+\x16\x55\x53\x3a\xdb\x3a\x7d\x00\xb8\x74\xf1\xe8\xc9\xd1\x2c\xe3\
+\x23\xe9\x89\x3d\xf7\xf1\x2b\xaa\xbe\xfd\x17\x33\xd6\x7f\x49\xef\
+\xb3\x7d\x87\x14\xd1\xbd\xda\x0f\x96\xad\x0e\xf9\xdb\x3a\x7d\x95\
+\xc6\x37\x8b\x16\xcf\x3c\x3d\xce\x91\x7d\x13\x59\x45\x96\x00\xf4\
+\x8e\x65\x01\x2e\xbc\xac\x95\x62\xc1\x7c\x7f\x47\x77\x6a\xdd\x40\
+\x6f\xea\x7b\xbf\xb8\xd5\xf8\x21\xc2\x3e\x29\x8f\x0c\xca\xdb\x82\
+\x52\x11\x73\x45\x00\x25\x1c\x71\xbd\xb3\xa1\x49\xaf\x3e\xd8\x29\
+\xf4\x6c\x7f\x4f\x9c\xe3\x47\x13\x4f\x22\xfc\xfa\x71\x44\xa0\xe5\
+\x74\xbb\x59\xe5\x75\x5b\x9b\x6e\xf1\x07\x1c\x9d\x4b\x96\x0b\x89\
+\x14\x1b\xcb\x11\x9d\xc8\xd1\x73\x24\xc6\xd0\xf3\xa9\xfe\xff\xfa\
+\x56\xec\x56\x55\xa1\xf0\x7e\x89\x7f\x00\xdc\x1d\xcb\x02\x5c\xf9\
+\xa6\x2e\x7a\x0f\xc7\x2f\xeb\xef\x49\x5c\xb6\x61\x6b\xe1\xab\xc5\
+\x82\x35\x50\x34\xed\xfe\x54\xbc\xf0\xe8\x9e\xdf\x45\xef\xda\x7b\
+\x3f\x47\xa9\xe6\x13\x2e\x08\x22\xcc\x19\x01\x6c\xa4\xcb\x9b\x5a\
+\x3d\x95\x1d\x65\xb7\xef\xc4\x68\x8e\x07\x7e\x9c\x7f\x08\x21\xf6\
+\xd3\x54\x3f\xde\x69\x7d\x1f\x6c\xde\x10\x69\x73\x13\x9d\xc8\x11\
+\x1d\xcf\x32\x3e\x92\x21\x19\x2b\x64\x9f\x3b\x18\xbd\xff\x81\xef\
+\x58\xf7\x20\x8c\xd1\xcc\x7f\x7d\x73\xf8\x6f\x90\xa5\xaf\x00\x9e\
+\x8e\x65\x01\xd6\x6c\xe8\xe0\xc2\x8d\x1d\x14\xec\x49\x46\xc7\x07\
+\xda\xb3\x69\xa3\x7d\x64\x20\xb5\xad\xb1\x45\xff\xc0\xc6\x8b\xd3\
+\x5f\xbb\xfd\x93\xf1\xcf\x50\x75\x55\x9f\x6e\x1b\xe6\x65\x63\xce\
+\x08\x20\xab\xac\x9b\x9a\x8f\x67\x18\x26\xd9\x74\x81\xf4\x10\x87\
+\xa9\xc6\xf3\xe7\xe2\xa3\x49\x89\x68\xee\x96\xfd\x8f\x8d\xbe\xa9\
+\x58\xb4\x1c\x13\x23\xd9\x81\x9e\x67\x0a\x07\x0f\x3e\xcc\xd3\x54\
+\xbd\x8e\x31\x20\xdd\xb7\x8f\x81\x9f\xde\x31\xf4\x97\xc0\xbf\x67\
+\xd3\xa6\xe7\x82\x8d\x1d\x38\xe4\x10\x92\xad\xe0\x0f\x8d\xe3\x0f\
+\x39\x69\xee\xf0\xd2\xb5\x2a\xc4\xee\x07\xfb\x6f\xd8\xf6\xc6\xf8\
+\xd3\xbb\x7f\xc6\xcf\x39\x03\xd2\xcb\x4f\x06\x73\x41\x00\xe9\xad\
+\x7f\xef\xd9\xe4\x74\x4e\x7f\x54\x32\x5a\x60\x6c\x30\x73\x18\x21\
+\x36\xcb\xd1\xbd\xd3\x3d\xec\x03\xb0\xee\xf8\x54\xfc\xab\xc0\xf7\
+\x11\x21\x63\x47\xe9\xb9\x59\x04\x11\x53\x54\xa5\x91\x7c\xe4\x51\
+\xfe\xfb\xe7\xca\xd0\x75\xd9\x94\xf1\x95\x89\xe1\x7c\xf3\xe6\x0b\
+\xb3\x58\x4a\x02\xb4\x22\x72\x29\xdc\xed\x0f\x39\xe9\x5a\x1d\xa2\
+\xb5\x6b\xec\x0a\xc8\xed\xa2\x3e\xa3\xf8\x8c\xc5\x9c\x10\x40\x73\
+\x28\x21\x87\xab\x3a\x72\x72\x3b\x9b\xf0\xab\x1d\xc4\x79\xa6\xbc\
+\xab\x76\x52\xc6\x5c\xf4\x18\x0b\xd1\xd8\x63\x08\x71\xad\x94\x9e\
+\x5d\xa8\xd9\xca\x1e\x3f\x19\xb0\x0e\xed\xe2\xe1\x43\xbb\xc6\xdf\
+\x7c\xcd\x8d\x85\xaf\x0d\x3c\x1f\xbb\x60\xed\xe6\xc8\xb4\x0c\xe3\
+\xfe\x9e\x04\x89\x58\xa1\x88\x48\x30\x51\xa9\x9f\x9a\x76\x46\x62\
+\x4e\x08\x60\x5b\x96\x64\xd6\x68\x75\xb7\xd6\x8c\x43\x0e\xe1\x50\
+\xc2\x48\xb2\x54\x3b\x55\x6b\xae\xc4\xa5\x49\xd5\xab\x97\xa6\x1a\
+\x18\x2a\x5b\xf1\xd6\x0c\xe7\xc6\x81\xa3\x77\xdd\x9c\xd8\x71\xc5\
+\xdb\x13\x37\x24\xe3\x85\xeb\x3a\xbb\x03\x7a\xa4\xd5\x8d\xa6\xc9\
+\x1c\x3f\x96\x20\x3e\x99\x9d\x78\xf0\x4e\xeb\x7e\x16\x80\xe8\x2f\
+\x63\x4e\x6c\x00\xcb\xb4\xeb\x7a\x42\xba\x30\x40\x22\x9f\x43\xf3\
+\x27\x08\x86\x9d\x4b\x10\x0d\x50\x9b\xa0\x31\x27\xaf\xc5\xc9\x1b\
+\x9b\x16\xd5\x78\x44\xf1\xe1\xef\x71\xcb\xc3\x44\xef\xb9\x72\x47\
+\xfc\xba\x86\x26\xd7\x66\x24\x28\x16\xcc\xd1\x9f\x7c\x3d\x7f\x2b\
+\x22\xe7\x20\xc9\xdc\x18\xb3\x2f\x1b\x73\x42\x00\x59\x91\x6c\x4d\
+\xab\xb6\x6d\xb6\x30\x49\x3c\x9d\x41\xf7\xaa\xf8\x42\x2e\x77\x63\
+\x37\xab\xc7\x9f\xe3\x38\x42\x14\xcf\x14\xa6\x3d\x13\x60\x51\x6d\
+\x54\x03\xc8\x3e\xb4\xd3\xfa\x24\x64\x7c\x40\x79\x7c\x9b\x41\x18\
+\x8f\xe5\x00\xd6\x99\xf8\x3b\xea\x30\x17\x9e\x2c\xfb\x91\x5f\xa5\
+\xf6\xc7\xc6\x72\x75\x3b\x0b\x05\x61\xec\x2f\x5b\x1d\xe0\xaa\xb7\
+\x7a\xff\x14\xe1\x5d\x73\x72\x66\x4f\x57\x2b\x67\x0d\x65\x10\x1e\
+\xcb\x21\xe0\x38\x70\xac\xb4\xf5\x21\x22\x98\x29\x16\x48\xad\x82\
+\x39\x21\xc0\xf1\xbd\x4c\xda\x36\x03\xc9\x78\xa1\xb2\x33\x31\x99\
+\x27\x3a\x96\xa3\x6d\x89\x9f\xee\xf3\x42\xdb\xd7\x5d\xc9\x15\x08\
+\x37\xab\x73\x8e\xde\xeb\xe5\xa0\x6c\x17\xa4\x10\x44\x18\xa7\x1a\
+\xc0\x2a\x3b\xb2\xce\x78\xf1\x0f\x73\x44\x00\xc0\xcc\xe7\x8b\x7f\
+\x88\x4e\x54\xa5\x40\x2a\x61\x10\x9f\xc8\x51\x28\x98\x9c\xbf\xad\
+\x99\xd7\xbd\xad\xf5\xd3\xc0\x52\x04\x09\x1c\x9c\xf9\x16\x74\xd9\
+\x68\x2c\x20\xfc\x17\x39\x16\xe0\x44\xd2\xb9\xea\x69\x66\x21\x6b\
+\x3e\x32\xdc\x9f\xac\xec\x70\x7b\x35\x00\x3c\x3e\x95\xe6\x0e\x2f\
+\x2b\xd6\x86\x3d\x37\xdc\x12\xf9\x77\x24\x96\x01\x7e\x16\x86\x24\
+\x58\xf0\x98\x33\x02\xdc\xf9\xd9\xc4\x9d\xd9\x94\x91\x2c\x4b\x01\
+\xa7\x4b\xc1\xe5\x56\x49\x27\x85\x2d\xb0\x76\x73\x84\xf3\xce\x6f\
+\x58\xfe\x81\x2f\x37\xff\xa8\xa4\x0e\x02\x88\xf1\xf4\xa2\x09\xbd\
+\x9e\x89\x98\x33\x02\x00\xf9\x64\xd4\xf8\xf6\x73\x87\x63\x75\x07\
+\xd2\x09\xa3\x52\xbd\xe3\xfc\x6d\x2d\x6c\xba\xb4\xa5\xf9\xf5\x6f\
+\x6b\xfd\xcf\xb7\x7c\xc0\xfd\x41\x20\x8c\x98\xac\xb1\x28\x42\xaf\
+\x67\x22\xe6\x72\x66\x90\xcb\xdb\x46\xd7\xbb\xfe\x2e\xf2\xf8\x86\
+\xad\xcd\xde\x5a\x2f\x9a\xe6\x50\xea\x66\x04\x25\xa2\x79\xf6\xec\
+\x1a\x62\x6c\x30\xfd\xe4\xc1\x3d\xe3\x5f\xfc\xed\x4e\xfb\x7e\xaa\
+\xae\xd9\x05\x61\x5d\xbf\x1c\x2c\xd6\xa9\x61\x0a\xe0\x7d\xed\x75\
+\xda\x3b\xce\xdb\xd4\xf0\xd5\xad\x97\xb7\xd6\xcd\xcb\x77\xea\x2a\
+\x4d\x4d\x4d\x68\x8a\x08\xcf\xe6\xcd\x51\x0e\xed\x1f\xa6\xf7\x70\
+\x94\x91\x81\xd4\xbd\x0f\xfd\x34\xfa\xcf\x47\xff\x20\xa6\x77\x53\
+\x75\x1b\x2f\x08\x4b\xfb\xc5\x62\xb1\x12\x40\x42\x88\xf2\xd0\x75\
+\x9f\x0c\xed\x6c\xe9\xf0\x5e\x79\xc1\xb6\xe6\xca\x79\xba\x23\x4c\
+\x4b\x78\x3d\x9e\x92\x71\x68\x53\x64\xb2\xb0\x97\x82\x51\xe0\x99\
+\x27\xc7\xe8\xef\x49\x90\x8c\x15\x1e\x7c\xf6\x8f\xd1\x5b\xef\xbf\
+\xdd\x7c\x80\xfa\xe0\xd1\x82\x88\xbd\x9f\x2c\x16\x2b\x01\x40\xe8\
+\x71\xb7\x12\x64\xf9\x7b\x3e\xdc\x70\x77\x4b\xa7\x6f\xe5\xba\x4d\
+\x8d\x00\x34\x7a\x37\xe0\x54\xfd\x68\x0e\x05\x8f\x4f\x43\x92\x20\
+\x6d\x3e\x4f\xd6\x1c\x06\x20\x9b\x2a\xd0\xdf\x9b\xa2\xf7\x70\x94\
+\x64\xa2\x70\x78\xf0\xf9\xd4\x6d\xbf\xfe\x61\xfa\x9e\x58\x2f\x23\
+\x54\x83\x37\x0b\x6a\x08\x36\x1b\x16\x33\x01\x40\x24\x55\x7a\x71\
+\xb0\xe2\xaf\x3f\xd7\xfc\x60\x38\xa2\x7b\xd7\x6d\x6a\xac\x10\x00\
+\xc4\x94\x70\xaf\x5f\x23\xc7\x50\x5d\x55\x10\x10\x39\x04\x23\xfd\
+\x29\x7a\x0f\xc7\x88\x4e\xe4\x92\xc9\x78\xe1\x37\x7f\x7c\x7c\xe2\
+\x5b\xbb\xee\xae\xab\xee\x55\x4e\xdd\x5e\x90\x64\x58\xec\x04\x90\
+\x10\x24\xf0\xaf\xdf\xce\x2b\x2f\xb9\x2a\xf4\xb9\xd6\x25\x81\xee\
+\x8b\x2e\x5e\x4b\x48\x5f\x09\x80\x59\xb4\xb1\xed\x22\x59\xf5\x20\
+\xaa\xd3\x44\x51\x25\x14\x75\xfa\x20\x20\x11\xcd\xd3\xdf\x93\x60\
+\xa0\x27\x41\x26\x65\x0c\x46\x27\x72\x3f\x7c\xfc\xa1\xd8\x77\x0f\
+\xfc\xb6\x92\x9a\x55\x6b\x2b\x2c\x18\x32\x2c\x76\x02\x80\x20\x81\
+\x13\xf0\x23\xd3\xfd\xde\x4f\x87\xbe\x13\x6e\xf2\x74\x5f\x7c\xe9\
+\x06\xfc\xee\x16\x8a\x76\x8e\x74\x6e\x08\xc3\x4c\xe1\xd4\x55\x82\
+\x8d\xf5\xd3\xc6\x67\xc2\x48\x7f\x8a\x91\x81\x14\x23\xfd\x69\x52\
+\x49\xe3\xd0\xc4\x48\xe6\xee\x7d\x8f\x26\x7e\xfa\xf4\x03\x1c\xa5\
+\x5e\x45\x9c\xf1\x64\x38\x1b\x08\x00\xd5\xa2\x0c\x7e\x14\x96\x5d\
+\xf3\x21\xef\x27\xc2\xcd\x9e\xd7\xae\xbf\x70\x7a\xa2\x85\x24\x4b\
+\xf8\x02\x5a\xdd\x0c\xe2\xd9\x50\x56\x11\x23\x03\x69\x46\xfa\x53\
+\xe4\x73\xd6\x82\x23\xc3\xd9\x42\x00\x98\x52\x99\xe3\xe2\xff\xc5\
+\x9b\x37\xbd\xa2\xf1\x63\x91\x36\xaf\x67\xf5\xfa\xd0\xb4\xf2\x6d\
+\xb2\x2a\xe1\xf3\x3b\x2b\x29\xdc\xb3\xc1\xa1\x04\x51\x25\x2f\xd9\
+\x5c\x92\xbe\xbe\xe3\xd3\xc8\xf0\xd3\x3b\x13\xdf\x89\xf5\x32\xca\
+\x74\xe3\xf1\x8c\x20\xc3\xd9\x44\x00\xa8\xda\x04\xe5\x92\x2e\xcb\
+\xde\xf1\x51\xff\x67\x82\x11\xfd\x92\xa5\x2b\x02\x94\xd3\xb6\x6b\
+\x31\x13\x11\x0c\xc3\x24\x9f\x35\x09\xfb\xdb\xf0\x3b\xba\x2b\xfb\
+\x13\xe6\x61\x0a\x66\x6c\x9a\x64\x48\xc6\x0a\x0f\x4c\x8e\xe6\x7e\
+\xf9\xcb\x1f\xa4\xee\xad\x19\x49\x9c\x11\xb3\x7f\xcf\x36\x02\x40\
+\xb5\xae\x8f\x13\x11\x0d\x0c\xad\xdf\xce\xab\x37\x5f\x16\x78\x6f\
+\xa8\x49\x5f\xdf\xb5\x32\x40\x79\x42\x49\xdd\x45\xb2\x84\xee\x51\
+\xf1\xf8\x34\x12\xd1\x02\xf9\x6c\x91\xce\xa6\x4d\x04\xfc\xe1\xca\
+\x39\x53\xeb\x0b\xc2\x74\x35\x11\x9b\xcc\xff\x78\x74\x20\x7d\xdf\
+\x8f\xbf\x92\xbb\x87\x6a\x54\x6f\xde\x46\x12\x67\x23\x01\xca\x28\
+\xab\x04\x37\x22\x22\x18\xda\xbe\x43\xda\xb1\x6a\x63\xf8\x5d\x2e\
+\xb7\xd6\xd4\xd1\xe5\xa7\xad\xd3\x33\x4d\x35\x00\x58\x36\xd8\x96\
+\x4d\x57\xe7\x4a\x02\xee\xa5\x95\xfd\xb5\xbe\x84\x99\x90\x4d\x15\
+\x18\x19\x4c\xd3\x73\x38\x4e\x62\x32\x37\x18\x9d\xc8\xfd\xf0\xb1\
+\xdf\xc6\xee\xfa\xe3\x43\x94\xb3\x95\xe7\xdc\xeb\x78\x36\x13\x00\
+\xaa\xd2\xa0\x4c\x84\x00\x10\x5c\x7d\x19\x57\x6e\xdd\x1e\xb8\x36\
+\xd4\xe0\x5a\x17\x6c\xd4\x59\xb2\xdc\x8f\x2f\x30\xb3\x51\xd8\x10\
+\xe8\xc2\xe7\x0e\x81\x96\x22\xc7\xd0\x09\x1f\x66\xdb\x16\xe9\x64\
+\x11\xa7\xae\x90\x4d\x15\xe9\x3d\x12\x65\xa4\x3f\xcd\xe4\x58\x76\
+\x26\xaf\xe3\x9c\x4c\x0d\x3f\xdb\x09\x50\x86\x4c\x55\x2d\xb8\x11\
+\xaa\xc1\xef\x68\x60\xf5\x1b\xfe\xdc\xfd\xf6\x96\x76\xf7\xab\x1d\
+\x6e\xd5\xdd\xd1\xe5\x27\xd2\xe2\x9e\x56\xe6\xb5\x0c\xcd\xa1\xa0\
+\x39\x65\x5c\xba\x4a\x6d\x6a\x7a\x19\xa9\x44\x81\x54\xac\x80\x53\
+\xaf\xd6\x02\x30\x0c\x93\x81\x9e\x04\x3d\x87\xe3\x44\xc7\xb3\x87\
+\x07\x9f\x4f\xdd\xf6\x83\x7f\x49\x7f\x1b\x41\x84\xd3\x5e\x24\xe2\
+\x1c\x01\xea\x51\x4b\x04\x17\x62\xc4\xe0\x03\xfc\x5b\xae\xe6\x35\
+\x9d\x2b\xdd\x97\x37\xb5\xb8\xb7\xf9\x42\x0e\x77\x43\x93\x4e\xa4\
+\xc5\x33\xab\x64\x00\x70\xb8\x14\x1c\x2e\x05\x55\x93\x71\xba\x64\
+\x8a\x45\x9b\x64\xb4\x80\xdb\xa7\x55\x26\xac\xd6\xa2\xbf\x27\xce\
+\xd1\x03\x51\x12\x93\xb9\xa1\xe3\xc7\x12\x5f\xbe\xfb\x96\xcc\xb7\
+\x11\x91\xc9\xd3\x56\x2c\xe2\x1c\x01\x66\x86\x8c\x88\x28\x6a\x08\
+\x22\xb8\x4b\x9b\x17\xf0\x6e\x7a\x3d\x57\xad\x58\xe3\xbe\xd4\xdf\
+\xa0\x6f\xd0\xdd\x6a\x63\x38\xa2\x13\x6a\x74\x12\x6a\xd0\x67\x95\
+\x0e\x20\x46\x14\x9a\x26\xa4\x84\xc3\xa1\xcc\xea\x75\x2c\x13\x21\
+\x3a\x9a\x3d\xb2\xff\x89\xd1\x4f\x3e\x78\x67\x5d\x88\xfa\x94\xc6\
+\x20\xce\x11\xe0\x05\x6e\x49\x69\xd5\x30\x84\x9d\x50\x96\x0c\x6e\
+\x84\x74\xf0\x44\x56\xb1\x61\xc3\x56\xe9\xc2\xa6\x76\xcf\x86\x50\
+\xd8\xb5\xce\xe9\x51\xdd\x6e\xb7\x46\x28\xa2\xe3\x0b\x38\x4e\xb8\
+\x66\x40\x19\x0e\x97\x82\x4b\x57\xd0\xbd\x6a\x9d\x17\xb2\xf7\x48\
+\x94\xa3\xfb\x27\xe9\xef\x4d\x7c\xf7\xae\xaf\xc6\xff\xa9\x30\x4e\
+\x3f\xd5\x62\x16\xa7\x44\x1a\x9c\x23\xc0\xc9\xa3\x2c\x15\x54\x84\
+\x64\x70\x96\x36\xbd\x76\x8b\xac\x64\xdd\xf9\x17\xcb\x9b\xfd\x61\
+\x57\xb7\x2f\xa0\x75\x79\xfd\x8e\xa5\x6e\xaf\x86\xdb\xeb\x40\x77\
+\x2b\x84\x1a\x75\x5c\xba\x3a\xa3\xa4\x90\x64\x09\x7f\xb0\xde\xe7\
+\x90\x4d\x15\xd8\xb7\x7b\x84\xfe\xde\xd4\xd1\xdf\xdd\x37\xf2\xa1\
+\xa7\x7e\xc5\xef\xa9\x96\xad\x7d\xd9\x24\x38\x47\x80\x97\x86\x32\
+\x19\xca\x6a\x42\xa3\x2a\x21\xca\x52\xa2\xbc\x39\xbb\xb7\xb1\xa5\
+\x6b\x95\xbc\xd2\xa5\xab\xcd\xa1\x46\xd7\x32\x7f\xd0\xb9\x54\xd3\
+\x15\x77\x4b\xbb\x87\x25\xcb\xfd\xd3\x86\x9a\x0e\x97\x42\x28\xe2\
+\x9c\x56\xca\xf6\xd0\xde\xb1\xd4\x2f\x76\x0e\x5d\x77\xf0\x61\x7e\
+\xc7\x29\xaa\x68\x76\x8e\x00\xa7\xe0\xb1\xd4\x13\xa2\x2c\x21\x6a\
+\x89\x51\x4b\x0e\x27\xe0\x5c\x79\x31\x5b\x2e\x7d\x5d\xf0\x1d\x1e\
+\xbf\xb6\xae\x6b\x65\x68\x9a\x17\x52\x92\x25\xc2\xcd\xae\xba\x92\
+\xb6\xfd\x3d\x71\x9e\x7c\x64\x38\xfd\xb3\xef\x0e\xbd\xe7\xf0\x2e\
+\x76\x51\x5f\xe1\xe4\x25\x39\x92\xce\x11\xe0\x34\xbc\x06\x82\x10\
+\xb5\xa4\x28\x4b\x8a\x5a\x72\xb8\x00\xcf\xb6\x37\x72\xf5\xf9\xaf\
+\x68\xb8\x21\xd2\xe2\x6e\x5a\xb7\xb9\xb1\x4e\x1a\xc8\xaa\x44\xa4\
+\x55\x9f\x56\xd1\xfc\xc9\x47\x86\xd3\xeb\x62\xd2\x13\xd9\xa4\xb1\
+\x6b\xd7\x13\x63\x3f\xd9\x79\xff\xb4\x90\xf4\x49\x7f\xe8\x73\x04\
+\x98\xa3\xdf\xce\x74\x52\x94\x03\x53\x01\xa0\xeb\x1d\x1f\xf5\xff\
+\x63\x73\xa7\x67\x6b\xed\x02\x12\x00\x6e\xdf\xf4\x25\xea\xf6\xec\
+\x1a\x44\x1e\x31\xb9\x32\xdc\xc9\xd8\x48\x1f\x63\x93\xf9\x1f\xff\
+\xc3\x2d\xfd\x1f\x7f\x6e\x84\x41\xaa\x79\x8c\x27\xa5\x1a\xce\x11\
+\x60\xee\x21\x03\xda\x5d\x9f\xeb\xb8\xce\xad\xab\x6f\xd2\x14\x69\
+\x83\xc3\x21\xb7\xc7\x92\x85\x63\xa3\x3a\xcb\x07\x34\x9b\x0b\x2e\
+\x69\xc1\xb2\xa1\x90\x2d\x62\xdb\xd0\xb9\xc2\x57\x47\x0a\xc3\x30\
+\x79\xe8\xde\x5e\xde\xbd\xf9\x35\x2c\xf5\x36\x72\xe0\xc0\x6e\x86\
+\x06\x8e\xa7\xee\xf8\xf1\xb1\x77\xee\xbc\x9f\x47\x78\x11\x33\x86\
+\xcf\x11\x60\xee\x20\x01\xea\x77\x6f\x6e\x7b\x73\x43\xc8\xf5\xf9\
+\x70\xb8\xa1\x73\xc5\xca\x75\x04\x42\x61\xe4\x40\x88\xd4\x40\x1f\
+\xcf\x3e\x7b\x90\xa3\x43\x03\x8c\x74\x39\x51\x75\x95\xa2\x21\xda\
+\x2f\x1c\xd1\x69\x5e\xe2\x9e\x56\xe2\xde\x3f\x19\xe6\xad\x17\x5c\
+\x06\xc0\xf1\xde\xc3\x3c\xfd\xd4\xe3\xc3\x6f\xfa\x9b\xde\x57\x21\
+\x4a\xcf\x94\x49\x70\x42\xcc\x25\x01\xce\xe6\xc9\x16\xd2\x47\xae\
+\x75\x47\xee\xfd\xd7\xae\xdb\xdb\x5b\x83\x3b\x2f\xbd\xec\xd5\x9d\
+\x17\x5d\xf5\x26\x42\x5d\xdd\xc8\x01\xb1\x50\x84\xb7\xbd\x93\x8d\
+\xaf\x7c\x2d\x17\xac\x58\x45\xe8\x58\xae\xd2\xf8\x00\xf9\x7c\x91\
+\x54\xac\xbe\x98\x59\x47\x97\x97\x3d\x83\x47\x2a\x7f\x2f\xe9\x5a\
+\x4d\x7b\x6b\x6b\xcb\xdf\xbd\xc3\xf9\x2e\x44\xa8\xfb\x8c\x9b\xee\
+\x76\x46\xbd\xcc\x1c\x42\xfa\xc0\x0e\x67\xd3\xe5\xdb\x5a\x1e\xe8\
+\xec\xec\xb8\xe6\x95\x57\xbf\x19\x6f\x7b\xa7\x38\xa2\xe8\xe0\x5a\
+\x01\x52\xd5\x9d\xbc\x6a\xcb\x65\x44\x1c\x1e\xd4\x64\xb5\x7e\x95\
+\xaa\xca\xd3\x7a\xaa\xee\x15\xab\x91\x1e\x9b\x1c\x02\x49\x03\xc5\
+\x4d\x6b\xe7\x32\xae\xd8\xda\x7a\x2d\x62\x96\x93\x9b\x33\x6c\xfa\
+\xfb\xd9\x48\x00\xe9\x03\x3b\x9c\x4d\x57\x5d\xd1\xfe\x9b\x15\x2b\
+\x56\xae\xdf\xf8\xca\xd7\x82\x56\x6a\x6c\xc9\x01\x9e\x4b\xc1\xb9\
+\x02\xf4\xf5\x75\x17\xad\x59\xbb\x1e\xc7\x58\xb5\xc7\xab\x9a\x3c\
+\x63\x70\x49\xf7\x6a\xc4\xb2\x69\xf0\x75\x43\x78\x2b\xad\xed\xcb\
+\x69\x0c\x07\x9a\xde\xfd\x46\xe9\x6a\xce\xc0\xe9\xef\x67\xcc\x8b\
+\xcc\x11\xa4\x8f\x5c\xeb\x8e\x94\x1b\x7f\xd5\x96\xcb\xea\x8f\xea\
+\xab\xa9\xd4\x87\xd7\x9a\xea\xa4\x40\x7b\x7b\x3b\x11\x59\x42\x73\
+\xc8\x38\x74\x95\x86\x86\x30\xa1\x40\xe3\xb4\x07\x38\x9d\x32\x93\
+\xa9\x04\xa8\x01\xd0\x02\xe0\x68\xa4\xb3\x6b\x15\xaf\xd8\xd2\x7c\
+\x35\x62\x74\xa1\x73\x06\x4d\x78\x3d\x9b\x08\x20\x01\xea\x65\x5b\
+\x9a\x6f\x6b\x6b\x6d\x9b\xde\xf8\x92\x03\xb4\xf6\xfa\x7d\x8a\xb7\
+\xf2\x5f\x39\x10\x42\x47\x22\xd4\xe8\x62\xf5\xea\x2e\x3a\x1a\x37\
+\x13\xd0\xd6\xe0\x53\x97\x57\xce\xb1\x6d\x30\x0a\x26\xd9\x4c\x1e\
+\x64\x31\xc3\x09\xf7\x12\xda\xdb\x96\xd3\xdd\x15\xdc\xda\xd5\xc8\
+\x4a\x84\x1a\xd0\x38\x43\xea\x1f\x9c\x4d\x04\x50\xee\xfe\x62\xe7\
+\x27\x43\xe1\xf0\x1b\x36\xbe\x62\xbb\xd8\xa3\xb5\x88\x0d\xc0\x11\
+\x99\xe1\x8a\xc0\xb4\x5d\x4e\x5d\xc5\xe3\xad\x7a\x08\x65\xa9\xea\
+\x0f\x30\xf2\x45\x06\x7a\x62\x44\xf4\x9a\xf4\x35\xbd\x05\xc5\xa1\
+\xd1\x14\xe9\xe4\x86\xbf\x08\xed\x40\xa8\x81\x33\x66\xda\xfb\xd9\
+\x42\x00\xf9\x8e\xcf\xb6\xbd\xca\xe3\x72\x7d\x64\xdb\xc5\xaf\x14\
+\x3a\xdf\xb5\x02\xdc\x1b\xa1\x38\x29\xce\x50\x9a\xa7\x5f\x25\x4d\
+\x6f\xa3\x60\xa3\x83\x6c\x71\x08\xc3\x4e\x60\xd9\x79\xb2\x56\x35\
+\xe3\x28\x3e\x91\xa2\x90\x2d\x12\xd4\xbd\x90\xcb\x94\xee\xa1\x81\
+\xde\xc2\xd2\xae\x55\xac\xed\x0e\xbe\x1a\x91\xea\x76\xc6\x18\x83\
+\x67\x03\x01\xa4\x0f\xec\x70\x46\xfc\x6e\xf5\xd6\x75\x1b\x36\xa2\
+\x84\x42\xe0\xde\x20\x0c\x3d\x63\x14\xec\xd2\xb0\x5c\x6b\x3a\xb9\
+\x9b\x49\x32\x36\x26\x71\xe3\x19\x26\x8d\xbd\x14\xcc\x18\x00\xd9\
+\x54\x9e\xf1\xe1\x34\xa6\x25\xd3\xe2\x0b\x62\xa6\x6b\x52\xd1\x9c\
+\x6d\x78\x03\x21\xc2\x8d\x0d\xee\x77\x5e\xcd\x6b\x38\x83\xca\xcd\
+\xcf\xfb\x0b\xcc\x01\x94\xcb\x2e\x6a\xfe\x7f\xe1\x70\xa4\xb3\x73\
+\xed\x06\xd1\xf8\x65\x5d\x6f\x8e\x88\x7f\xd5\x30\x18\x06\xe4\xa7\
+\xf8\x68\x24\xad\xee\xcf\x40\x20\x8c\x3d\x9a\x9f\xf6\x00\xdb\xb6\
+\x18\xed\x8f\x93\x4a\x18\xac\x6b\xe9\x00\x20\x97\x19\xa9\x9e\xa0\
+\x0b\x35\xd3\xd6\xbe\x8c\x2b\xb6\xb6\xbc\x8e\x33\x48\x0d\x2c\x76\
+\x02\xc8\xdf\xf8\x87\xe6\x2d\xba\x53\xbb\x7e\xdb\x25\x97\x81\xa3\
+\xb5\xda\xf8\x76\x11\xf2\x03\xe2\xff\x39\x9d\xc9\xa1\x71\x26\x47\
+\x26\xb0\xe2\xd5\x3a\x46\x28\x35\xba\xbc\x68\xa1\x6a\xda\xb4\x07\
+\xd8\xb6\xc5\x58\x5f\x1c\x55\x95\x98\x18\xcd\xb3\xaa\x41\xdc\xdf\
+\xc8\x4e\x82\x55\x22\x94\xa4\x81\x16\xa0\xa9\xb9\x9d\xae\x76\xcf\
+\x56\xa0\x15\x31\x1a\x98\xf7\x72\xb2\x8b\x9d\x00\x4a\x73\x83\xf3\
+\x0b\x4b\xbb\x56\xe0\x08\x87\xc1\xb9\xa4\x7a\xa4\x58\xed\xa1\x96\
+\x5d\x2d\x63\x9f\xcd\xcd\xe2\xa9\x95\xa9\x94\xb2\xa9\x45\x62\x3c\
+\x43\x26\x95\x63\x72\x3c\x4b\x7c\x28\xc3\x86\xa6\x6a\x4a\x3a\xa9\
+\x1a\x29\xe0\x68\xc0\xa5\x7b\x89\x34\x77\xf2\x57\x6f\xd1\x5e\x45\
+\x55\x0d\x9c\x23\xc0\xe9\xfa\x6d\x3b\x3f\xdf\xf1\x1e\xb7\xae\x5f\
+\xba\x6e\xc3\x26\x90\x65\x30\x6b\x7a\x77\xfe\x78\xf5\x44\x6f\x10\
+\x45\x55\x90\x65\x09\x4f\xd8\x3f\xcb\xdd\x64\xf2\x85\xfa\x4a\xf6\
+\xc9\xc9\x2c\xb1\xf1\x14\xb6\x05\x07\x1f\x1f\x65\x73\xcb\xb2\xba\
+\xd8\x40\x9d\x1d\xa0\xb7\x01\xd0\xd4\xdc\xc6\xa6\xb5\xe1\x4b\x11\
+\xb9\x8c\xf3\xae\x06\xce\x08\x4b\xf4\x34\x41\xd3\x1d\xf2\x47\x57\
+\xae\x5e\x0f\xae\x92\x43\x27\x73\x10\xa2\xfb\xc0\xa9\x42\x6d\x90\
+\xca\x15\x26\xd0\xf6\xc2\x37\x54\x24\x89\xd8\x78\x06\x7f\x44\x23\
+\x36\x96\x21\x15\xcb\x60\x99\x36\x03\x3d\x71\xb2\xe3\x45\x2e\xbe\
+\x68\x75\xdd\xf9\xb9\xcc\x08\x15\xd9\xe2\x68\x00\xa0\x29\xd2\x49\
+\x57\xa7\xf7\x22\x18\x69\x41\x54\x2b\xd7\x98\xc7\xba\x47\x8b\x55\
+\x02\xc8\x77\x7e\xb6\xed\x3a\x97\x4b\xef\x5c\xda\xb5\x1c\x4a\xba\
+\xdb\x4a\xe6\x98\x1c\x4b\x90\x8f\x66\xaa\x67\xce\x30\xd6\xaf\xa0\
+\x56\x62\x00\x8d\x4d\xcd\x8c\x1f\x9d\xe4\xf9\x67\x46\x89\x8d\x26\
+\x29\x16\x4c\xb2\x69\x83\x7d\xbb\x47\xb8\xbc\x6b\x15\x2e\xa5\xde\
+\x46\xa8\xb3\x03\x00\x1c\x8d\x28\x0e\xad\xac\x06\xfe\x07\xc2\x0e\
+\x98\x57\x35\xb0\x58\x09\xa0\x79\xdd\xea\x8d\xab\xce\x5b\x8f\xec\
+\xa8\x3a\x6a\xe4\x92\x78\x76\x68\x35\x3f\x5b\x3a\x81\x04\xb6\xeb\
+\xa3\x7d\x8a\xac\x62\x5b\x36\xb6\x25\x3a\x6b\xd1\xb0\x78\xea\xf7\
+\x83\x78\x8b\x12\xdd\xc1\x08\xb6\x3d\x43\xa8\xbf\xd6\x0e\x70\x09\
+\xd7\x71\x28\x1c\xa9\x55\x03\x4e\xe6\x51\x0d\x2c\x46\x02\x54\x7a\
+\x7f\x67\x67\x17\x38\x6b\x7a\xa5\xee\x20\xdc\xd9\x80\xe4\xab\x2e\
+\x5e\x85\xa2\x9f\xf4\x8d\x6b\x73\x1b\x2c\xdb\xe6\xd8\xe1\x49\xe2\
+\x23\x39\x36\xb8\x03\xa5\xe3\xd3\x3f\xa7\x55\x3b\x1c\x74\x8a\xe1\
+\x60\x53\x73\x3b\x6d\xcd\xee\x75\x88\xec\xa3\x79\x2d\x90\xbd\x18\
+\x09\xa0\x06\x7c\xa2\xf7\x2b\xaa\x2c\x8c\xbf\x13\x41\x7a\x11\x04\
+\x90\x81\x52\x4e\xc0\xf8\x40\x8a\xc3\x7b\x27\xb8\x38\xd2\x8a\xc7\
+\xe9\xaa\x1e\x9f\x02\x23\x73\xbc\x2a\x49\x34\x11\x20\x72\xe9\x5e\
+\x02\x01\xbf\xfb\xea\x8b\xd9\x86\x30\x04\xe7\x2d\x36\xb0\xd8\x08\
+\x20\xdf\xfa\x8f\xcd\xaf\x76\x38\x44\xef\x97\x1c\xce\x97\x7f\xc7\
+\x1a\xb4\x34\xb7\x61\xc5\x0d\x06\x7a\x93\x3c\xfa\xdb\x41\xae\x5e\
+\x73\x3e\x4d\x4e\x37\x6e\x8f\x17\xcd\xe9\xc0\x30\x66\x58\xef\xca\
+\x36\x20\x7d\xac\xfa\xb7\x47\xd4\x2e\x08\x86\x9a\xd8\x7a\x81\x77\
+\x33\xd5\xe0\xd0\xbc\xa8\x81\xc5\x46\x00\x25\xe8\xd7\xfe\xba\xbd\
+\xa3\x4b\xfc\xe5\xd0\x5e\xd6\xcd\xb0\xb3\xd3\x76\xa5\x93\x06\x8f\
+\x3e\x38\xc8\x96\x8e\x2e\x36\xb4\xb4\x93\x4a\xa7\xf0\xfb\x02\x38\
+\x35\xad\x62\x1b\xd4\xbd\x90\xa2\x40\xf2\xb9\xaa\x14\x28\x79\x05\
+\xc3\x8d\x11\x96\x2f\xf1\xaf\xa7\x6a\x08\xce\x4b\x5b\x2c\x26\x02\
+\x48\x9f\xf9\xeb\xc0\x0a\xa7\x43\x7e\xfd\xf2\xee\xd5\xc8\x8a\x2c\
+\x42\xb2\xca\xcb\x08\xbf\x9b\xf5\x04\x90\x55\x89\x42\xca\x62\x5b\
+\xd7\x12\xae\x5a\xb5\x06\x80\x58\x34\x8a\xdb\xed\x45\x73\x88\x67\
+\x14\xf2\x06\x45\xd3\x22\x97\x37\x91\x24\x19\x45\x55\x44\xe3\x67\
+\x4b\x35\x0a\x4a\xc1\xa1\x50\xb8\x89\x86\xa0\xb3\x0b\x41\x80\x79\
+\x33\x04\x17\x13\x01\xe4\x95\x4b\xfd\xef\x6a\x6d\x5d\x8a\xa6\x69\
+\xc8\x9a\x07\x5c\x5d\xa0\x75\x8a\x7f\xa5\x53\xa3\x0e\xda\x43\x01\
+\x5e\xbb\x7a\x1d\x00\xc9\x54\x12\xa7\xd3\x85\xaa\x2a\x68\xa5\xac\
+\xa2\xa2\x65\x31\x34\x92\x60\x6c\x22\x49\xb6\x60\x22\x95\x6d\x90\
+\xf4\x73\xd5\x9b\x38\xdb\x70\xe9\x5e\x74\x8f\xd3\x7d\xe1\x4a\xd6\
+\x22\x24\xc0\xbc\xb8\x85\x17\x13\x01\x54\x87\x26\x5d\xd3\xb1\xb4\
+\x4b\xfc\xe5\x6a\xa4\xda\xa9\x14\x50\xfc\x2f\xf1\xb6\x55\x38\x03\
+\x61\x12\x89\x78\xe5\xef\x78\x2c\x8a\xc7\xed\xc1\xe1\xaa\x92\x2b\
+\x1a\x4b\x01\xe0\x70\x68\xc8\x72\x8d\x71\x6f\xc4\xc5\x06\xe0\x14\
+\x4e\xa1\x80\x37\xc4\x8a\x25\xb4\x53\x1d\x09\x9c\x23\xc0\x4b\xfd\
+\x1d\x77\x7c\xa6\xed\xcf\x74\x5d\xef\x6c\x6c\x28\x85\x75\xe5\xa9\
+\x12\xf5\x25\x38\xda\xca\xb9\x02\x65\x68\x8e\x3a\x57\x6f\x32\x99\
+\xc6\xef\x0f\xe0\xaa\xb1\x35\xbc\xba\x13\x5d\x77\xa2\xeb\x3a\x1e\
+\xf7\x94\x3a\x05\xf9\x92\x1a\x50\xdc\xa0\xb8\x09\x36\x36\xb3\xba\
+\xdb\xdb\x4d\x55\x02\xcc\xb9\x1a\x58\x2c\x04\x50\x74\xa7\xf2\xa6\
+\x8a\xf1\x07\x40\xac\x6a\x78\xd9\x79\x28\x46\x4f\xc9\x83\x24\x09\
+\x14\x45\x7c\xb6\x54\x3a\x89\xdb\xed\xc5\xe1\xaa\x6d\x68\x8b\xee\
+\x65\xcd\x2c\xe9\x0c\xe3\xf1\x4c\x51\x3b\xd9\x9a\xd8\x80\xc3\x8f\
+\xae\xbb\xe9\xee\xf4\xad\x47\x48\x00\x8d\x79\x68\x8f\xc5\x12\x0b\
+\x50\x9d\x0e\xae\x5e\xb2\x64\x79\xcd\xae\x22\xe4\x7b\x5e\xf8\xca\
+\xe2\xa4\xf8\xfc\x53\x61\xcf\xbe\x84\xb1\xc3\xa1\x91\x4c\xa5\x49\
+\x25\x93\x34\x36\x35\x21\x97\x1c\x00\xb2\x22\xe3\xf1\xf9\x90\x54\
+\x1d\xac\xe9\x79\x03\x18\x71\x41\x4a\x49\x03\x2d\x88\xae\x7b\x71\
+\x38\x95\xf2\x82\x18\x2a\xf3\x40\x80\xc5\x20\x01\xe4\xdb\x3f\xd5\
+\xfa\xa7\x5e\x6f\xc8\xaf\xbb\xdd\x00\xa8\xda\x8b\xe0\xb5\x99\x9a\
+\x79\xff\x54\xf1\x5f\x03\x55\x55\x88\x46\xa3\xf8\xfd\x01\xbc\x1e\
+\xe1\x48\x92\x15\x19\x9f\x3f\x88\xcb\xbf\x1a\xf4\x65\xe0\x98\x25\
+\xc3\xc8\x48\x94\x6e\x12\xc0\xe7\x0d\xd1\x18\x72\x2e\xe1\x9c\x11\
+\xf8\xf2\x7e\x83\xee\x54\xae\x08\x85\x1b\x4f\x7c\x56\xce\x98\x79\
+\xbf\x5d\x10\xa9\x61\xb5\xb0\x92\x90\x3d\x30\xe3\xe9\x91\x48\x13\
+\xb9\x7c\x06\x45\xb1\x69\x69\x6d\x46\x55\x55\x64\x45\xc6\xeb\xd1\
+\xd1\x5c\x21\x2a\x4b\xe4\x2a\xb3\x8c\x3a\x0a\xe3\xa5\xb7\xd6\x50\
+\xaa\xb6\x43\x59\xff\x9f\xb3\x01\x5e\x02\x14\x87\x26\x5d\xd6\xd2\
+\xd6\x3e\xf3\x51\xc3\x24\xd6\x1f\x65\x72\x2c\x41\xac\x3f\x0a\x33\
+\x24\x75\x90\x7f\xa6\xea\xf4\x31\x06\x20\xf5\x78\x35\x57\x70\x0a\
+\x24\x59\x74\xd2\x54\x32\x46\xa4\x31\x52\x69\x7c\x49\x96\x28\xe6\
+\x63\x60\x95\xee\x5f\x4c\xcf\xfc\x3e\x66\xfd\x7d\x75\xdd\xcb\x05\
+\xcb\x59\x49\x55\x02\xcc\xa9\x14\x58\xf0\x36\xc0\x3b\x5f\x4f\x83\
+\xa2\x48\xeb\x2a\xd6\xff\x14\x14\x53\x79\xac\x52\x94\xce\xb2\x2d\
+\xf2\xc9\x1c\xce\xb0\xa7\xfe\x24\x33\x0b\x89\xff\x3e\xe9\x67\x26\
+\x93\x71\xa2\x93\x13\x9c\xb7\x66\x23\x1e\xb7\xab\x42\x0a\xdb\xca\
+\x93\x89\x1d\x20\xe0\x69\xc5\xb6\x2c\xcc\xa2\x85\xea\x98\xf2\x89\
+\xcb\x2a\xa0\x04\xdd\xe5\xa6\x29\x8c\x8f\x63\x15\x09\x30\xa7\x4b\
+\xe7\x2e\x74\x02\xc8\xaf\xb8\x30\xb2\xc9\xeb\x0d\xce\x7a\x82\x39\
+\x65\xfe\x9e\x52\x8e\xe8\xe5\x0c\x70\xbd\x34\x57\x71\xff\xf1\x3e\
+\x9a\x9b\xdb\xf1\xf9\x7c\xc2\xe3\x38\xf5\x99\x45\x93\xe1\x91\x04\
+\xa6\x65\xe3\xf7\xb9\x08\x06\xdd\x27\xbc\x9f\x65\xd7\x2d\x9e\x7d\
+\x4e\x02\xbc\x08\x48\x7e\x8f\xb6\xd1\xe7\x9b\x3d\xa9\xc3\xe9\xd7\
+\xc9\xe5\x0c\x4c\xd3\x44\x51\x14\x54\x5d\x23\x3e\x18\xc3\x34\x4d\
+\x64\x49\x26\xd8\xec\x07\xed\xc5\xa9\xde\x65\xdd\xe7\xd1\xd8\x10\
+\xa9\xf3\x09\xd4\x22\x95\x2e\x60\x96\xe2\x02\x85\x99\x54\xce\x14\
+\xd8\xf3\xb3\x7a\x3a\xb0\xf0\x09\x20\x17\x8b\x76\xb0\x6c\xfd\x97\
+\x51\x34\x8a\xd5\x1f\xa6\xca\x04\xda\x82\x95\x63\x56\x22\x8b\x69\
+\x8a\x46\x99\x55\x25\x9c\x04\x9c\xae\x99\x8d\x3c\x4d\x73\xe0\xf5\
+\x38\x48\xa5\x72\x98\x96\x8d\x2c\x89\xa9\xe4\x65\x48\x85\x71\x1c\
+\x8d\x27\xfb\x94\xd3\x8f\x05\x4f\x00\x9f\x57\xbd\xd4\x1f\x0c\x4d\
+\x3f\x62\xdb\xf5\x79\x7f\xa7\x10\x66\xb1\x88\xaa\xce\x6c\x3f\xcb\
+\x92\x84\xa2\x2a\xb4\xb7\x87\x66\xbe\xb8\xdc\xfa\xd6\x2c\xa3\x92\
+\x39\xc6\x42\x1f\x05\xc8\x86\x61\x4a\xda\x0c\xf9\xfa\xd5\xf1\xd8\
+\x94\x0b\xdc\x4e\x11\xa2\x05\x64\x49\xc6\xe9\x7b\xe1\xa2\x91\xb5\
+\x68\x68\x6c\x22\x9d\x4e\xce\x7a\x5c\x51\x4f\xf2\x93\x16\xe3\x27\
+\x77\xde\x69\xc6\x42\x97\x00\xb3\xc3\x28\x82\x3a\x83\x8e\x2e\xab\
+\x04\xc3\x9c\x55\xf7\x17\xa3\x19\x12\x29\x31\x2c\x74\x3a\x35\x3c\
+\x11\xdf\x34\x69\x62\x18\x06\x33\x11\x4f\xd5\x5e\x78\x59\x1b\x71\
+\x83\x18\x00\xd9\x5c\x25\x41\x75\x5e\x56\x2c\x59\xe8\x12\x60\x76\
+\x14\x8b\x27\x3e\x3e\xb5\xf1\x15\x1d\x5c\xdd\xc0\x32\x52\xf9\x6a\
+\xbf\xc8\xe7\x8d\xfa\x2c\xe2\x12\xb2\xd9\x02\xd6\x0c\x52\xc6\xf9\
+\x42\x49\x28\xa5\xc4\x50\x0a\x89\xd2\x7d\x52\x3c\xb8\x87\xfd\x54\
+\x6b\x0a\xce\x29\x09\x16\x2d\x01\x8a\x86\x21\xec\x80\x93\x85\xe4\
+\x01\x14\xb0\x2c\xd4\x52\x0e\x7f\xe5\x5e\xc5\xaa\x25\x5f\xce\xfa\
+\xb1\x2d\x9b\x54\x3a\x5b\x97\x05\xa4\xaa\x0a\x6a\x0d\x01\xcc\xa2\
+\x49\x3e\x5f\xac\xdb\x90\xdd\x60\x66\xc0\xcc\x60\x16\x2a\x76\x80\
+\x49\x75\xb9\x9a\x39\x25\xc0\xe2\x55\x01\x20\x26\x7c\x3a\x1c\xa2\
+\xd0\x83\x56\x9a\xf9\x61\x0c\xce\xec\xff\xb7\xc5\xb2\xf6\xc8\x32\
+\x66\x49\x3c\x57\x3e\x52\x8d\x2a\x29\x8f\x20\x40\x90\xc0\x30\x8a\
+\x38\x4a\x99\xc7\x53\x47\x23\x8a\xaa\xa0\x4c\xfd\xc2\x9a\x1f\xf2\
+\x13\x00\x24\x53\x51\xfa\x06\xd3\x87\x11\x8d\x3f\x2f\x4b\xd4\x2c\
+\x78\x09\x70\xc2\xf5\x04\xcb\xb3\x7d\xe5\x6a\xa5\x0f\xe4\x59\x12\
+\x43\xcc\x14\xe4\x7b\x41\x1a\xc0\xa5\xe5\x2a\xbb\x1d\x9a\x8a\x33\
+\x34\xbb\x23\xa7\x9c\x08\x2a\x49\x32\x1e\xcf\x89\x1d\x3e\xe5\x49\
+\xa2\xe4\x07\x01\x48\x24\xa2\x4c\x26\xf2\xe3\x54\x57\x2f\x9b\xf3\
+\xc5\xaa\x16\xbc\x04\xb0\x6d\x12\xd9\x4c\x06\x4a\x52\x3b\x9b\x35\
+\x48\xa4\x73\xc8\xb2\x84\xcf\xeb\xc2\xeb\x71\x83\x95\xaa\x66\x04\
+\x59\x89\x13\xdc\x4c\x10\xc6\x19\xf6\xe0\xf4\xeb\x42\x85\xbc\x80\
+\x93\xa8\xac\x1e\x42\xa1\x40\x35\xfd\x6b\x36\xe8\x2d\x42\xfc\x97\
+\xf2\x03\xa3\x93\x63\xf4\xf6\xa7\x7b\x39\x8d\x8b\x4f\xbc\x10\x16\
+\x3a\x01\xec\x74\xb6\xf8\xc7\x4c\x3a\xf5\x1a\x10\x8d\x31\x3a\x59\
+\x15\xef\xb9\x7c\x11\xb7\xdb\x85\xec\x97\xc1\xea\x2d\x5d\x51\x1f\
+\x8c\xa9\xb5\xf8\xdd\xba\x13\x57\x83\x47\x58\xfc\x27\x18\xce\x29\
+\x35\x72\xdd\xb2\x2d\xb2\x79\x18\x19\xcf\xa0\xc8\x59\x9a\x23\xbe\
+\xe9\xfe\xff\x32\x5c\x6d\x90\xed\xab\xfc\x39\x39\x39\xcc\x6f\x1e\
+\x29\xec\xa7\xbe\xa6\xf0\x9c\x62\xa1\xab\x00\x2b\x9e\x32\xf6\x4f\
+\x4c\x88\x70\x6e\x71\x4a\x5a\xb6\x65\xd9\x64\x32\x39\x28\x5a\xa2\
+\xe1\xa7\x46\xf8\xd2\xf9\x4a\xe3\x03\x64\xb2\x79\xac\x64\xee\x84\
+\x0f\x1c\x1f\x1b\xc3\xe3\x15\x75\x03\x1c\x4e\x0d\xdd\xed\xad\xa4\
+\x9f\x99\x96\xcd\x64\x3c\x3b\xf3\x85\x8a\x5b\xe4\x02\x26\x45\x72\
+\x68\x2a\x1e\x25\x16\xcd\x4c\x1c\xea\xe7\x30\x62\xa9\xba\x79\x51\
+\x01\x0b\x9e\x00\xfb\x0f\x47\x9f\x4e\x25\x85\x58\x57\x66\xf0\xfc\
+\xa9\xb2\x04\xd9\xcc\xcc\x17\xcf\x30\x8c\xcb\xe6\x4e\xec\xa1\xf3\
+\xfb\x75\xc2\xa1\x00\xcd\xcd\x8d\x84\xc3\x41\x54\x55\xad\x1b\x25\
+\x4c\x83\xec\x04\xf7\x72\x08\x5f\x0a\x46\x0e\x34\xe1\x21\x1c\x1c\
+\xec\xe1\x58\x7f\xf2\x10\xa2\x90\x74\x86\x93\xac\x23\x7c\xaa\xb1\
+\xe0\x09\x70\xdb\xbd\xd6\xb1\x7c\xbe\x30\x90\x88\x47\xd1\x34\x85\
+\x86\x90\x5b\x0c\xc7\xd4\xea\xff\x8b\x46\x11\x72\xd3\x53\xb4\x66\
+\xf2\x14\x3b\x5f\x20\x9b\x48\xd3\x14\x34\x87\x5a\x09\x01\x7b\xdd\
+\xf5\x89\xa2\x7e\x6f\x8d\x67\x51\x92\xc1\xb5\x04\x64\x1d\xb4\x30\
+\xe4\x46\xc4\xbf\x8e\x46\xc6\x46\x06\xf8\xd6\x5d\xd1\x5f\x20\xd6\
+\x1d\xca\x31\x4f\x53\xc4\x17\xbc\x0d\x00\x14\x33\xb9\xe2\xee\xe3\
+\xc7\x7b\xff\x6c\xfd\x86\x10\x5e\xb7\x13\xaf\x7b\x7a\xa0\xc6\xcc\
+\xe6\x50\x54\xad\x4e\xb7\x4b\x5e\x17\xce\xac\x41\x3e\x2f\x7a\xbd\
+\x43\x53\x51\x83\x27\x98\x2b\x58\x9c\xde\x41\x25\x59\x22\x12\x76\
+\xa3\x39\x5c\xa8\x8a\x88\x03\x54\x20\xfb\xc4\xec\x63\xbd\x0d\x72\
+\xc7\xc1\x16\x92\x62\x72\x2c\xc1\xe8\x58\x74\xe2\xc8\x10\xcf\x20\
+\x0a\x48\x67\x11\x04\x98\x73\x2c\x74\x09\x60\x03\xc5\xa1\xf1\xdc\
+\x2f\x46\x47\x06\x4e\x7c\xa2\x6d\x63\x67\x52\xf5\xce\x21\x49\xc2\
+\xd3\xe4\x27\xdc\x12\x24\x1c\xf1\xe3\x6d\x09\xbc\x40\x00\x69\xf6\
+\x0e\xea\x74\xaa\x28\x9a\x4b\x88\x7b\xcf\x79\xa2\xee\x80\xac\x88\
+\xd4\x30\xd9\x01\xf9\x52\x56\x72\xb1\xc8\xd8\xd8\x10\x3f\xbe\xff\
+\xf8\x3d\x88\xc6\x2f\xaf\x35\x34\xe7\x06\x20\x2c\x7c\x02\x00\x98\
+\x1f\xba\x25\xfa\x93\x64\x3a\x33\x38\x3e\x31\x7a\xe2\x13\x8b\x16\
+\xa4\x66\xb0\x07\x34\xa5\x3e\x39\x44\xf1\x82\x6b\x15\xf8\x5f\x09\
+\xbe\x4b\xab\x25\xe4\x2c\x93\x89\xf1\x31\x42\xa1\xc6\xe9\xf7\x05\
+\x31\xc6\x2f\x97\x97\x75\xf8\xa1\x98\x04\x67\x13\xa4\x7b\xc5\x3e\
+\xdb\x22\x36\x39\x4e\x7f\x7f\xef\xc4\x77\xee\xe3\x97\x88\x35\x86\
+\x52\x08\xfd\x7f\xae\x42\xc8\x4b\x84\x09\xe4\x63\x89\xc2\x0f\x8f\
+\x1c\x3a\xf0\x82\x27\x17\x0d\x03\xd2\x99\xd9\x4f\x90\x1c\xc2\x6b\
+\xa8\xfa\xc5\xe7\x91\x4a\x45\x25\x15\x2f\x98\x33\xb7\x51\x2a\x93\
+\x63\x60\x20\xca\x40\xdf\x00\xe9\x44\x1a\x6c\x0b\xcc\xbc\x48\x01\
+\x8f\x3f\x55\xa9\x12\x62\xe6\x0d\x7a\x9f\xdd\xc3\x1d\xdf\x7b\xe0\
+\x1e\x44\xe3\xc7\x98\x47\xf1\x0f\x8b\x83\x00\x16\x50\xf8\xe7\x6f\
+\x8d\x7c\x7d\x62\x62\x3c\xf5\x42\x52\x00\xa0\x98\x2f\xcc\x4e\x82\
+\x8a\xc3\xa8\x66\x34\x60\x17\x40\xf6\x63\xe5\xa6\x0f\x11\x0d\xc3\
+\x62\x32\x9a\xa1\x68\x5a\x98\x85\x34\x93\x23\x07\x89\x0d\x1d\x82\
+\x42\xe9\x3d\x0a\xe3\x90\x7d\x0e\x2b\xdd\xc3\xa1\xbd\xf7\xf2\xcc\
+\x81\xdf\xf5\xdf\xf1\xb3\xc2\x7d\x88\xc6\x4f\x72\x8a\x96\x9a\x7b\
+\xa9\x58\x0c\x04\x00\x28\x1e\x1e\x64\x68\x6c\x32\xf7\xed\xa3\x33\
+\x48\x01\xc3\x30\xe9\x1b\x8a\xd1\x37\x14\xab\x94\x7a\x3b\x21\x09\
+\x00\xac\xac\x48\x17\x37\x46\xa1\x98\x00\x59\x21\x16\x9b\x20\x18\
+\xac\x17\xff\xe5\x61\xa3\x59\xca\x06\xb6\x4d\x83\x42\x61\x4a\x46\
+\xb0\x6d\x31\x3a\x70\x8c\x81\xe3\x07\xb8\xe9\x4b\xfd\xdf\x00\xa2\
+\xa5\x2d\x8d\x18\xff\xcf\x1b\x16\x0b\x01\x2c\x20\xff\xfe\x4f\x8d\
+\xdd\x3c\x32\x3a\x3e\x74\xec\xd8\xe1\xba\x83\xa9\x4c\x01\xcb\xb2\
+\xb1\x2c\x9b\x58\xb2\x80\xe2\x5e\x8a\xea\x5b\x83\xad\xb4\x62\x27\
+\xd2\xf5\x86\xa1\x5d\xd3\x78\xb6\x51\x9d\x5e\x66\xa6\x88\x27\xa7\
+\xbb\x91\x15\x55\x18\x8d\x56\x4d\x90\x48\x9e\x62\x47\xa6\xe2\x09\
+\x8e\x1c\x7a\x9c\x7b\x7e\x7d\xfc\xfb\x47\x86\x78\x1a\x18\x47\x48\
+\x80\x1c\xf3\xd8\xfb\x61\xf1\x10\xc0\x46\xf4\xa4\xe4\x23\x7b\x46\
+\xff\xef\xb3\x47\x0e\x92\xcd\x54\x7b\xb7\x56\x53\x14\xca\xed\x6b\
+\x42\x52\x44\xd0\x46\x52\xdc\xa0\x36\x62\xc6\x12\x50\x76\xe6\x98\
+\xd9\xe9\xf3\x08\xcd\x14\x18\x93\x64\x52\x29\x42\xe1\xfa\xaa\xe2\
+\x1e\xdd\x81\xd3\xa1\x62\x96\x9c\x4a\x8a\x2c\x11\xf4\x57\x87\x92\
+\xc5\x82\xc1\x53\x7b\x1e\xe6\xb1\xa7\x06\x1e\xfd\xb7\xbb\x8d\x1f\
+\x20\x4a\xc3\x4d\x22\x7a\xff\xbc\x58\xfe\xb5\x58\xe8\x7e\x80\x5a\
+\x58\x40\xee\x4b\x3b\xf3\xf7\xaf\x5e\x96\xbc\xeb\xf1\xc7\x7e\x77\
+\xcd\x15\x57\xbe\x16\x00\xaf\xdb\x59\x71\xf0\x38\xbd\x53\x7c\x04\
+\x92\x82\x6d\xdb\x14\x13\x49\x64\x45\x25\x91\x32\xc0\x8e\x12\x6c\
+\x0c\x82\xc7\x0d\x96\x25\x26\x97\x9a\x16\x89\x78\x8c\x50\x78\x7a\
+\x55\xf1\xa6\x46\x2f\x45\xd3\xc6\xe7\xf7\xe1\xd0\xe4\x4a\x50\xc8\
+\x2a\x9a\x1c\xd8\xb7\x9b\xfe\xa1\xf1\xfe\x0f\x7d\x29\xfa\xef\xc0\
+\x28\x82\x00\x71\xe6\xc9\xf3\x37\x15\x8b\x45\x02\x40\xc9\x27\x00\
+\xa4\xae\xff\xec\xd8\x8d\xe3\x13\x93\x47\xf7\x3e\xf9\x58\xe5\xa0\
+\xa6\x29\x68\x9a\x82\x5d\x4c\x56\x1c\x32\x00\x96\x51\x9d\x03\x38\
+\x19\x4b\x11\x8d\xc7\x29\x18\x05\xd2\x89\xa4\x90\x06\x76\xc9\x83\
+\x58\x2c\x92\x48\xc4\xf0\xcc\x92\x82\xae\x2a\x12\xaa\x42\xa5\xf1\
+\xf3\xb9\x1c\xfb\xf6\x3e\xca\x33\x87\x9f\xed\xbf\xe6\xc3\xfd\x9f\
+\x00\x7a\x10\x2b\x87\x45\x11\xa2\x7f\xde\x7b\x3f\x2c\x2e\x02\x40\
+\x69\x44\x00\xc4\xbf\x71\xd7\xe0\xfb\xfa\xfb\x7b\x53\x7d\x7d\xbd\
+\x75\x27\xd8\x66\x1e\x33\xdd\x83\x99\x7d\x1e\x33\xf5\x2c\x76\xb1\
+\xaa\x2a\x9c\xaa\x82\x65\xda\x24\x53\x19\x0c\xa3\x20\x12\x4a\x4a\
+\xc8\x25\x12\x24\x93\x49\x4e\x34\x07\x21\x95\xcc\x50\x2c\x18\xa4\
+\x93\x49\x9e\xf8\xc3\x6f\xca\x8d\x7f\x13\xa2\xf1\x87\x81\x09\x84\
+\xe8\x9f\xb7\x61\xdf\x54\x2c\x26\x15\x50\x86\x09\x64\x7f\xbb\x97\
+\x03\xe1\x50\xdf\xb5\x36\xdc\x0e\x78\x3b\x3b\xbb\x2a\x27\xd8\xb6\
+\x01\xc5\xe9\xc6\xb7\xae\x6b\xb4\x35\xf9\x31\x6d\x1b\x97\x43\xa5\
+\x98\xac\x1a\x84\x87\x8f\x1c\xc5\xe5\x0e\x9f\xf0\xc1\xb6\x6d\x31\
+\xd8\x7f\x9c\xa3\x47\x9e\xe4\xa9\x83\xa3\xbf\xff\xd0\x2d\xd1\xaf\
+\x20\xc4\xfe\x30\x42\xf4\xa7\x10\xb6\xca\xbc\x38\x7d\x66\xc2\x62\
+\x24\x40\xd9\x20\x4c\xfd\xf0\xb7\xec\x32\x8c\xbe\xf7\xfc\x85\xcd\
+\x6d\x80\xa7\x96\x04\xb3\x41\xd3\x14\x66\x4a\xeb\x3c\xde\xdb\x43\
+\x30\x14\x99\xf5\xba\x62\xd1\xa0\xff\xf8\x51\xfa\xfb\x7a\xb2\xdf\
+\xff\x45\xcf\xce\x3b\xef\xe3\xe7\x88\xc6\x1f\xa1\xda\xf3\xe7\xcd\
+\xe3\x37\x1b\x16\x23\x01\x40\x7c\xe4\x02\x90\xb8\x77\x17\x0f\x43\
+\xdf\x75\xc0\x6d\xf1\x58\xd4\xb3\x7e\xc3\xa6\x17\x7d\xb3\x6c\x36\
+\xcb\xc0\xc0\x10\x4b\xba\x56\xcf\x78\x7c\x64\xa8\x8f\xe1\xa1\x67\
+\x79\xee\xf9\x89\x23\xff\xe7\x93\xc3\xff\x96\x32\x39\x82\xe8\xf1\
+\x63\x88\xe1\x5e\x86\x33\xac\xe7\x97\xb1\x58\x09\x00\x25\xdf\x00\
+\x10\xbf\x77\x17\xbf\x83\xbe\xeb\xde\xf6\x06\xf3\xe6\x6c\x26\xbd\
+\x7c\xe3\xe6\x6d\x33\xe6\xf4\xcf\x86\xc3\x87\x0e\xd1\xd0\xd8\x84\
+\xd3\x51\x3f\x89\x64\x62\x6c\x98\x91\xe1\x5e\x86\x46\x46\x27\x7e\
+\xf2\xeb\xe3\xf7\x7c\xe7\x3e\x7e\x81\x18\xe2\x8d\x97\xfe\x4d\x50\
+\x35\xf8\xce\xb8\xc6\x87\xb3\x63\xed\x60\x19\x51\x04\x26\xe0\x92\
+\x58\xfe\xb5\x9b\x9a\x3f\xdf\xd1\x12\xbc\xf4\xfc\x4d\x5b\x99\x6d\
+\x4a\x79\x2d\x0c\xc3\xe0\x47\x3f\xfc\x01\xdd\x2b\xd7\x13\x0c\x84\
+\xf1\xfb\x3d\x4c\x4e\x0c\x33\x32\xd2\x4b\x2c\x9e\x2a\x37\x7c\x39\
+\xb0\x13\x45\x34\x7c\x9c\x6a\x92\xc7\x8b\xb6\xf6\xcf\x2d\x1e\x7d\
+\xea\x51\x26\x81\x17\x68\xb8\xe9\xfd\xde\x1b\xd6\xaf\x0e\xbd\xbb\
+\x7b\xf9\x4a\xcf\xea\xf3\xd6\x9f\x50\x1a\x3c\xb6\x7b\x17\xa3\xa3\
+\x31\x42\xe1\x08\xb1\xd8\x38\x32\x19\x8e\x0f\x65\xf6\xee\x7e\x7a\
+\xfc\x89\xef\xfc\x82\x5f\x22\x7a\x79\xac\xb4\x25\x10\x0d\xff\xb2\
+\x12\x3c\xce\x11\xe0\xf4\x40\x46\x54\xe2\xf2\x00\xc1\xf3\x3a\xd8\
+\x70\xe3\xfb\x5b\x3f\xd5\x10\xd2\x37\xac\x5d\xbf\x89\xa9\x06\xa2\
+\x61\x18\xec\x7d\x72\x37\x23\x23\x03\x44\xe3\x59\xfa\x87\xb2\x7b\
+\x87\x27\x8c\x83\xdf\xfd\x89\xf1\x68\xdc\xe0\x79\x44\x20\x27\x89\
+\x68\xf4\x24\xa2\xe1\xcb\xc9\x9d\x2f\xcb\xc1\x73\x8e\x00\xa7\xf1\
+\x75\x10\x76\x8f\x0b\xb1\x7e\x5f\xe8\x4f\xaf\xe4\xd5\xd7\xbc\xb1\
+\xe3\x46\x7f\xc0\xdb\x14\x0e\x46\xd0\xdd\x6e\xb2\x99\x0c\xa3\xa3\
+\x83\xec\x3b\x3c\xfe\xfb\x9d\xf7\xc6\x1f\xdc\xd7\xcb\x21\xaa\xc6\
+\x5c\xba\x66\xcb\x20\xc2\xb9\xa7\x34\xaf\xff\x1c\x01\x4e\x3f\x14\
+\x44\x65\x2e\x37\x25\x22\x5c\xb2\x96\x2d\x97\x6d\x71\x6d\xd5\x5d\
+\xaa\x3f\x91\x34\xb2\xb7\xff\x28\xff\x87\x58\x9e\xe3\x88\xb1\x7b\
+\x0a\xd1\xe0\x59\x44\x2f\x2f\x6f\xe5\x54\xee\x53\xea\xd2\x3d\x47\
+\x80\x39\xfa\xed\x08\x69\xe0\x44\x10\xc1\x43\x75\x01\x07\x05\xa1\
+\xc3\xcb\x19\xbb\x59\xaa\xa9\xdb\x26\xd5\x46\x3f\x2d\x1f\xef\x1c\
+\x01\xe6\x16\x32\x55\x89\xe0\xa0\xba\x78\x83\x85\x68\xf0\xf2\xac\
+\x9d\x53\xde\xd3\x67\xc3\x5c\x12\xe0\xff\x03\x10\xe2\x7d\x58\x4a\
+\x1a\xc9\x2c\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\
+\x63\x72\x65\x61\x74\x65\x00\x32\x30\x31\x30\x2d\x30\x32\x2d\x31\
+\x31\x54\x31\x31\x3a\x32\x39\x3a\x31\x39\x2d\x30\x36\x3a\x30\x30\
+\x69\xe3\x50\x98\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\
+\x3a\x6d\x6f\x64\x69\x66\x79\x00\x32\x30\x30\x36\x2d\x30\x35\x2d\
+\x30\x38\x54\x31\x30\x3a\x33\x37\x3a\x32\x34\x2d\x30\x35\x3a\x30\
+\x30\x1e\xe3\xe9\x9d\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
+\x82\
+\x00\x00\x07\xf2\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x32\x00\x00\x00\x36\x08\x03\x00\x00\x00\xb2\x70\x3a\x95\
+\x00\x00\x02\xf1\x50\x4c\x54\x45\x4c\x69\x71\x00\x9a\xd4\xff\xb6\
+\x00\x00\x9a\xd3\x00\xff\xff\x00\xff\xff\xad\x45\x00\x00\x9a\xd0\
+\xff\xcd\x00\x03\x95\xce\x00\xd0\xf9\x00\x83\xc5\x01\xb8\xf0\x89\
+\x1b\x00\x54\x02\x0a\x00\xc8\xf6\x00\xb1\xe5\x03\x9f\xd4\x00\xa2\
+\xdd\x00\xff\xff\xe8\x7d\x00\x00\xe0\xfd\xff\x98\x00\xa4\x32\x00\
+\x00\xde\xff\x1a\x11\x3c\x00\x4a\x82\x00\x8b\xc8\x00\xda\xfc\x00\
+\xa3\xd7\x00\xb2\xea\x2c\x00\x20\x00\xf0\xff\x00\x7a\xc1\x00\x60\
+\xac\x00\xd7\xfc\x00\x90\xc8\x00\xa1\xd6\x00\x72\xad\x00\x96\xcc\
+\x00\xb3\xe7\xab\x56\x00\x00\x84\xbc\x00\x85\xc0\x00\x76\xb4\x04\
+\x03\x36\x00\xb0\xe5\x00\xea\xff\x00\xf6\xff\x00\x2e\x7d\x00\x95\
+\xce\x00\xae\xe2\x00\x9f\xd5\x00\xc0\xf4\x03\x00\x48\x00\x73\xbb\
+\x00\x94\xcc\x00\xff\xff\x00\xee\xff\x00\xec\xff\x00\x8f\xcb\x02\
+\x00\x4f\x00\x37\x8c\x00\xd3\xf9\x00\xd9\xfb\x00\xa4\xd9\x00\xb9\
+\xee\x00\x89\xc0\x00\x80\xb8\x00\x3c\x7d\x00\xbb\xed\x00\x51\x8c\
+\x00\x4e\x8c\x00\x8a\xd0\x00\xcc\xf4\x1d\x00\x0e\x00\x85\xbc\x00\
+\x9f\xd5\x00\xc5\xf3\x00\x9f\xd5\x12\x00\x36\x00\xe1\xff\x00\xdc\
+\xfa\x00\xe7\xff\x00\xd8\xf9\x00\x46\x99\x00\xf0\xff\x00\x9a\xda\
+\x00\xe0\xfd\x00\xde\xfd\x01\x9c\xd1\xff\xff\xff\x01\x9b\xd1\x01\
+\x9a\xd0\xff\xf6\xe9\x00\x9c\xd8\x02\x98\xcf\x22\x85\xc4\xff\xff\
+\xfd\x01\x9c\xd4\x00\x9d\xdb\xfd\xff\xff\xf7\xff\xff\xff\xfe\xfa\
+\xf2\xff\xff\x00\xa2\xe1\x01\xa0\xd7\x00\xa7\xe5\xff\xf9\xee\x03\
+\xaa\xe1\xed\xff\xff\xfb\xf8\xf6\xff\xff\xfb\x0a\xc4\xf5\x01\xab\
+\xe7\xff\xfe\xf7\x14\xac\xdd\x02\xa6\xdb\xde\xfd\xff\x33\x9d\xc9\
+\x01\x9c\xd2\x02\xae\xea\x14\x89\xc8\x18\xd9\xfb\x3b\x86\xc0\x3c\
+\xbb\xe4\x07\xa3\xd8\x2d\xa5\xd3\xb4\xf7\xff\x33\xd6\xf9\x16\x91\
+\xc8\xf4\xe6\xda\x2e\x85\xc2\x2a\xb2\xdf\x0f\x94\xca\x74\xd1\xef\
+\x9a\xcf\xe6\x17\xb2\xe3\xfb\xf2\xea\x21\xdb\xfc\xee\xeb\xed\x03\
+\xb2\xeb\x4d\xe7\xfe\xc6\xfc\xff\x0f\x8d\xc9\xb9\xfa\xff\x08\x92\
+\xcd\x3a\xe3\xfd\xe0\xe3\xea\x0b\x9b\xcf\x1e\xb8\xe6\x67\xb9\xdb\
+\xca\xbf\xc7\x89\x93\xb9\xe2\xef\xf6\xc5\xe5\xf1\xa1\xe1\xf1\x14\
+\x9c\xcf\x5d\xd8\xf7\x0f\xbc\xec\x48\xbf\xe5\x6b\xef\xff\xb6\xae\
+\xbf\x54\xad\xd6\x21\xa6\xd5\xd7\xd2\xd7\x57\xeb\xfe\xdc\xc6\xc5\
+\xa1\xf9\xff\xab\xf7\xff\x1d\x87\xc6\x29\x85\xc3\x00\x9f\xe0\xf9\
+\xed\xe1\x6c\x87\xb8\xb7\xca\xd8\x3a\x96\xc5\xe4\xfa\xfe\x61\xe4\
+\xfc\x74\xd8\xf5\x73\xdd\xf8\x25\x94\xc5\x35\xc9\xf1\x3d\xa5\xcf\
+\xdc\xda\xe1\x47\xc7\xee\x9e\xed\xfc\x8c\xe4\xf9\x7a\xc0\xd9\x1f\
+\xac\xdb\x3b\xad\xd8\x40\xdd\xfc\x6d\xb3\xd4\xbf\xf1\xfd\xb7\xbe\
+\xcd\x15\xcd\xf8\xc2\xce\xdc\xd1\xfc\xff\x89\xb1\xca\x6f\xbe\xdd\
+\x53\x9d\xc4\xd0\xbf\xc3\x73\xb1\xd1\xa7\xcb\xdf\x2e\xbe\xea\xae\
+\xea\xfa\x61\xcd\xec\x20\x9b\xcb\x93\x9a\xba\x80\xf3\xff\xe9\xd6\
+\xd1\x84\xec\xfa\x1f\xc0\xed\xaf\xfa\xff\x96\xf8\xff\x8a\xf3\xfe\
+\xef\xde\xd6\x4f\x85\xbd\x62\x86\xba\x82\xe0\xf8\xf3\xf7\xfb\xef\
+\xe6\xe4\x5d\xb3\xd7\xb4\xd3\xe3\xc5\xd2\xdf\x42\x9d\xc7\x66\xad\
+\xd0\xb2\xc1\xd1\xce\xf7\xf9\xab\xb3\xc7\x1d\xc5\xf3\xba\xdf\xee\
+\x4a\xb2\xda\xb7\xe3\xe7\x9b\xbd\xd2\x09\xa0\xd3\xd3\xf2\xfb\x22\
+\xce\xf5\x49\xab\xd3\xe9\xf7\xfb\x4a\xce\xf3\xb0\xd3\xe5\xe0\xd7\
+\xd7\x7f\x8d\xb7\x50\xd7\xf7\xb9\xee\xed\x7c\xf0\xfd\x77\xf2\xff\
+\x8c\xc4\xe8\x23\x8d\xc4\x73\x87\xb7\xb1\x03\x50\xb6\x00\x00\x00\
+\x5a\x74\x52\x4e\x53\x00\xfe\x09\xfe\x02\x08\x24\xfe\x04\xfe\xeb\
+\xef\xfd\x39\x40\xf1\xe9\xfe\xfe\x0c\x1b\x8d\x13\x2f\x44\x17\x4e\
+\xf3\x69\xac\x72\x33\x39\xe7\xc4\xd0\xe5\xf3\x61\xad\xce\x0c\xc2\
+\xe6\xcd\x46\xf2\x24\x1b\x8a\xca\x93\xc0\xf6\x62\xde\xf9\x15\x5c\
+\x6d\xf7\x67\x9b\xd9\xc9\xd5\x3e\xb0\xae\x37\xa2\x64\x7f\x88\x64\
+\x23\xd4\xe3\xb6\xd2\x54\x7a\x76\x80\x98\xaa\x46\xfe\xa8\xad\x28\
+\xfe\xa4\xfc\x00\x00\x04\x56\x49\x44\x41\x54\x78\x01\x95\x96\x03\
+\x94\x24\x49\x10\x86\x73\x98\x6b\xdb\xb6\xad\xb3\x6d\x65\xd5\x6c\
+\xf7\xd8\xb6\x6d\xdb\xb6\x8d\xb5\x6d\xef\xd9\xb6\xf1\x74\x91\x95\
+\x95\x35\x35\xd3\xaf\xf7\xfa\xfe\xc7\xc6\xf7\x32\xfe\xf8\x23\xb2\
+\x0a\xe9\xd1\xcc\xa7\x9e\x36\x45\xff\x43\x23\x66\xad\xd2\x62\xbc\
+\xee\xf1\x0d\x86\xfe\xff\xb1\x47\x67\x63\xa6\x49\x2b\x66\xa0\xff\
+\xd4\xca\x65\xab\xe8\xff\x09\x93\x88\x8d\x26\x6c\x5e\x8f\xf4\x6b\
+\xe6\xb2\x47\x96\x60\xf9\xff\xbe\x17\x4b\xce\x47\x11\x10\xc6\x26\
+\x4b\x1f\x9a\x3b\xef\x61\x33\xa4\xd2\xfc\x4d\x5b\xe6\x2e\x5f\xbe\
+\x69\xc9\x0e\xcc\xff\xef\x94\xd8\xe3\x26\x08\x82\xfd\x49\x7f\x2d\
+\xa3\xa8\x26\x2e\x7d\xee\x85\x2d\x73\x37\xcf\x9b\x0c\xa5\xdf\x87\
+\x65\x89\x84\x2a\x2c\xaf\xbb\x53\xe0\x4a\xf9\x24\x4e\xa9\x91\xc9\
+\xeb\x75\x84\x8c\xef\x57\x4a\x27\x61\x3e\xfb\xf6\xdb\x09\x43\x64\
+\x95\x73\xb0\xbf\x74\x24\xe1\xc2\x2d\xbb\x11\x9a\xb6\x08\x88\x80\
+\x89\x61\x87\x42\xe3\x8f\xd9\x0a\x7a\xe4\x5e\x9c\x90\xff\x55\x78\
+\x4d\x29\x21\x9a\x37\x01\xd9\x8e\xad\xb3\x05\xc3\xd4\x1a\x44\x4c\
+\x16\x23\x64\x3e\x1a\x6b\xea\x0c\x44\xf6\x36\x13\x3c\x0a\xa1\xe9\
+\x7b\xb0\xcd\x35\x03\x11\x97\x10\x40\x4c\xd1\xd8\x91\xd8\xf1\xb6\
+\xa0\xa3\xf7\x8a\x3a\x6d\x75\x91\xb7\x19\x82\xb1\x63\xc3\xd0\x1e\
+\x1d\x0d\xcf\xb5\x90\xf2\xf1\x09\x3d\xe0\x20\xa8\xe4\xfd\x0e\xc1\
+\xe3\xcd\xd0\x38\x2c\x3a\xab\x10\xfb\xf6\x5e\xa2\x96\x6f\xdf\x29\
+\x15\xf2\x8f\x82\xdc\x52\xbe\x7c\xbf\x83\x0c\x97\x6b\x97\xad\x82\
+\xb4\x29\x48\xa3\xc0\xe4\x71\x3c\x83\x10\x56\x52\x6c\xe8\xe1\x70\
+\x39\x79\x4d\xa4\xdd\x50\x04\xbc\xf0\xc2\xac\xaa\xb4\xd2\xff\xf3\
+\xde\x95\x1d\xe4\x1c\x8d\x92\x98\x23\xb6\x1c\x61\xf6\xb5\x4a\xc7\
+\xa2\x83\x08\x0c\x42\x79\x84\x3a\xf6\xd3\x59\xf0\x9d\x57\x04\xb7\
+\xcf\x72\xd9\x81\x5b\xee\x30\xe7\x85\x04\xe4\xe9\x36\x74\xc6\x0a\
+\x60\xc0\xf8\x78\x40\x93\x4d\x1e\x60\x51\xfe\xc9\xb2\x8d\xa1\x45\
+\xa4\xe9\x26\x4e\x48\xb0\x12\xa5\xd1\x83\xea\x81\xd9\x6b\x49\xeb\
+\x4a\x1e\x96\x60\x74\x06\xfd\x52\xc6\x9b\x88\xe6\x2d\x84\xa6\x2c\
+\xa4\x63\xa9\x20\xc4\x91\xf7\x94\x29\x29\x04\xbe\xb3\xa9\x54\xc6\
+\xf2\x8d\xa9\x6c\xf8\x83\x6d\x07\x11\xa2\xb9\x39\xe8\xdf\xfd\x9b\
+\x28\xb5\xbf\xbb\x59\xa2\xe3\x2e\x69\xc5\x88\xa7\x83\x0a\x01\xb7\
+\x3e\x15\xf1\x25\xc5\x5f\x26\x9c\xf1\x67\xbb\x95\x9a\x29\x30\xd5\
+\x5b\x88\x81\x0b\xa4\x45\x26\x63\xfc\x38\x12\x60\x41\x74\x54\xfe\
+\x7b\xf5\x1d\x39\x37\x22\x8e\xd9\x89\x90\x12\x3f\x35\x47\x02\xbf\
+\x3e\xab\x1d\x36\x2f\xbf\x39\x54\xc9\x1d\xb3\xff\x15\x62\x31\x43\
+\xac\xcb\x95\xfc\x14\x18\xb7\x94\xa3\x57\x14\xca\x29\xb1\xcc\x4d\
+\xa8\xcd\x08\x08\x56\xf7\x58\x6a\x19\x34\x91\x7b\x91\xb2\xb7\x2a\
+\x4a\x38\xf3\x43\x6c\x45\xfc\x25\x3b\x96\x3f\x0b\x8b\xf6\xdb\xe6\
+\x15\x8a\x18\xbf\x46\xfd\x2b\xf6\x35\x79\x07\x54\x97\x4c\xca\x79\
+\x3a\x9b\x63\x4e\xa9\xdd\x83\xff\x6d\x98\x04\xa6\xab\x3b\xe6\x1b\
+\x7b\xb9\xe4\xbb\x1f\x8b\x8a\x13\xaa\xaf\x10\x2a\xc7\x3f\xf8\x40\
+\x89\x5b\x27\x53\x04\x56\x59\x04\x33\x1c\xd1\x95\xd7\x8d\xbf\x32\
+\xe5\x81\x32\x82\xfb\x45\x36\x43\x4e\xd8\x32\xc4\xeb\x60\x14\x51\
+\x8b\x26\x9b\x5e\xaf\x95\xbc\x5e\x1f\x49\xe8\xc5\x27\x99\xd9\x88\
+\x49\x6a\x3a\x1b\x4b\xaf\x4c\xfb\x0b\x72\x80\x84\x5d\xe7\x8d\x42\
+\x6b\xb3\xd4\x64\x8f\xab\x90\x0a\x7f\xe2\x8c\xd3\x8a\x9a\x34\x36\
+\xfc\xd6\xdf\xbb\x41\x93\x7e\xce\x3f\x5c\x53\x73\x08\x5a\x06\xf7\
+\xa7\x55\xed\xdf\x44\x93\x4d\xa7\xcd\x52\xba\xf7\xe4\xca\xb6\x63\
+\x3a\x46\x6c\xc5\x5c\x7f\x82\x51\x50\xe4\x71\xd2\x9f\xba\xa1\x5e\
+\xce\x59\x10\xe7\x5d\x0c\x60\x3d\x73\xbc\x46\xd7\x49\xcb\xf2\xbb\
+\x0c\x81\xd0\x0e\x5f\xa8\x96\xd6\xdf\x26\x8d\xe5\x28\xc2\xde\x73\
+\x99\xcf\x66\xd1\x78\x7c\xce\x6d\x04\x4c\xfc\x36\x97\xcf\x80\xcd\
+\x11\x3b\x7a\x88\x96\xd8\x4c\xe5\x00\x6b\x80\x0d\xec\x19\x14\xde\
+\x4b\x86\x2b\xf0\x86\xb4\x37\x31\x10\xca\x4e\x85\xa0\xab\xc9\x9a\
+\x06\x07\x7d\xd4\xaf\x34\x8c\x3d\xd0\x1c\xa4\x18\xaf\x42\xa2\x2f\
+\x0d\x79\x0a\xbf\x88\xc5\x80\x13\x0e\xf2\x5e\x7d\xf8\x41\x78\x69\
+\xae\x38\x31\xcc\x27\xb4\xdb\x4f\xb9\x34\xe0\xa2\x50\xbf\x08\xb0\
+\xdd\xb4\xe6\x57\x9c\xae\xae\x67\x40\x26\xfc\xd1\xac\x2e\x4d\xa3\
+\x8f\xb9\xdb\x44\x44\xe7\x39\x3a\x2f\x08\xf3\xf7\xe8\x65\x7e\x09\
+\x22\xe2\x24\xe8\x96\x0e\xb3\x76\x24\xd4\xf6\x85\x3a\x47\x7e\x51\
+\x67\x01\xf1\xaa\x2e\x41\x19\x38\x87\xa4\xee\x1f\x46\x24\x9d\x25\
+\x44\x74\xe4\x67\xe8\xd4\x06\x7e\x88\x53\x9f\xfa\x20\xfb\x4f\x33\
+\x80\x70\x7d\x19\xe9\x93\xf9\x1a\x0c\x90\x6b\x99\x03\xbf\x90\x07\
+\xe8\xe3\x06\xaf\x5b\x70\xaf\xb7\x9e\x27\xc1\x10\x85\xdc\x24\xe0\
+\x63\x0b\x02\x36\x9e\x9f\x8c\xee\xa9\x67\xe8\x41\x00\xed\xfb\xac\
+\x9d\xce\xb0\x68\x34\x61\x86\x01\xaf\x6f\xab\x07\x5f\xae\x4c\x26\
+\xcc\x31\xe8\xdd\xcf\xf8\x09\x80\x44\x90\x91\x81\x00\x3b\xe9\x59\
+\x8c\x7d\xd9\x2b\x9f\xe1\x9a\x39\x4f\xaf\xe9\x7f\x01\xd6\x59\xed\
+\x39\x30\x66\x52\x98\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
+\x82\
+\x00\x00\x2e\x0a\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\
+\x00\x00\x2d\xd1\x49\x44\x41\x54\x78\xda\xed\x5d\x07\x80\x53\x55\
+\xd6\xfe\x92\x69\x99\xc9\xf4\x4c\xef\xbd\x51\x86\x32\x30\xf4\x5e\
+\x15\x45\x96\x62\x61\x15\x5c\x75\x5d\x75\x5d\xfd\x2d\xab\xeb\xea\
+\x5a\x56\x45\x57\x54\x14\x45\xb0\xac\xa0\x2e\x62\x5b\xc5\x15\x04\
+\x44\x41\x50\x41\x8a\x34\x85\xa1\xce\x0c\xcc\x0c\xd3\x7b\xcd\x4c\
+\x92\xff\x9e\xfb\x92\x4c\xf2\xf2\x5e\x26\xa1\x65\x80\x39\x78\xcd\
+\xcb\x9b\x97\xe4\xdd\x77\xbe\x7b\xda\x3d\xe7\x5e\x05\x7a\x48\x8e\
+\xbc\x58\x4b\x60\x2d\xc6\xd8\x22\x58\x53\xc9\x5c\x5b\x6b\x6c\x05\
+\xac\x15\x19\x5f\x75\xae\xee\x80\x23\xa4\x70\xf5\x0d\x74\x13\x52\
+\xb3\x96\xc3\xda\x08\xe3\x6b\x86\xb1\x9d\x29\x75\xb0\x96\x67\x6c\
+\xdb\x59\xfb\x81\xb5\x5d\xe8\x86\xa0\xb8\x9c\x01\x90\xcc\xda\x2c\
+\xd6\xa6\xb0\x36\x04\xf2\xa3\xfb\x5c\x51\x23\x04\x20\x7c\xc1\xda\
+\x57\xac\x15\xbb\xfa\x01\x10\x5d\x6e\x00\xd0\xb0\x36\x9f\xb5\xdf\
+\xb3\xd6\xcf\xc5\xf7\x42\x60\x58\xce\xda\x2a\xd6\x9a\x5c\x75\x13\
+\x97\x0b\x00\x06\xb2\xf6\x00\x6b\xd7\xe0\xfc\x8f\x74\x67\x89\x24\
+\xc3\x07\xac\x2d\x62\xed\xf0\x85\xfe\xf1\x4b\x1d\x00\xa3\x59\x7b\
+\x82\xb5\x31\x67\xf2\x61\x8d\x26\x04\x71\xf1\xf1\x88\x8a\x8e\x41\
+\x70\x70\x30\xfc\xfd\xfd\xd9\x13\x53\xc0\xcb\xcb\x0b\xda\x36\x2d\
+\x1d\x72\xaa\xa9\xa9\x46\x4d\x75\x0d\x8a\x8b\x4e\xa1\xb0\xb0\x80\
+\xbd\xaf\x39\xd3\xfb\xfd\xc2\x78\xbf\xfb\x2e\xd4\x03\xba\x54\x01\
+\x90\x0d\x61\x44\x8d\x71\xf4\x03\xc1\x1a\x0d\x72\x72\x06\xa3\x4f\
+\x76\x3f\xf4\x65\x2d\x35\x2d\x1d\x6a\xb5\x9a\x31\x59\x21\x6a\xe8\
+\x3c\x86\xe8\xbd\xf1\xef\x0d\xf5\xf5\x38\xb0\x7f\x3f\x0e\x1d\xfc\
+\x0d\x3b\x77\xee\xc0\xb6\x6d\x3f\xf1\x73\x4e\x10\xa9\x85\x47\x59\
+\x3b\x7e\xbe\x1f\xd4\xa5\x06\x80\x00\xd6\x16\x42\xd0\xf3\xee\x5d\
+\x5d\x9c\xc2\x98\x3c\x6e\xc2\x24\x8c\x1d\x3b\x9e\x1f\x9b\x99\x09\
+\x09\x66\x3b\x09\x02\xf1\xdf\xf7\xfc\xb2\x1b\xdf\x6e\xfc\x06\xab\
+\x57\x7f\x81\xc2\x82\x02\x47\xfa\xd2\x6a\xec\xcb\xd3\xac\xb5\x9d\
+\xaf\x07\x76\x29\x01\x60\x26\x6b\x4b\x59\x0b\xb1\x77\x51\x40\x40\
+\x20\xa6\x4e\xbb\x1a\x57\xcf\x98\x89\xc4\xc4\x24\x59\xe6\x7a\xb8\
+\xbb\xc3\xc3\x83\x35\x77\x37\xb8\x29\x59\x73\x53\x1a\x19\x0a\x28\
+\xe9\x98\xbd\xea\xf4\x06\xfe\x6a\x30\x18\xc0\xff\x19\x00\x3d\x1d\
+\xeb\x85\xf7\x72\x20\xd9\xbf\x6f\x2f\x3e\x5c\xf9\x1f\x7c\xf6\xe9\
+\x27\x68\x6e\x6e\xee\xaa\x5f\x05\x10\x00\xfd\xfd\xf9\x78\x68\x97\
+\x02\x00\x68\xd4\x13\xe3\xaf\xb3\x77\x51\x34\xd3\xe3\x37\xdc\xf4\
+\x07\x4c\xbe\xe2\x4a\x78\xa9\x54\x66\xc6\x28\x89\x31\x4a\x25\x54\
+\x5e\x9e\xf0\x56\x79\xc1\xcb\xd3\x03\x9e\x8c\xf1\x0a\xc5\xd9\x3f\
+\x1a\xbd\x5e\xcf\x9a\x41\x00\x88\x01\x36\x92\xa2\xa1\xbe\x0e\x1f\
+\x7f\xb4\x0a\x8b\x5f\x7d\x05\x15\x15\x15\x5d\x7d\x1d\xa9\xb4\x87\
+\x71\x8e\xa5\xc1\xc5\x0e\x00\xb2\xee\x3f\x85\x10\xb1\x93\x24\x32\
+\xe0\x6e\x9c\x7f\x0b\x26\x4e\x9d\x06\x77\x77\x77\x8b\x51\xae\x84\
+\x9a\x31\xdc\x57\xed\x0d\xb5\x37\x03\x84\xf2\x3c\x3f\x0a\x93\x74\
+\x20\x24\x00\x56\x12\x87\x0c\xca\xf7\x56\x2c\x37\x02\xa1\xdc\xde\
+\xb7\xec\x65\x9f\x99\xc5\xbe\xe3\x9c\xd9\x06\x17\x33\x00\xe6\x41\
+\x18\xf9\x92\x6e\x9d\x5a\xed\x8b\xb9\xf3\xfe\x80\x99\xd7\xde\xc0\
+\x18\xef\x61\x21\xda\xdd\xe0\xef\xab\x46\x80\x9f\x1a\xee\x6e\x6e\
+\x2e\xb9\x71\x83\xf1\x95\x3f\x7c\x0b\xb5\xd3\xdc\xd4\xc4\x41\xf0\
+\xc6\x92\xd7\xd1\xd6\x26\x3b\xd0\x29\xe4\x4c\xd2\x6e\xfd\xb9\xb8\
+\x97\x8b\x15\x00\x0b\x20\x88\x43\x49\x1a\x31\x6a\x2c\xee\xf8\xcb\
+\x7d\x08\x0d\x0b\x37\x8f\x34\x62\x7c\x70\xa0\x3f\x67\xfc\xb9\x10\
+\xef\xe7\x92\x94\x4a\x6b\x3b\xe4\xd8\xd1\xa3\xb8\xef\xde\x7b\xb0\
+\x63\xc7\xcf\x72\x1f\xa1\x50\xf3\xbd\xac\xbd\x7e\xb6\xbf\xdd\xbd\
+\x9e\x44\xd7\x44\x43\x96\x82\x26\x92\xfa\x9e\x46\xfd\x1d\xf7\xdc\
+\x8f\xf1\x13\xa7\x9a\x1f\xa6\x3b\x33\xd8\x34\x41\xfe\x08\x0a\xf0\
+\xeb\x76\x8c\xb7\x24\x02\x81\x52\xa9\xb4\x32\x1c\x97\xbc\xbe\x18\
+\xcf\x2d\x78\xd6\x9e\x34\x78\x8e\xb5\xbf\x9d\xcd\xef\x76\xdf\x27\
+\x62\x4b\xc4\x7c\xf2\x8f\x67\x49\xfd\x91\xdc\xb8\x47\x1e\x7f\x06\
+\xe1\x91\xd1\xbc\x53\xf4\x10\x69\xb4\x87\x87\x04\x31\x15\x70\x76\
+\xa2\xbe\xa5\x55\x8b\xa6\xe6\x16\xb4\x69\xdb\xd1\xdc\xda\x0a\x5d\
+\x87\x8e\x8b\x71\xf2\x14\xc8\x78\x54\xa9\x3c\x99\x2d\xe1\x03\x15\
+\x33\x20\xcf\x8e\x18\x60\xdd\x95\x9d\x40\x60\xff\x7e\xfb\xed\x00\
+\xfe\x70\xf3\x7c\x14\xe4\xe7\xcb\x7d\x88\x8c\xc3\xff\x3b\xf3\x5f\
+\xbc\x38\xc8\x2e\xf3\xc7\x4e\x9c\x82\xbb\xee\x7d\x90\x47\xe8\xf8\
+\xa8\x67\x8c\x89\x0e\x0f\xe1\x06\x9e\xb3\xd4\xd2\xda\x86\xb2\xf2\
+\x2a\x94\x56\xd5\xa0\xb2\xaa\x16\x35\xf5\x8d\x6c\x04\x6a\x8d\x96\
+\xbc\xb1\x01\xd6\xef\x0d\xc2\x7b\x02\x03\xa9\x99\x10\x4d\x20\x62\
+\xd8\xef\xc7\x44\x86\xb1\x7b\xf2\x74\xbe\xb3\x0c\x00\x04\x2e\x32\
+\x4c\xe9\x5f\x3d\xf3\x16\x08\x04\x5b\xb7\xc8\x7a\x82\x67\x0c\x82\
+\x8b\x05\x00\x9f\x40\x86\xf9\x37\xdc\x74\x0b\xe6\xcc\xbd\xc9\x2c\
+\x3a\xfd\x7c\x7c\x10\x1d\x19\xea\x94\x81\xd7\xd4\xd2\x82\x82\x53\
+\xa5\x38\x59\x52\x86\xaa\x9a\x3a\xb3\xb5\x6e\x62\xae\xb6\xbd\x1d\
+\xad\x4c\x0c\xeb\x74\x3a\xf6\xaa\x85\xc1\x6c\xc6\x09\x52\x80\x46\
+\xac\xa7\x87\x07\x73\x29\x95\xe6\x78\x00\xfd\x8f\xee\x27\x3c\x34\
+\x18\xa9\x09\x31\x48\x4b\x8a\xe3\xde\x86\xa3\x44\x9f\x25\x97\x54\
+\x88\x39\x28\x98\x3b\xa9\xc3\xc3\x7f\x7d\x10\x2b\x98\xb7\x20\x43\
+\x67\xa4\x0e\x2e\x06\x00\xbc\x00\x61\x22\xc7\x8a\x94\x4a\x37\xdc\
+\x7a\xe7\x5f\x30\xe5\xca\xe9\x66\x77\x2a\x54\x13\xc4\x44\x7e\x30\
+\x1c\x55\xf5\xc5\xa5\x95\x38\x51\x58\x8c\x92\xf2\x4a\x33\xc3\x3b\
+\x18\x93\xab\xeb\xea\x51\x5b\xdf\x80\xfa\xc6\x46\x06\x8e\x56\xce\
+\x78\x47\xc8\x8d\x81\xce\x47\xa5\xe2\x4d\xed\xed\x0d\x1f\xd6\xe8\
+\xde\x0c\x46\x30\x24\xc7\x45\xa1\x6f\x56\x2a\x62\x19\x40\x1d\x25\
+\x4f\x06\x02\x2e\x0d\x8c\xc1\xa4\x7f\x3e\xf5\x24\xf7\x14\x64\xe8\
+\xcf\x70\xd2\x30\xec\xee\x00\xb8\x85\xb5\xb7\xc5\x27\x69\xc4\xdd\
+\xfb\xd0\x63\x18\x36\x62\x34\x7f\xb0\xf4\x3e\x3a\x22\x94\x1b\x7a\
+\x8e\x50\x71\x69\x05\xf2\x8e\x17\xa2\xbe\xa1\x89\x33\xa7\x9d\xe9\
+\xf4\xd2\xca\x4a\x9c\xae\xa8\x44\x1d\x63\xbc\xde\xd0\x39\xc2\xb5\
+\xf0\x40\xb3\x42\x8d\x16\x85\x37\xda\xe1\x05\x9d\xc2\x3a\xc2\xec\
+\x6e\x68\x87\xa7\xa1\x8d\xf9\xa2\x2d\xf0\x31\x34\xb1\xab\x3b\x3a\
+\xef\x93\xdd\x1b\xd9\x06\x81\x7e\xfe\xec\x55\x0d\x25\x04\x17\x90\
+\xa4\xc2\xf0\x9c\x3e\x88\x89\x08\x73\xe8\x7e\x29\x30\x45\xaa\xc4\
+\x04\x82\x65\x4b\x97\xe2\xb1\x47\x1f\x91\xba\x94\x7e\x7c\x1a\x9c\
+\x70\x11\xbb\x33\x00\x72\x21\xcc\x99\xdb\xc4\xf4\x6f\xbf\xfb\x3e\
+\x8c\x9f\x7c\x25\x7f\x20\x14\xa2\x8d\x8b\x0e\xe7\xbe\x7d\x57\x54\
+\x5b\xd7\x80\x5f\x8f\xe4\xf3\xd1\x4d\x8c\x6f\x64\x86\x5d\xfe\xa9\
+\x62\x14\x97\x95\x43\xa7\xd7\xf3\x6b\x5a\xe0\x8d\x2a\x65\x08\x6a\
+\x14\x1a\xd4\x29\x03\xa1\x55\x38\x37\x7b\xec\xad\x6f\x42\x80\xa1\
+\x16\x41\x86\x6a\x68\xf4\xe5\xf0\x64\xb0\x21\x22\xbd\x1e\x1c\x18\
+\x80\xe0\x80\x40\xb3\x7a\x22\x1b\x61\x74\x6e\x3f\x6e\x37\x74\x45\
+\x14\x96\xf6\x36\xda\x38\x5d\x48\x82\x5a\x76\x4d\x8e\xa3\xc1\xa2\
+\xee\x0a\x00\x4a\xdc\xd8\x0b\x21\x17\xcf\x8a\xae\xbb\xf1\x16\x5c\
+\x33\xfb\x3a\xfe\x20\x28\x46\x9f\x18\x17\x09\xb5\x8f\x7d\x63\x4f\
+\xa7\xd3\xe3\x68\xfe\x29\x14\x16\x97\x72\xc6\xb7\x30\x4b\xfe\xd0\
+\xf1\x7c\xce\x78\x1a\xec\xed\x0c\x63\xa5\xca\x28\xd6\xa2\xd1\xa0\
+\x0c\x38\xa7\x1d\x09\xd4\x57\x21\x52\x5f\x8c\x30\x7d\x29\xb3\x64\
+\xf5\xfc\xbe\x83\xfd\xfd\x11\x1a\x1c\xcc\x55\x06\x01\x63\x40\x9f\
+\x74\x0c\xea\x9b\xd9\x65\x34\x92\x54\x01\x8f\x5a\x1a\x41\xf0\xe0\
+\xfd\xf7\xc9\xd9\x04\xf4\xec\x28\xcb\xa9\xcb\xb0\x71\x77\x05\xc0\
+\xff\x20\x88\x32\x2b\x1a\x33\x61\x32\xfe\xf8\xe7\xfb\x8d\x62\x5f\
+\x81\xc4\xd8\x28\xf8\xf9\xfa\xd8\xfd\xa2\xc6\xa6\x66\x1c\x38\x7c\
+\x02\xcd\x4c\x97\x77\x30\x51\x7f\xb4\xe0\x24\x0e\xe7\x17\xf2\x38\
+\x7d\x1b\x1b\x9f\x85\x6e\x49\x28\x51\xc6\x42\xaf\xb0\x35\x1a\xa3\
+\x83\x54\xc8\x49\x08\x44\x46\xa4\x2f\x92\xc3\x7c\x10\x11\xa0\x82\
+\xaf\xca\x5a\x20\x95\xd6\xb5\xa2\xac\xae\x0d\xc7\xca\x9b\xb0\xb7\
+\xb0\x1e\x7b\x4f\xd5\xa1\xa9\xcd\xd6\x66\x20\x55\x11\xa3\x2b\x44\
+\xac\xbe\x80\xab\x09\x52\x5b\xe1\x1a\x0d\x93\x08\x02\xe0\x42\x83\
+\x03\x31\x65\x4c\x2e\xeb\x8f\x7d\x49\x46\x86\xa1\x0f\x81\xc0\x68\
+\x18\xce\x99\x3d\x4b\xce\x3b\x70\xc8\x33\xe8\x8e\x00\xa0\x10\xef\
+\x72\xf1\xc9\xe4\xb4\x0c\xfc\xe3\xd9\x17\x99\xf8\x14\xe2\xf9\xf1\
+\xd1\x11\x5d\xea\xfc\xb2\xaa\x6a\x3e\xf2\x49\x02\xd4\x30\xc3\x6e\
+\xfb\xbe\x5f\x39\x20\x68\xc4\x17\xb8\x25\xa3\x58\x19\x67\xc5\x78\
+\x37\x06\xaa\x11\x69\xc1\x98\x31\x20\x02\x93\x7b\x87\x22\x21\xc4\
+\x3e\xb8\xa4\xa8\x43\x67\xc0\xae\x82\x5a\xac\xdd\x5f\x8e\x4f\x77\
+\x9e\x46\x7e\xa5\xf5\x6c\x1f\x01\x21\x56\x97\x8f\x38\x06\x04\x92\
+\x08\xde\xcc\x60\x8c\x09\x0f\xe7\x5e\x04\xb5\x89\x23\x07\x21\x2e\
+\x2a\xdc\xee\x6f\x50\xdc\xc1\x04\x02\x72\x11\x27\x4e\x18\x27\x17\
+\x27\x18\x83\x2e\x66\x11\xbb\x1b\x00\xa8\xe7\x94\x49\x1b\x68\x79\
+\xd2\x97\x19\x51\x4f\x2f\x7c\x0d\x9a\x90\x50\xce\xfc\x88\x30\x0d\
+\x22\x42\x35\x76\xbf\x88\x5c\xba\xe2\xd3\xe5\xdc\xa0\x3b\xc2\x46\
+\xfc\xde\x43\x47\xb8\xf8\x2f\x57\x84\xe3\x88\x7b\x16\xd3\xed\x5e\
+\xe6\x6b\x43\xfc\x3c\x71\xdb\xa8\x38\x5c\x97\x1b\x8d\x70\x7f\x2f\
+\x9c\x4b\xda\x73\xb2\x0e\xef\x6c\x3d\x89\x2f\xf7\x94\x41\xdb\xa1\
+\x37\x9f\xf7\x66\x06\x63\x46\xc7\xaf\xcc\x56\xa8\xe1\xc6\x62\x14\
+\x03\x41\x80\xaf\x2f\xef\xdf\x88\x41\x7d\x91\x95\x9a\x68\xf7\x7b\
+\x29\xc6\xa1\x32\x1a\x86\x14\x2c\x9a\x3a\x79\x92\x54\xc4\xb0\x00\
+\x42\x76\xb3\xac\x2a\xe8\x6e\x00\xf8\x10\x12\x61\xde\xbb\x1f\x7c\
+\x14\x39\xb9\xc3\xf8\xc3\x21\x63\x2f\x39\x3e\xda\xee\x97\x9c\x62\
+\xcc\x3f\x5d\x51\x05\x3d\x1b\xf9\xdb\xf7\x1e\x40\x7e\x51\x09\x13\
+\xba\x6e\xc8\x73\xeb\x8d\x72\xb7\x48\xf3\x75\x1a\x5f\x4f\xfc\x69\
+\x6c\x3c\xe6\x0c\x8e\x82\xca\x5d\x79\x5e\x3b\x56\xde\xa0\xc5\x92\
+\x6f\x0b\xf0\xf1\xce\x62\x66\x70\x76\x9e\x27\x69\x90\xac\x3b\x02\
+\x8a\x20\x84\x04\x05\x71\xb5\x40\x34\x74\x40\x6f\xf4\xc9\x48\x96\
+\xfd\x3e\xb2\x01\x02\xfc\xfc\x98\x71\xe8\x66\x0e\x1b\x3f\xf9\xc4\
+\xe3\x52\x97\x52\x42\xc9\x63\xb2\xdf\x73\x5e\x7b\xed\x1c\x0d\x87\
+\x60\xf5\x5b\x9f\x1c\x3d\x0e\xb7\xde\x75\x1f\x67\x3e\x89\xc8\x8c\
+\xe4\x78\xbb\xa1\xdd\x22\xe6\xe2\x95\x57\x56\xf3\xe0\xcd\xe6\xed\
+\xbb\x99\x7b\x57\x85\x26\xa8\x71\xc0\xa3\x3f\x73\xe7\x7c\xf9\x35\
+\x6e\xac\xd7\xd7\x0f\x89\xc1\xad\xa3\xe3\xe0\xe3\x29\xf1\x5d\xe7\
+\xea\xa9\x18\x6c\x4f\xe5\x57\x34\x63\xc1\x9a\xa3\xd8\x7b\xb2\x33\
+\x45\xcc\x5f\x5f\x8b\x3e\x1d\xbf\x30\x27\x53\xcb\xa5\x40\x74\xb8\
+\x30\x89\x35\xa4\x7f\x2f\xbb\x20\x20\x03\x32\x88\x79\x10\x4a\x63\
+\xc4\xf0\xaa\x2b\xa7\x4a\x4d\x20\x51\x66\x51\x6f\xc8\xa4\x97\x75\
+\x27\x00\xec\x84\x50\x94\x61\x26\x5f\x5f\x3f\x3c\xf3\xf2\x52\xf8\
+\xf9\x07\x70\xc4\xa7\x24\xc4\xd8\x35\x92\x4a\xcb\xab\x50\x51\x5d\
+\x03\xad\xb6\x03\x1b\x7e\xdc\x8e\x8a\xaa\x1a\xe6\x13\x05\x62\xbf\
+\xfb\x00\x74\x28\x84\x90\x6c\x0c\x33\xec\x1e\xb9\x2a\x15\x29\xe1\
+\xbe\xf2\xbd\x57\x74\x79\x42\x86\x0c\x76\xdf\x5a\xd2\x17\xbb\x4e\
+\xe3\xdf\x4c\x35\xb4\xb6\x0b\xe2\x40\x65\x68\x41\x76\xfb\x2e\x06\
+\xd5\x26\xf8\xab\xd5\x88\x89\x88\xe0\x20\x18\x36\xb0\x0f\x32\x53\
+\x13\x64\xbf\xc7\x9b\xa9\x01\x7f\xe3\x0c\x27\xcd\x22\x8e\x1f\x3b\
+\x5a\x4a\x15\x50\x18\xfd\x7a\x87\xba\xea\x22\x9a\x0e\x21\x23\xd6\
+\x8a\x6e\xba\xed\xcf\x18\x35\x6e\x12\xef\x5c\x48\x70\x00\x33\x8e\
+\x22\x64\xbf\xa0\xb6\xa1\x01\xa7\xcb\x2a\xb9\xc1\xf7\xf5\xf7\x3f\
+\xf1\x91\x4f\xcc\xdf\xe7\x9e\x63\x0e\xde\x90\x81\x77\xc7\xd8\x04\
+\xa6\x3b\xc5\xa3\x5e\xe1\xd8\x93\x90\xbb\xc6\xe0\xc0\x67\x0d\xb6\
+\x17\x16\x32\x03\xf1\xf9\x35\xc7\xb8\x17\x41\x44\x01\xa5\xfe\xed\
+\x3b\x38\x08\x48\x12\x98\x40\x40\x86\xa1\xbd\xa0\x11\x19\xc3\x5e\
+\x5e\x1e\xfc\xda\xe7\x9e\x7d\x06\x8b\x5e\x7e\x49\xea\x32\xaa\x83\
+\xb0\xc9\x36\xee\x2e\x00\xd8\x03\x51\xa1\x46\x4c\x5c\x02\xfe\xfe\
+\xcc\x4b\x3c\x68\x42\x41\x10\x32\x8a\xe4\xe2\xfb\xad\x5a\x2d\xd7\
+\xfb\x94\x7e\xf5\xdd\xf6\x9d\x38\x5e\x58\x64\xcd\x7c\xd6\xcb\xab\
+\xfb\x47\xe2\x77\x03\x23\x44\x3d\x16\x31\x5e\xe2\x69\xcc\xec\x17\
+\x0e\x5f\xaf\x2e\xf3\x4b\xad\xe8\x87\xe3\x35\x38\x6e\x69\xfd\x8b\
+\x01\x62\x11\x69\x6c\x68\xe9\xc0\x2b\x1b\xf3\x71\xac\xb4\x91\x5f\
+\x67\x09\x02\x93\x4d\x40\x91\xc0\xab\x27\x8e\x84\x9f\x5a\xda\x2b\
+\xa1\x60\x18\xb9\x91\x14\x47\xa0\xa4\x92\x21\x83\x07\x49\x65\x16\
+\xd1\x00\x9b\x21\x3e\xd9\x1d\x00\x70\x25\x84\x52\x29\x2b\xfa\xd3\
+\xbd\x0f\xa3\xff\xa0\x21\x1c\xd5\xe4\x16\x85\x04\x07\x4a\x7e\x98\
+\xfc\xf9\x12\xa6\xf7\xb5\x1d\x1d\xd8\x7b\xf0\x08\x37\xfa\x9a\x14\
+\x6a\xec\xf2\x18\x06\x9d\xd2\x8d\x5b\x4b\x33\x06\x44\x62\x5c\xa6\
+\x45\xae\xa8\x15\xd3\x45\x8f\x40\xf4\xf6\x26\x66\x20\xfa\xab\x9c\
+\x03\xc0\xc6\xc3\x55\xc8\x2b\x33\x16\xfb\xd8\x61\xbe\xe9\x6f\xad\
+\xed\x3a\x2c\xd9\x54\xc8\xec\x83\x26\x7e\x4e\xa5\x6f\xc6\x40\xed\
+\x36\x6e\x13\x90\x14\x20\x69\x40\x39\x0d\x57\x8c\x1b\xc6\x27\x9c\
+\xa4\x88\xe2\x21\x7e\x6a\x61\xee\xe1\xad\x65\xcb\xf0\x8f\xc7\xfe\
+\x2e\x75\x19\x79\x04\x56\xc5\x27\xdd\x01\x00\x5f\x43\xa8\xcf\x33\
+\x93\x69\xf4\x53\x67\xc8\xd5\xa1\xd1\x2f\x97\xcc\x51\x5d\x5b\xcf\
+\x7d\x7b\x8a\xe3\x7f\xbe\x61\x33\xb4\x06\x37\xec\xf2\x1a\x86\x16\
+\xa5\x9a\x33\xf7\x8a\xbe\x61\x18\x99\xaa\xe9\xec\xa9\x42\xd4\x6d\
+\x85\xe8\xd5\xf4\xc6\xf8\xfe\x8e\x61\xd1\x08\xf0\x76\x6e\x9e\x7f\
+\xcd\xc1\x0a\x1c\x28\x21\x00\xd8\x32\xdb\xfa\xd8\x60\x3e\x6e\x63\
+\xb6\xc0\xbf\x7f\x3c\x89\x93\x95\x2d\x1c\x24\xfe\xba\x5a\x0c\xd0\
+\x6e\x87\x3b\xbb\x8f\xe4\xb8\x38\x6e\x00\xf7\xcd\x4c\x41\xbf\xac\
+\x54\xc9\xdf\x24\x57\x92\x72\x1f\x68\xf6\x90\x72\x0c\x07\x0d\xec\
+\x27\x95\x68\x4a\x29\x74\x77\x88\x7a\xea\x52\x4a\x87\xe0\xf7\x5b\
+\xd1\x1f\xff\xf2\x57\xf4\xcb\x19\xc2\x07\x67\x42\x4c\x24\x43\xbf\
+\x74\x78\x96\x12\x34\xaa\x6a\x6a\xf9\xeb\xca\x2f\xd7\xa1\x81\x01\
+\x61\xbf\xe7\x00\x54\xba\x33\x51\xcf\x06\xca\xe0\xc4\x20\x4c\xcc\
+\x0a\x81\x95\xa8\x37\x01\xc0\x46\xf4\x2b\x24\x9f\xc8\xfd\x63\xe2\
+\x10\xe4\x24\x00\x3e\xdb\x5f\x8e\x3d\xc5\x0d\x9d\x27\x2c\xf5\xbf\
+\x41\x74\xce\xe2\x3d\xc5\x09\xde\xf9\xf1\x14\xaa\x1b\xdb\xf8\xfb\
+\x58\xed\x09\xa4\xb6\xe7\xf1\x60\x51\x52\x4c\x0c\x1f\x04\x57\x4d\
+\x18\xce\xdc\x3f\x5f\xc9\xdf\x25\x29\x40\x6e\x32\x3d\xb7\xb7\xdf\
+\x24\x29\xf0\xa8\xf8\x12\x2a\x43\x23\x43\xca\x5c\x8b\xe8\x6a\x00\
+\xd8\xe4\xf6\x05\x6b\x42\xf1\xc4\xbf\x5e\x83\x9b\x31\xdb\x26\x2b\
+\x4d\x66\xf4\xb3\x51\x42\xa3\xbf\x9d\x89\xfe\x1f\x76\xed\xc5\xee\
+\x5f\xf3\x50\xe2\x1e\x8d\x3c\x55\x36\x1f\xf9\x31\xc1\xde\x98\xd1\
+\x3f\x82\xbb\x48\x9d\x3d\x55\xd8\x8e\x78\x85\x98\xf1\xd6\xe0\xf8\
+\xc7\xf8\x04\x04\xfb\x38\x07\x80\x95\x7b\xca\xb0\xa3\xa8\x5e\x86\
+\xf1\xf6\x81\x50\xdb\xd4\x8e\x0f\x77\x96\x30\x37\x56\xc7\xaf\xed\
+\xdf\xb2\x1d\x41\xba\x6a\x44\x86\x86\xf2\xb0\x31\xcd\x24\x4e\x18\
+\x31\x48\xf2\x77\x49\x0a\x44\xb2\xbf\x2b\x99\xad\x44\x29\xe7\x03\
+\xfa\xf5\x95\xaa\x3b\xb8\x95\xb5\x77\x2c\x7a\xeb\x52\x3a\x05\xd1\
+\x84\xcf\x55\xb3\x6e\xc0\xa4\x2b\x67\x70\xa6\xc7\x46\x85\x21\x4c\
+\x13\x24\xf9\x41\x1a\xf5\x34\x9b\x47\x12\xe0\xbd\xcf\xd7\xa2\x0d\
+\xee\xd8\xae\x1e\x8b\x0e\xa5\x07\xbc\x98\x6f\x3f\x8b\xe9\x7d\xee\
+\xe3\x2b\x14\xd2\xcc\x96\x61\xb8\x18\x10\xcf\x4e\x4a\x42\x88\xda\
+\x39\x00\xbc\xbb\xfb\x34\xb6\x15\x9a\xfc\x7c\x83\x2d\xb3\x4d\x2f\
+\x52\xa0\x60\xc7\x27\x98\x01\xf9\x6d\x5e\x25\x33\x70\x98\x9b\xa7\
+\x6b\xc4\xe0\xa6\x2d\xf0\x60\x12\x2d\x2d\x3e\x9e\x4f\x20\x8d\x1f\
+\x3e\x90\xe7\x3d\x48\x51\xa0\xbf\x1f\x97\x04\xf4\xfc\x1e\xfe\xeb\
+\x03\x78\xff\xbd\x15\xe2\x4b\x28\xd6\x32\xd2\xa2\xf7\x2e\x23\x9b\
+\xc0\x0f\x4d\x90\x3c\xb9\xf0\x0d\x04\x04\x06\x71\xcb\xb6\x77\x46\
+\x0a\x7f\x95\xa2\xc6\xc6\x16\x3e\x85\xfb\xc5\x37\x9b\xb8\xd5\x7f\
+\x48\xd5\x17\xa7\xbd\xe2\x68\x18\x60\x44\x72\x30\xe2\x34\xde\x16\
+\x8c\x36\x32\xb9\x2b\x00\x48\xa8\x87\x45\x57\x24\x23\x54\xed\x5c\
+\x5a\xd7\xb2\x1d\x25\xd8\x52\x58\x27\x29\xe6\xcd\x07\x62\x00\x18\
+\xac\xcf\x6f\x39\x56\x8d\xa2\xea\x16\x2a\x26\x40\x62\x6b\x1e\x12\
+\xdb\x8e\x41\xc3\x24\x40\x04\x93\x04\x9a\x40\x7f\x4c\x18\x35\x58\
+\xf2\xb7\x69\xc6\x30\x2a\x4c\x63\xae\x40\xba\x62\xca\x24\xa9\xcb\
+\x68\xd0\x15\x9b\xbb\xec\x22\xb2\xc9\xf4\x49\xcd\xec\x8d\xbb\xee\
+\x7f\x8c\xf3\x49\xc3\xac\xfe\xf8\x98\x48\xc9\x0f\x52\x86\x0e\xe5\
+\xe9\x95\x56\x54\x61\xc5\x7f\xbf\x42\xb3\xd2\x07\x3f\xfb\x8f\x85\
+\x81\x66\xd8\xfc\xbd\x30\x34\x29\xa8\x73\xe4\x5b\x31\xdd\x74\x6c\
+\x87\xe9\x22\x60\x2c\xbb\x2a\x05\x61\x4e\x02\x60\xf1\xcf\xc5\xf8\
+\xee\x44\xad\xf0\xc6\xca\xf8\x33\x48\x8b\x7e\xab\xf3\xc2\x31\x79\
+\x06\x1b\x99\x14\xe8\x60\x76\x81\xbb\xae\x1d\x43\xeb\x37\xc2\x13\
+\x3a\xa4\x25\x24\x70\x77\x78\xcc\xd0\x01\x08\x93\x91\x02\x11\xcc\
+\x18\xe4\xf3\x04\xac\x9f\xc3\x87\x0e\x96\xaa\x45\xfc\x13\x6b\xcb\
+\xac\xba\xeb\x02\x3a\x04\xd1\x32\x2c\x73\x6e\xba\x0d\x43\x47\x8d\
+\xe7\x61\xcd\xd4\xc4\x18\xf8\xf9\x49\x47\xfd\xda\xb5\x1d\x7c\xf4\
+\xaf\xfe\x66\x33\x0e\x1d\x2f\xc0\x41\x75\x3f\x94\xaa\xe2\x84\x0e\
+\xa7\x04\x41\xed\xe9\xde\xc9\x70\xd3\x68\x37\x33\x5d\x61\x2b\x19\
+\x2c\x9f\x84\x48\x05\x2c\x9f\x9e\x86\x08\x5f\xe7\x00\xb0\x70\x5b\
+\x11\x36\x1e\xb7\x04\x80\x1c\xe3\x2d\x8f\x2d\xce\x19\x55\xc2\x89\
+\x8a\x66\x1c\x2d\x17\xe2\x03\x09\xcd\x79\x48\x6a\x39\xc2\x63\x03\
+\x11\x1a\x0d\xcf\x80\x1a\x9a\xd3\x47\xf2\xf7\x7d\x7d\xbc\xd9\x75\
+\x01\xfc\x79\xbc\xf0\xfc\x02\xbc\xfa\xca\x22\xf1\x25\xeb\x58\x9b\
+\x6a\xd9\xed\x0b\x4d\xf1\x10\x66\xaa\xac\xe8\x89\x17\xde\x80\x7f\
+\xa0\x90\x31\xd3\x37\x2b\x45\xd2\xf8\xa3\x67\x43\x69\xd9\x64\xf1\
+\x2f\x7e\xef\x23\xb4\x28\xbc\xb0\x2d\x68\x22\x1f\xfd\x61\x6c\xf4\
+\x67\x98\x42\xbc\x96\x12\x40\xfc\x6a\xea\xb9\x0d\x08\x6c\x3d\x81\
+\x55\xbf\x4b\x47\xa4\x93\x00\x58\xf0\x63\x11\xd6\x1d\xaf\x91\x31\
+\xfc\x64\xc4\xbf\x58\x0d\xe8\x85\x18\xc7\xcf\x85\xb5\x68\x6f\x27\
+\x29\xa0\xc5\xf0\xea\xf5\xf0\x64\x1a\x31\x3d\x29\x89\xcf\x03\x5c\
+\x31\x76\x28\x9f\x1a\x16\x13\xa9\xd2\xb8\xc8\x70\x73\x55\xf2\xf4\
+\xab\xae\x14\x5f\x42\xde\x00\x05\x56\x74\xae\x02\xc0\x5c\x08\x05\
+\x1e\x66\x8a\x8c\x89\xc3\xfd\x8f\x3d\xc7\x6f\x9a\x52\xa4\x12\xe3\
+\xa4\x67\xfc\x78\xd6\x2d\xd3\x8b\xdb\x7e\xd9\x8f\x6f\x7f\xda\x89\
+\x13\x3e\xe9\x28\xf0\xcd\xe0\xcc\x73\x73\x57\xc2\x4f\xe5\xce\x93\
+\x36\x7c\x55\x6e\x50\x7b\x79\xf0\x39\x7e\x1b\x69\x20\x2b\x05\x14\
+\x36\x2a\xe0\xf3\x99\xe9\x88\x72\x12\x00\x4f\xfd\x70\x0a\x6b\x8e\
+\xd5\xd8\x32\xdd\x8a\xe1\x16\xe7\xad\x24\x80\xe5\xab\x01\x25\xb5\
+\x6d\x28\xaa\x11\x62\x03\x99\x75\xbf\x20\xb2\xed\x14\x63\x6e\x24\
+\x73\xf7\x7c\xf9\x44\x51\x6a\x62\xac\xe4\x3d\x44\x85\x6a\x84\x3c\
+\x42\xd6\x87\x5e\x99\x69\x52\xeb\x13\x50\xc6\xd0\xcf\xae\x02\xc0\
+\x62\x08\x19\xac\x66\x1a\x31\x6e\x32\xa6\xcf\x11\xd2\xbb\xe3\x63\
+\x22\x64\x7d\x7f\x32\x0a\xe9\xd9\x2c\x5b\xf9\x19\xca\x2a\xab\xf1\
+\x53\xc8\x24\xb4\xba\xfb\x40\x28\xf3\x35\x32\xd0\x74\xcc\x46\x8b\
+\xb7\x27\x01\xc1\x9d\x7b\x04\x74\xec\xc5\xa7\x7d\x45\xa0\x30\x31\
+\xdf\xc8\x77\xcb\xe8\xe0\xda\xd9\x19\x88\x76\x12\x00\x8f\x6d\x3d\
+\x85\x2f\x09\x00\x62\xdd\x0e\x48\x8f\x76\x3b\x00\xe8\xe8\x30\xe0\
+\x50\x69\x03\x07\x7d\x60\x5b\x25\x06\xd4\xfc\xc0\x7d\xfd\xf8\xa8\
+\x68\xe6\x16\xfa\x61\x64\xae\xf4\x52\x47\x41\xcc\x1b\x08\x0a\xf4\
+\xe3\xea\xf4\xb6\x5b\xe6\x63\xc3\xfa\x75\xe2\x4b\xc8\xfe\x7a\xd1\
+\x55\x00\xb0\x99\xf9\x9b\x7b\xdb\xdd\xc8\x1e\x90\xcb\x01\xd0\x8b\
+\xf9\xfe\x5e\x9e\xb6\x0f\x9d\x62\xdd\x34\x2f\x40\xfe\xff\x2b\xef\
+\x7e\x88\x3a\x8f\x20\xec\x0e\x19\x63\xc3\x74\xe1\xd5\x92\xc9\x9d\
+\xe7\xdd\xdc\x14\xf0\xa6\x04\x4b\xe6\x57\xa9\x3c\xdc\x58\x13\xaa\
+\x70\xa4\xa5\x00\xf0\xed\x9c\x4c\xc4\x38\x09\x80\x87\xb7\x9c\xc4\
+\xe7\xc7\x6a\xa4\x99\x2c\x06\x41\x57\xaf\x8c\xf1\x25\x75\x2d\x68\
+\x6c\xed\xe0\x6a\x61\x78\xd9\x5a\x78\x1b\xb4\xc8\x4a\x49\xe1\x31\
+\x8e\x29\xa3\x87\x70\xcb\x5f\x4c\x54\xea\x1e\x19\x2e\x78\x03\x6f\
+\x2d\x7d\x03\xcf\x3c\xfd\x94\xf8\x12\xaa\xaa\x9e\xed\x2a\x00\x30\
+\x99\x66\x5d\xd5\xfb\xd0\x53\x2f\x21\x38\x24\x94\x33\xb8\x0f\x73\
+\xff\xa4\xc8\xb4\x60\xc3\xcf\xfb\x7e\xc3\x57\xdf\x6e\xc5\x09\x26\
+\xfa\x0b\x02\xb2\x44\x4c\xb7\x00\x81\xd5\xb1\xc5\x39\x2b\x60\x28\
+\xe0\xc5\xa4\x8a\x17\x03\x02\x49\x07\x4f\x37\xaa\x27\xec\xf4\x13\
+\x7f\x20\x00\xf8\x39\x07\x80\x07\x18\x00\x3e\x3d\x5a\x63\xcb\x70\
+\x47\x99\xaf\x37\x58\x9d\x6b\x62\xcc\xaf\xa4\xe8\x20\x03\x40\x66\
+\xf5\x2e\x44\xb6\x9c\xe4\x91\x41\x5f\x1f\x1f\x0c\xec\x9b\x21\x99\
+\x1d\x25\xe4\x4c\x46\x72\x00\xfc\xb8\x75\x2b\x7e\x7f\xc3\xb5\xe2\
+\x4b\x7e\x65\xad\x8f\x2b\x00\x40\x19\x0e\xc7\x2c\x4f\xa8\xbc\xbd\
+\xf1\xf8\xc2\x65\xe6\x7a\xbe\xe4\xf8\x18\xc9\x0f\xfa\x30\x54\x93\
+\x0a\x58\xf5\xd5\x06\x1c\x38\x7c\x1c\x3b\xc3\xc6\xa0\xc1\x4b\x23\
+\x0f\x00\x4b\x29\x20\x96\x08\x76\x8c\x43\x7a\x78\x9e\x04\x0a\x77\
+\x05\xf6\xdd\xd0\x0b\xf1\x7e\xce\xa5\x89\xdd\xcb\x00\xf0\xf1\xd1\
+\x6a\x8b\x91\x6f\xe4\xbc\xa3\x23\x5f\x0f\xb3\x0a\xe0\x1f\x63\xc6\
+\x60\x29\x4d\x19\x33\x60\x44\x34\x16\x20\xab\x66\x37\x9f\x25\x0c\
+\x0f\x09\x41\x02\x53\x97\x72\xe9\x63\x71\x51\x61\xbc\xb0\x84\xf4\
+\x7f\x76\xef\x4c\xf1\x9f\xa9\x86\x40\xe5\x0a\x00\x4c\x86\xe0\x86\
+\x98\x29\x26\x3e\x09\x77\x3e\xf8\xb8\x50\x4a\xc5\x7c\x5b\x72\x71\
+\xa4\x48\x28\xed\x06\x5e\x78\xeb\x03\x54\xd6\x36\x60\x4b\xec\x74\
+\xe8\x95\xee\x02\xc3\x95\x0a\x49\x10\x24\x07\x7b\x63\x44\xac\xbf\
+\x35\xf3\x8d\x8c\xb6\xb6\xfa\xa5\x62\x01\xec\xb7\x86\xc7\x20\xd4\
+\xc9\xb9\x80\xf7\x0f\x57\x61\x47\x59\x93\x7d\x2f\xc0\xf4\x5e\x02\
+\x1c\xab\x7e\xab\xb0\x96\x04\xec\xb5\xb6\xb9\x1d\x1d\x3a\x3d\xbc\
+\xb5\x0d\x18\x5a\xb2\x9e\x8f\xfe\xe4\xb8\x58\x9e\x0b\x90\xdb\xaf\
+\x97\xe4\x7d\x50\xee\x24\xb9\x84\xd4\xcd\x81\xd9\xbd\xa5\x56\x2f\
+\x4b\x71\x05\x00\x6c\xaa\x7d\xb2\x73\x86\x62\xce\xbc\xdb\x8d\xe1\
+\xdf\x70\xe6\xc3\xda\x4e\xfd\xf2\xa4\x90\x20\x7f\xb4\xb4\xb5\xe1\
+\xf1\x45\x6f\xa2\xc1\x23\x00\x3b\xa3\x27\x4a\x8f\x7a\x8b\xd1\x3e\
+\xaf\x6f\x18\x96\x5f\x99\xea\xd8\x9d\x75\x13\x0a\x7a\xe9\xe7\x4e\
+\x29\xc0\x41\xa0\x47\xb3\x56\x07\x2d\x65\x0f\xb1\xf7\x23\x4f\xae\
+\x66\x43\x57\x8f\x3e\xa9\xa9\x5c\xff\x53\x50\x48\x8a\x28\x89\xc6\
+\x54\x16\x3f\x73\xfa\x34\xec\xdd\xbb\x47\x7c\xc9\x18\x57\x00\x80\
+\x32\x17\x9f\xb0\x3c\x31\x72\xc2\x15\x98\x7c\xf5\x6c\xa1\x7e\x8e\
+\x89\x7f\xa9\x2a\x1f\xca\x03\xa4\x39\xf1\xa2\xd2\x72\x66\x00\xae\
+\x42\x99\x3a\x16\xbf\x85\x0f\xb1\x06\x80\x95\x14\xa0\xf7\x4a\xcc\
+\xeb\xc3\x00\x30\x35\xd9\xa1\x1b\xeb\x2e\xe4\x2b\x06\x00\x53\x01\
+\xed\x6c\xf4\x9b\x00\x30\xb0\xf8\x5b\x04\xb4\x55\xa3\x6f\x7a\x1a\
+\xcf\x0f\x18\x33\x64\x80\x64\x9e\x64\xa0\xbf\x2f\x42\x35\x81\xfc\
+\xb9\xde\xf3\xe7\x3b\xb1\xe6\xab\x2f\xc5\x97\xcc\x77\x05\x00\x6c\
+\x5c\xc0\xc9\xd3\xe7\x30\x37\x70\x2a\xe7\x1d\x25\x7d\xd2\xf4\xa7\
+\x98\x28\xe0\x41\x6e\xcf\xfe\xc3\xc7\xb0\xe2\xb3\x35\x28\x08\xcc\
+\xc4\x89\x90\x3e\xf2\x00\x60\xcc\x27\x10\xcc\xeb\x1d\x8a\xe5\x53\
+\x2e\x2e\x00\xa8\x5e\xda\x61\xc1\x7c\x41\x0d\x50\x86\x73\x7b\x87\
+\x00\x80\x5e\xa5\xdb\x10\xde\x78\x0a\x19\x49\x89\xf0\xf6\x52\x61\
+\x70\xbf\x2c\x2e\xea\xc5\x44\xa9\xe3\x91\xc6\x79\x81\x27\x1f\x7f\
+\x14\x1f\xd8\x4e\x0c\xdd\xeb\x0a\x00\xbc\x0b\x61\xd9\x33\x33\x5d\
+\x73\xfd\x1f\x30\x20\x77\x04\xbf\xd1\xac\xd4\x04\x6e\xb8\xd8\x74\
+\x86\x75\x90\xc4\xd9\x0e\xe6\x01\xac\xfa\xea\x1b\x1c\x0e\xed\x8f\
+\xe2\xa0\x34\x6b\x91\x6f\x66\x7c\xe7\xb9\x79\xbd\x18\x00\x26\x27\
+\xb9\xa0\x9b\x67\x4e\x1e\x2f\xdb\x02\x80\x5e\x3b\x8c\x00\x48\x2d\
+\xdf\x83\xd8\xda\x23\x48\x4b\x88\xe7\xb6\x40\x76\x56\x0a\x9f\x05\
+\x14\x13\xb9\x82\xb1\xc6\x88\xe0\xe2\x57\x5e\xe6\x4d\x44\x4f\x74\
+\x0b\x00\x4c\xbf\xfe\x66\x0c\x18\x3c\xc2\x1c\x03\x20\x77\x4f\x4c\
+\x81\x7e\xbe\x08\x64\x00\xa0\x84\xcf\xf5\x5b\xb6\xe3\x60\xf8\x60\
+\x94\x06\x25\x89\x00\xa0\xb4\x78\x2f\x48\x85\x79\x59\x0c\x00\x13\
+\x13\x1d\xbb\xb3\x6e\x42\xca\x45\x3b\x3a\x8d\x40\x33\x00\xf4\x30\
+\xe8\x84\xf7\x89\x15\xbf\x22\xb1\x8a\xb5\x98\x68\x9e\x23\x90\x99\
+\x92\xc0\xf5\xbd\x98\x48\x92\xd2\x94\x3a\x05\x83\x5e\x7b\xb5\xfb\
+\x00\x60\x13\x44\x4b\xb8\x5e\x7f\xcb\xdd\x48\xef\x9d\xcd\x6f\xb4\
+\x5f\x2f\x69\x83\x2d\x38\x40\x58\xe7\x67\xed\xe6\x9f\xb0\x6e\xcb\
+\x36\x1c\x8c\xcc\xb5\x06\x80\x95\xf8\xef\x3c\x9e\x97\xa9\xc1\xf2\
+\x09\x17\x17\x00\x14\xaf\xee\x14\x31\x1f\x1c\x00\xa6\x73\x89\xe5\
+\x07\x90\x58\x29\x00\x40\x13\x18\xc8\xd3\xe5\x29\x1d\x4c\x4c\xb4\
+\x52\x4a\x52\x5c\x14\x1f\x58\x4b\x5f\x5f\x8c\x45\x2f\x2d\x14\x5f\
+\xd2\x3d\x00\x70\xc3\x6d\xf7\x20\x35\xb3\x0f\xe7\x59\x76\xa6\x34\
+\x00\x68\x76\x8b\xe6\x08\xd6\x6e\xfe\x91\x49\x01\x06\x80\x28\x06\
+\x80\xe0\xe4\x4e\xb1\x2f\xf6\x02\x8c\x20\xe0\x00\x18\x97\xe0\x82\
+\x6e\x9e\x39\x29\x5e\xdb\x25\x01\x00\x83\x19\x04\x89\x65\xfb\x05\
+\x29\xc0\x00\x40\xb3\x83\xb4\xf0\x44\xa8\x44\xe2\x0c\x79\x08\x49\
+\xf1\x26\x00\xbc\x86\x57\x5e\xb6\x01\xc0\xd3\xae\x00\xc0\xfb\x10\
+\xd6\xeb\x37\xd3\x55\xd7\xce\x47\xff\xc1\xc3\x05\x15\x40\xe9\xdf\
+\x12\x16\x2d\x65\x05\x87\x32\x31\xf7\xd5\x26\x06\x00\x26\x05\x0e\
+\x46\x0f\x41\xa9\x26\xd9\x42\xec\x43\x52\x0a\xcc\xcb\x08\xc6\xf2\
+\xb1\x09\x2e\xe8\xe6\x99\x93\x62\xc9\x6e\x2b\xdd\xdf\x09\x00\x01\
+\x04\x89\xa5\xfb\xb9\x14\xa0\x68\x20\xb9\xcc\x54\x2a\x27\x35\x77\
+\x42\x36\x40\x5c\x74\x04\x7f\x24\xaf\x2f\x7e\x05\xaf\xbf\x6a\x33\
+\x2d\xdc\x3d\x6c\x80\xab\xe6\xcc\x43\x3f\x23\x00\x32\x53\xe2\x79\
+\x06\xac\x98\x48\xff\x47\x87\x87\xf2\xfc\xbf\x95\x5f\xae\x47\x5e\
+\xf4\x60\x94\x84\xa6\x5b\x8c\x78\xd8\x02\x80\x54\x40\x3a\x93\x00\
+\xa3\xe3\x5c\xd0\xcd\x33\x27\xc5\xd2\x5f\xec\x00\xc0\x80\x94\xe2\
+\x5d\x88\xab\x38\x84\xac\xe4\x24\xf8\xa9\xd5\x48\x4f\x8a\x93\x2c\
+\x93\xf7\xf6\xf6\xe2\xc5\x34\xf4\x5c\x97\x2c\x5e\xc4\x41\x20\x22\
+\x97\x00\xe0\x0d\x08\x19\x29\x66\x9a\x78\xf5\x6c\xe4\x8e\x9c\xc0\
+\x6f\x34\x2d\x29\x16\x2a\x2f\xdb\xd0\x2b\xf9\xb4\x54\x12\xbe\xeb\
+\xc0\x21\xbc\xfd\xd1\x6a\xe4\x47\xf4\x45\x7e\x54\xbf\x4e\xc6\x4b\
+\xd9\x01\x04\x80\x34\x26\x01\x46\x5d\x64\x00\x78\x73\x8f\xb5\x17\
+\x20\x02\x41\x66\xfe\x56\x44\xd6\xe4\xa3\x4f\x5a\x2a\x5f\x8b\x28\
+\x23\x39\x8e\x31\xdb\xd6\x75\x26\x37\x90\x2a\x8a\xe8\xb9\x3e\xff\
+\xec\x3f\xf1\xfe\x8a\x77\xc5\x97\x3c\xd0\x2d\x02\x41\xc3\xc7\x4d\
+\xc5\x98\x29\xd7\x70\xde\x25\xc5\x45\x4b\x2e\xef\x46\x0b\x2f\xa5\
+\xb3\x8e\x16\x14\x95\x60\xc1\x1b\x2b\x70\x3a\x38\x11\x87\x92\x46\
+\x5a\x33\x5f\x02\x04\xf3\x52\x19\x00\x46\xc6\x3a\x78\x6b\xdd\x83\
+\x14\x6f\xef\x95\x00\x40\x27\x10\x06\x1c\x5a\x8b\xc0\xa6\x0a\x0c\
+\xea\xd3\x9b\x27\xcf\x90\xe7\x24\x1d\x08\xf2\x43\x44\x68\xb0\x90\
+\x20\xfa\xe0\x7d\x58\xf3\xbf\xd5\xe2\x4b\x5c\x12\x08\xba\x1d\x42\
+\x81\x82\x99\xfa\x0c\x1c\xc2\xd4\xc0\x7c\x7e\xa3\x31\x91\xb4\xd8\
+\x93\xed\x9a\x39\xd4\xc1\xfe\xcc\x43\xa0\x05\x1b\xef\xfd\xe7\xcb\
+\xa8\xf7\xd1\x60\x57\xaf\x69\x02\xa3\xdd\xec\x48\x80\xd4\x20\x2c\
+\x1f\x7e\x91\x01\xe0\xdf\xfb\xec\x00\xc0\x80\x11\xbf\xac\x82\xb7\
+\xa1\x03\x43\xb2\xfb\xf2\x89\x2b\x72\x03\xa5\x88\x6c\x26\xb2\x9d\
+\xe8\xb9\xce\x9b\x7b\x2d\xcf\x0e\x12\xd1\x04\x57\x00\xc0\xa6\x14\
+\x2c\x26\x3e\x19\x37\xde\xf1\x00\xbf\x51\x0a\x5d\xca\x2d\xfe\x30\
+\xb0\x4f\x3a\x9f\x0d\x7c\x70\xc1\x62\x54\x36\xb6\x60\x6b\xce\x5c\
+\x18\xdc\x94\xd2\x52\xc0\xd8\x26\xc7\xf8\xe1\xa1\x5e\xa1\xb6\x89\
+\x1f\xfc\xd8\xf2\x55\xe2\x3c\xa3\xa1\x01\x5e\x50\xb9\x39\xb7\x76\
+\xc0\xa1\x46\x2d\x4a\xb5\x3a\x51\x59\x98\xc1\xfa\xbd\x5c\x9d\x00\
+\xa3\x71\xeb\x8e\xcb\x32\xdf\xb3\xa5\x11\x23\xf6\x7c\xc2\xc3\xe5\
+\x14\x0a\x26\x75\x49\xd3\xbe\x52\x44\x93\x6a\xfe\xc6\x14\xf1\x71\
+\x23\x87\xa2\xaa\xaa\x52\x7c\x49\x86\x2b\x00\x60\x53\x0d\xe4\xed\
+\xa3\xc6\x3d\xff\x78\x81\xc7\x01\xa8\xd4\x99\x74\xbd\x14\x65\x30\
+\x03\x91\x6c\x81\xd7\xde\xfb\x84\xd7\x01\xee\xe8\x7b\x35\x1a\xfd\
+\x42\xec\x02\xc0\xca\x48\x94\x9d\x32\x96\xcb\x1f\x04\x4e\x30\xf5\
+\x91\xe8\xe4\x6c\xe0\xfc\x03\xe5\x58\x51\xdc\x28\x9b\xe5\x63\x1d\
+\xe7\x17\xbb\x7a\x06\xdb\xd1\xaf\xeb\x3c\x0e\xad\x60\xba\xff\xc8\
+\x26\xa6\xdb\xc3\xb9\x1b\x48\xcf\x43\xae\x46\x80\xd6\x50\xa2\xec\
+\xe0\x96\x96\x66\x0c\x1b\x24\x99\x39\xe4\x92\xe9\x60\x52\x56\xb4\
+\x68\x81\x55\xb8\xef\xee\x47\xff\x05\xb5\xaf\x2f\xf3\x5d\x3d\xb8\
+\xae\x97\x22\x5a\x56\x2d\x81\x81\x63\xc3\xd6\xed\x3c\x1c\x7c\x2c\
+\x7e\x20\x4e\xc6\xf5\x73\x8c\xf9\x92\x81\x22\x74\x4e\x11\xdb\x24\
+\x8e\x08\xaf\x27\x86\xc5\x30\x00\x38\x57\x1c\x3a\xff\xb7\x0a\xac\
+\x28\x91\x00\x80\x5e\x02\x00\x32\x96\xbe\x5c\x4b\x3b\xfa\x03\x62\
+\x4a\x0f\xa3\x77\x5a\x0a\x8f\x02\x92\x8e\xf7\x95\xa8\x1a\xa6\x2e\
+\xa4\x27\xc5\xf3\x6a\xa1\x03\xfb\xf7\x72\x15\x20\x22\x1a\x84\x99\
+\xae\xca\x08\xb2\x29\x07\xbf\x66\xee\x1f\x85\x68\x20\xbb\x61\x72\
+\x6b\xdc\x25\xd2\x9c\x48\xec\x51\x71\x64\x49\x59\x05\xfe\xfe\xe2\
+\x1b\xa8\x09\x88\xc0\x9e\xec\x69\xd6\x36\x80\x3d\x00\xc8\xe4\x0c\
+\xd8\x26\x8b\x74\x82\xe0\xc4\xd0\x68\x24\x3a\x59\x1d\x3c\xff\x50\
+\x25\x56\x9c\x6e\xb4\x60\x3a\xba\x18\xfd\x8e\x03\x60\xe8\xf6\x55\
+\x50\x6b\x9b\x30\x22\xa7\x3f\x9f\x09\xa4\x35\x12\xdd\x24\xca\xe6\
+\x29\x06\x40\xc9\x22\xf4\x3c\x57\xbe\xbf\x02\x2f\xfe\x6b\x81\xf8\
+\x12\x5e\x2e\xee\x2a\x00\xbc\x05\xa1\x46\xcd\x4c\x83\x47\x4d\xc4\
+\xe8\xc9\xd3\xb9\x51\x43\x23\x5d\xae\x00\x72\x44\x4e\x5f\xde\xe1\
+\x87\x9e\x5f\x8c\xd2\xaa\x5a\xfc\x30\x7c\x2e\x3a\xbc\xbc\x8d\xc6\
+\xa0\xd2\x3e\xf3\xed\x49\x01\xa5\xb4\x1a\x38\x31\x38\xca\x79\x00\
+\xe4\x55\x61\x85\xb1\xde\xdf\xb1\xd1\xef\x00\x00\x98\x1a\xf0\xab\
+\x2b\xc7\xa0\xdd\x5f\x70\x23\xb9\x7f\x66\x3a\x9f\x33\x89\x0c\x93\
+\xde\x22\x89\xa2\xa6\xa6\x65\x73\x1f\x61\x1e\xc0\x86\xf5\x5f\x8b\
+\x2f\xa1\xca\xd1\x67\x5c\x05\x00\x9b\xa4\x90\xc8\x98\x78\xcc\xbd\
+\xe3\x41\xfe\xec\xe9\xe6\xe5\x3a\x96\x99\x1a\x8f\x28\xf6\xb7\xff\
+\xae\xdf\x84\xd5\xdf\x6c\xc1\xd1\x94\x5c\x9c\x4a\xc8\xb6\x66\xb2\
+\x3d\x89\x60\x13\x2e\x86\xad\x1d\x60\x3a\xcf\xfe\x77\x22\x27\xc2\
+\x79\x00\x1c\xa9\xc6\x0a\x9e\x11\x64\xb0\x96\x00\x7a\x83\xc4\x24\
+\x4f\x17\x20\xd0\x75\xce\x01\xa4\xe6\xfd\x80\xd8\xa2\x83\xc8\x4a\
+\x49\x42\x64\x68\x08\x97\x88\x72\x4b\xe6\xc4\x33\xc9\xa0\xf6\x11\
+\xd6\x0b\x98\x3c\x6e\x04\xaa\xab\xaa\xc4\x97\x8c\x61\xed\x7b\x57\
+\x01\xc0\xc6\x10\xa4\x8d\x9b\xee\xfa\xdb\x73\x50\xf9\xf8\xf0\xc4\
+\xcf\x34\x99\x7c\x77\x9a\x14\xca\xc9\xce\x40\x79\x65\x0d\xee\x7f\
+\xf6\x15\x34\xf9\x04\xe0\xe7\xe1\xd7\x49\x33\xdd\xcd\x01\x10\x58\
+\x02\xc0\x2a\x87\x50\x78\x3d\xd1\x9f\x19\x5b\x4e\xae\x10\x32\xff\
+\x18\x01\xa0\x59\x94\xe7\xd7\x15\xf3\xa5\x47\xbd\xe9\x58\xd9\xde\
+\x81\xe1\xdf\xbf\x07\x95\xbe\x03\xa3\x07\xe7\x70\x49\x49\x85\xb3\
+\x52\xb5\x93\x54\x18\x42\xab\x93\x93\xfe\x3f\x71\xec\x28\xae\x9f\
+\x7d\x8d\xf8\x12\xb2\xc1\x28\xed\xaa\xcd\x55\x00\x20\xca\x87\x68\
+\xb3\xa7\xa9\xb3\x6e\x44\x56\xbf\xc1\x1c\xb5\xa4\xbf\x7c\x54\xd2\
+\xeb\xf4\x8e\xca\xcd\xe6\x3a\x6e\xe1\x5b\xff\xc1\xbe\x43\x47\xb1\
+\xbf\xdf\x64\x54\x46\x26\xc9\x33\xdd\xcd\x9e\x14\x80\xad\x5d\x60\
+\x21\x11\x4e\xf4\x0d\x73\x1e\x00\x27\x6a\xb0\xa2\xa2\xb9\x73\x54\
+\x3b\x0b\x00\x9d\xed\xfb\x98\xfc\x7d\x48\xcb\xfb\x09\x71\x91\x11\
+\xc8\x4c\x16\xa6\xcc\xa5\x72\x00\x88\xc8\x93\xa2\xfd\x12\xe8\x39\
+\xfe\xfb\xed\x65\x78\x73\xc9\x62\xf1\x25\x9b\x59\x1b\x4b\x07\xae\
+\x04\x80\x4d\x48\x38\x39\xa3\x0f\xa6\xcf\xbd\x4d\x58\x4f\x97\x8d\
+\x74\xaa\x85\x97\x22\xf2\x7b\x69\xce\xe0\xe0\xd1\x7c\x3c\xbb\x64\
+\x39\x1a\xfc\x34\xd8\x39\x6a\x8e\x75\x3e\x80\x88\xe9\x7c\xf3\x05\
+\x3b\x01\x23\x31\xe3\x4d\xd2\xe0\x58\x2f\x02\x80\x73\x3b\x8e\xdc\
+\x5c\x50\x8b\xf7\x68\x8d\x20\x33\xf3\x61\xcd\x50\x91\xee\xe7\xfb\
+\x0c\xd2\x4c\x9f\x4e\x1a\x08\x34\xfa\x87\x7e\xc7\x46\x7f\x7b\x0b\
+\x46\x0d\x1a\xc8\x17\x8d\x26\xcb\xdf\x53\x66\x87\x92\x58\x66\x43\
+\xd1\xdf\xa9\x0b\x37\x5e\x3f\x0b\xc7\x8e\xd8\x6c\x49\x4c\x6b\x32\
+\x3c\xef\x6a\x00\x8c\x67\x6d\xa3\xe5\x09\x5a\x14\xe2\xf6\xbf\x3e\
+\x0d\x95\xb7\x0f\x0f\x71\xa6\xc4\x47\x4b\xd6\x07\xd2\xdf\x26\x8c\
+\x1c\xc4\xc5\xdf\xd3\xaf\xbd\x8b\xbc\xe3\x05\x38\x30\x70\x32\x2a\
+\xa2\x53\x8c\x8c\x57\xda\x80\x80\x72\xfe\x3d\xac\x8c\x44\x74\xfe\
+\x5d\x21\x56\x07\x30\x03\xe1\xd7\x8c\x10\xc4\x7b\x3a\x07\x80\xdb\
+\x4f\xd6\x61\x65\x75\xab\xed\xc8\x37\x58\x32\xd6\x08\x0e\x76\x4c\
+\xf9\x7e\x6d\x1d\x7a\x19\x29\xa0\x47\xec\xd1\x3d\x48\x3d\xf8\x13\
+\x8f\xeb\x67\x67\xa4\x09\x1b\x63\xc8\x2c\x18\x45\xde\x53\x72\x42\
+\x0c\x57\x11\x27\x0b\xf2\x71\xc3\xac\xe9\x52\x97\x51\xe1\xc5\x71\
+\x57\x03\x80\x9e\x6a\x29\x44\x3b\x7d\x8e\x9b\x36\x1b\xd9\xc6\xec\
+\x20\xca\x67\x93\xf3\x06\xc8\x46\xa0\xfc\xc1\x23\x05\x27\xf1\xc4\
+\xa2\xb7\xd0\xe2\xed\x87\x9f\x27\xfc\x1e\x7a\xca\x26\xb2\x62\xbe\
+\x92\x1f\x07\x31\x26\x7a\xb8\x2b\x6d\x47\xbc\xa5\x44\xb0\x04\x80\
+\xf1\xdc\xce\xe4\x20\xc4\x39\x09\x80\xbf\x14\x37\xe0\xa3\x9a\x56\
+\x79\xd7\xcf\x04\x0a\x23\x93\x29\xd7\xaf\x86\x22\x87\x66\xc6\xeb\
+\xcd\xc7\x9e\xcd\x4d\xc8\xdd\xf8\x01\xbc\x98\xee\x1f\x3f\x74\x10\
+\xaf\x98\xa2\x26\x95\x35\x45\x44\xd3\xc3\x21\x1a\xa1\x32\xf8\xf5\
+\x45\x2f\x62\xd5\x7f\xde\x13\x5f\x42\x2b\x89\xf7\x37\xbd\x71\x25\
+\x00\x88\x6c\x12\x44\x35\x61\x11\xb8\xf1\xae\xbf\x71\x7e\x50\x98\
+\x93\xac\x59\x29\xa2\xea\xd8\x49\xa3\x06\xf3\x87\xf1\xc6\x7f\x3e\
+\xc3\xd6\x9d\x7b\x51\x98\xd2\x1f\xc7\xb3\x47\x5a\x8b\x7f\xe3\x71\
+\x08\x15\x8c\x12\x23\xc5\xba\x5e\x52\xf7\x77\x4a\x81\xad\x89\x01\
+\x88\xf1\x70\x0e\x00\x0f\x32\x17\xf0\xd3\xda\x36\x6b\x00\x58\x4a\
+\x00\x91\x5a\x68\x64\xcc\xaf\x6c\xd5\x59\x31\xde\xf4\xb7\xde\x3f\
+\xad\x41\x58\xc9\x71\xae\xf7\x53\x13\xe2\xcc\x5b\xc9\x48\x11\xfd\
+\x8d\xb2\x83\x48\x0a\xb4\x6b\xdb\x30\x73\xda\x64\xd4\xd5\xd5\x8a\
+\x2f\xa3\xed\xe6\xcc\xf3\xc2\xae\x06\x00\xed\xfc\xb9\x4b\x7c\x72\
+\xc6\x4d\x77\x20\x3e\x39\x83\xf3\x81\x8c\x19\xb9\xfd\x00\x28\x5e\
+\x90\xdb\x2f\x0b\x75\x0d\x8d\xcc\x23\x78\x15\xf5\xcd\x2d\xd8\x33\
+\x66\x26\xea\xc2\x62\xac\xa5\x00\x7b\x0d\x66\x86\x5c\xa4\xb7\x44\
+\x11\x49\x17\xf1\x80\xf5\xb1\x01\x88\xf6\x70\x6e\x2e\xe0\xef\xe5\
+\x4d\x58\x5d\xdf\x66\xed\x01\xd8\xf8\xff\x9d\x00\x28\x6d\xe9\x40\
+\x55\x5b\x87\x35\xf3\xd9\x6b\x58\x61\x1e\x7a\x6f\x5f\xc7\xf5\xf9\
+\xb8\x21\x83\x8c\x1b\x62\xb9\x71\xeb\x5e\x8a\x82\x8c\x76\x13\x5d\
+\xb7\x66\xf5\xe7\x78\x61\x81\x4d\x3d\x20\x59\xff\x54\x76\x65\xf6\
+\x09\x5d\x0d\x00\xa2\x6d\x10\x4a\x95\xcd\x14\x1d\x9f\x8c\x59\x37\
+\xdf\x6d\x5c\x26\xce\xc3\xee\xf2\xe9\xc3\x07\xf5\x65\x3e\xb1\x06\
+\xbb\x7e\xcd\xc3\xc2\x37\x3f\x40\xab\xb7\x2f\x76\x4d\xbe\x1e\x5a\
+\xb5\xaf\x59\xfc\x13\x53\x3d\x99\xf8\xcf\x0e\x10\x56\xcd\x90\x8d\
+\x03\x48\x94\x92\x7d\x1e\xe5\x87\x28\x27\x17\x92\x7e\x8a\x19\x80\
+\x6b\x1a\xb5\xd2\xfa\x5f\xe4\x09\x18\x18\x10\xf6\xd5\xb5\x09\x2b\
+\x89\x5b\xa8\x00\x9f\xda\x6a\xe4\xac\x5f\x09\x0f\x9d\x20\xfa\xfd\
+\x8d\xaa\x50\x8e\xf9\xc2\x9e\x44\xd1\x70\x67\xaa\x81\xd6\x15\x98\
+\x77\xed\x0c\x14\x17\x17\x89\x2f\xb3\x59\x32\xb6\x3b\x00\xe0\x5a\
+\xe3\x8d\x59\xd1\xcc\x79\x77\x21\x26\x31\x55\xb0\x05\x78\xbc\x5b\
+\x5a\x0a\x90\x3b\x38\x69\x54\x2e\x5f\x4d\x73\xf9\x67\x6b\xb0\xee\
+\xfb\x6d\xa8\x0d\x89\xc2\x9e\x49\x73\x60\xa0\x39\x72\xb3\x14\x50\
+\x22\xdd\xd7\x03\x91\x2a\x77\x51\x1a\xb9\xbd\xc9\x21\xe0\xc3\x70\
+\x5f\x44\x38\x09\x80\xe7\xab\x5b\xb0\xae\xa9\x5d\xde\x08\xb4\x00\
+\xc1\xe9\xd6\x0e\x1c\x6e\x6c\xb7\x0a\xfa\xb8\x37\x37\x63\xe0\xfa\
+\x55\x50\xd7\xd7\xa0\x7f\x56\x3a\x33\x86\x63\xf9\x2a\xa8\xa2\xe9\
+\x45\x2b\xa2\xe0\x59\xa8\x46\x18\xfd\x1b\xd6\x7e\x89\xe7\x9f\x79\
+\x52\xea\x32\xaa\xc8\xb6\x9a\x13\xee\x0e\x00\x20\x05\x4b\x41\x21\
+\xab\x92\x60\x92\x02\x33\xe7\xff\xd9\xb8\x4a\xb8\xbb\x90\xde\x2c\
+\x83\x7e\x92\x00\xa3\x87\xf4\xe7\x3b\x82\x2c\x78\x63\x39\x7e\x3d\
+\x72\x02\xe5\x71\xa9\xf8\x6d\xcc\x55\x02\x08\x8c\x6a\x80\x8c\xc0\
+\x51\x41\x2a\xa8\x64\xad\x7f\xd1\x04\x11\xfb\xef\x9d\x50\x1f\x84\
+\x3b\x39\x1d\xfc\x72\x6d\x2b\xbe\x6d\x6e\x97\x08\xfd\xc2\xca\x18\
+\x6c\x65\xa3\x7d\x0b\x33\x16\xdb\x2d\x46\xbf\x5b\x5b\x1b\xb2\x37\
+\x7c\x86\xc0\x8a\x62\x24\xc6\x44\x61\x70\x76\x6f\x61\x37\x33\xbe\
+\x59\xa5\x34\x00\xc8\x2b\xa2\x99\x3f\x0a\x91\xd3\x2e\x22\x37\xdf\
+\x30\x0b\x25\xb6\xa3\x7f\x33\x8c\xbe\x7f\x77\x03\x00\xd1\x3c\x48\
+\xec\x12\x32\x75\xf6\x7c\xa4\x66\x09\x13\x44\x94\x12\x1e\x6c\x67\
+\x87\x10\x5a\x2d\xa3\x77\x7a\x12\xdf\xf8\xf1\x91\x17\x96\xf0\xad\
+\xe0\xca\xe3\x19\x08\xc6\x5f\xcd\x40\xe0\x6e\x61\x0b\xb8\x61\x74\
+\x80\x97\x50\x02\xee\xc0\xc4\x50\x9a\x27\x73\x1f\xc5\x45\xa4\x56\
+\x64\x31\x9f\x6f\x64\x70\x31\x63\x68\x2d\x8d\x66\xcb\x91\x6f\x25\
+\xfa\x85\x9d\x45\xb6\x30\xd1\xcf\x75\xbf\x51\xef\x73\xe6\x7f\xfd\
+\x09\x02\xcb\x8b\x11\x11\xa2\xc1\xd8\x21\xc2\x12\x0a\xb4\xf4\xbd\
+\xce\x72\x93\x01\x11\x51\xd8\x9c\x54\x04\xdd\xe6\xa7\xab\x3e\xc0\
+\x9b\x4b\x5e\x95\xba\x6c\x0c\x24\x76\x0f\xe9\x2e\x00\x90\x94\x02\
+\x7e\x01\x41\x98\x7b\xe7\x43\xf0\xf4\xf4\xe2\x7e\x6d\x14\x33\x08\
+\xbd\x3c\xe4\xe7\xe6\xc9\x1e\xa0\x5c\x02\xda\x1e\xe6\xa9\x57\xdf\
+\x11\x40\x90\x98\x86\xdf\x26\x4c\x37\xaa\x03\xc1\x26\x08\x65\x20\
+\x18\xc7\x13\x3d\x14\xd6\x96\xbf\x78\x42\xc8\xf2\xbd\xf9\x69\x49\
+\x00\xc0\x32\xb9\xc3\x66\x02\x08\x22\x10\xe8\xf9\xc8\xff\x8e\x31\
+\xbf\x82\x5c\x3f\x9d\x71\xe4\x33\xe0\x66\xaf\xf9\x08\x81\x65\xc5\
+\x3c\xc5\x9b\xf4\x3e\x8d\xe8\xb6\xb6\x76\xb4\xb7\xb7\xcb\xf6\x99\
+\x0c\xe4\x68\x63\xde\x5f\x55\x65\x39\xfe\x38\xef\x7a\x34\x35\x35\
+\x8a\x2f\xdb\x0c\x89\xd1\xdf\x9d\x00\x40\x24\xb9\x68\x74\xbf\x21\
+\xa3\x31\x72\xd2\x74\xf3\x86\x11\x7c\x0d\x3c\xa5\xbc\x21\x34\x2a\
+\xb7\x1f\x8f\x84\x59\x82\xa0\x36\x22\x06\xbf\x4e\x99\x01\x2d\x85\
+\x4e\x8d\xf6\x80\x2f\xb3\xec\xc7\xf9\x7b\x21\xda\x53\x29\x63\x00\
+\x76\xda\x01\x52\x6b\x07\x59\xf2\x5f\x76\xa9\x17\x9b\xa9\x60\x03\
+\x4a\xb4\x7a\x7c\xcb\x3c\x84\x46\x5e\xe8\x29\x44\xff\xbc\xab\xab\
+\xd1\xf7\xab\x8f\xa1\x66\x86\x1f\x8d\xfc\xf1\xc3\x06\xf3\x20\x57\
+\x6b\xab\x96\x57\x43\xcb\x11\x89\x7e\x5a\x4a\x8f\x5e\xa9\xef\xcf\
+\x3c\xfe\x37\xfc\xb0\x65\x93\xf8\x32\x5a\x07\x80\x44\xc9\x3e\xa9\
+\xef\xe8\x0e\x00\x88\x87\x90\x1b\x40\x93\x13\xb4\xfd\xa9\x55\x3a\
+\x90\x42\xa1\xc4\xef\xe6\xdd\x89\xa8\xb8\x24\x73\x04\x8c\xaa\x84\
+\x65\x1f\x0a\x1b\xe9\x23\x72\xb2\xb9\xcd\x40\xea\x60\xe1\x5b\x1f\
+\x70\x9b\xa0\x55\xed\x87\xdf\xa6\xce\x40\x5d\x6c\x9c\x95\x7b\xd8\
+\xcb\xc7\x03\x23\xd5\x1e\x50\xbb\x29\x45\x36\x80\xc4\xab\xd4\xbe\
+\x02\xf6\x00\x60\x21\x09\x9a\x98\x08\xdf\xca\x0c\xc3\xdf\x9a\x3b\
+\xcc\x8c\xa7\xf3\x21\x47\xf2\x90\xb9\xe1\x7f\xf0\x60\x7e\x3b\x55\
+\x46\x8f\xca\xe9\xcf\x7f\xa3\xb9\xa5\x8d\xe7\x3f\xca\x11\xdd\x52\
+\x54\x78\xa8\x71\xc6\x4f\x89\x4d\x1b\xd7\x61\xe1\xb3\x92\x86\x9f\
+\xcd\x02\xd1\xdd\x05\x00\xa4\xf7\x29\x08\x94\xd3\xd5\x85\xa4\x0a\
+\xae\xbf\xfd\x01\x78\xa9\x84\xe9\x4d\x02\x00\xa5\x43\xcb\x11\xb9\
+\x4a\x23\x07\x67\x23\x21\x36\x92\x1b\x4f\xef\x7f\xf1\x35\xf7\x0e\
+\xf4\xec\xfc\xa9\x01\xb9\xc8\x1f\x35\x16\x7a\x5a\x83\xc8\x08\x04\
+\x5a\x5d\x2c\xdb\xdb\x1d\x83\x98\x87\x10\x6e\x8a\x16\x4a\x01\x40\
+\xc6\x08\xb5\xb7\xda\x47\x59\x87\x01\xbb\x5b\xda\xb1\x87\xf9\xfa\
+\x5c\x8f\x1b\x19\xef\xce\x98\x9b\xc2\x98\x16\x75\x70\x3f\xef\x53\
+\x2e\x33\xf6\xfa\xa4\xa7\xb0\x8f\xe8\xd1\xd0\xd4\x82\x86\xc6\x66\
+\xd8\x23\x7a\x06\x1a\xe3\x5a\x80\x15\x65\xa5\xb8\xfb\x8f\xf3\xa5\
+\x44\x3f\x25\x01\x92\x5a\xad\x93\xfb\x1e\x57\x00\x80\x9c\x7a\x72\
+\xfb\xc6\x38\xfa\x81\xde\xfd\x72\x70\xed\xfc\x3b\x50\x5e\x2d\xf4\
+\x83\xa6\x3b\x29\xe3\xb5\xab\x9d\xb9\x29\x7b\x88\x1a\x59\xd1\x14\
+\x27\x58\xfa\x9f\xff\xf2\xa5\xe5\x5b\xfc\x03\x70\x6c\xdc\x24\x54\
+\xf4\xea\xd5\x19\x2b\x30\x82\x21\xc2\xd3\x0d\x59\xac\xa5\x79\xb8\
+\x21\xd6\x5d\x21\xf8\xdd\x4a\x47\x25\x80\x81\xbb\x6b\xa7\x18\x93\
+\x8f\xb4\xeb\x70\xb0\x4d\x87\x52\x12\xf5\x16\x8c\x57\xb0\xf3\x11\
+\x7b\x7f\x41\xf2\xe6\x8d\xf0\x6c\x69\xe1\x12\x6d\xe2\x88\x5c\x84\
+\x31\x17\x8e\xfc\x77\x5a\x00\x8b\xf6\x2b\xb6\x47\xfe\xec\x33\xa6\
+\x80\x0f\xd9\x07\x0f\xff\xdf\x9d\x52\x13\x3e\x44\xb4\xe1\xf6\x67\
+\xf6\xbe\xeb\x42\x03\x80\xf2\x00\x68\x02\x28\xc6\xd1\x0f\x10\xf3\
+\x6f\xbe\xf3\x3e\x1e\xe5\xa2\x87\x43\xbb\x7f\x12\x91\x8e\xa4\xb8\
+\xb7\xa7\x87\xfd\xa9\x5a\xca\x8c\x25\xbb\x80\xc2\xa7\x14\x31\xfc\
+\xe0\x8b\x75\x3c\x6c\x4c\xd4\x10\x1e\x81\xfc\xd1\x63\x51\xd9\x2b\
+\xcb\x06\x08\x64\x27\x90\xa7\x40\x31\x80\x30\xda\x91\x83\x1d\xfb\
+\xd1\x2a\x65\xa2\x55\xc4\xda\x19\x53\x1b\x58\xab\x60\xad\x9c\x59\
+\xff\xa5\xac\x75\x98\xe2\xf9\x3a\x83\x05\xe3\x3b\x10\xbe\x67\x2f\
+\x12\xb6\x7e\x0f\x9f\x9a\x6a\xce\xbc\x01\xbd\xd2\x91\xd3\x27\x8b\
+\xeb\x70\x2d\xfb\x3b\x2d\x7b\xa7\xd5\xb6\xdb\xed\x0f\xed\x17\x48\
+\x56\x3f\xdf\x28\x8a\xdd\xc7\x2b\x0b\x17\x60\xd3\x37\xeb\xa4\x2e\
+\x95\xdd\x27\xc8\x92\x2e\x24\x00\x88\xf9\x9b\x21\xd2\xf1\xf6\xc8\
+\x92\xf9\xb4\x3e\x10\x95\x3f\xed\xcf\x3b\x8e\xc2\x22\x01\x04\xc2\
+\xaa\x21\x01\x5d\x6e\x15\x4f\xdb\xad\xf2\xcd\x9a\x99\x71\x48\x23\
+\x94\x26\x90\x56\xfd\xef\x1b\x3e\x8b\x48\xd4\x14\x12\x82\x92\x9c\
+\x1c\x9c\xce\x19\x88\x0e\x8a\xb8\xb9\x29\xad\xec\x04\xcb\x88\xa2\
+\xd9\x40\xe4\x64\xb0\x09\xdf\x8a\x19\xaf\xaa\xac\x42\xc4\xce\xdd\
+\x88\xda\xbd\x0b\x2a\xe3\x62\x8d\xa4\xeb\x47\x0c\xcc\xe6\xf3\xf9\
+\x24\x9d\x6a\xeb\x1b\x51\x5e\x55\xcb\x25\x80\x3d\xa2\x0c\x5f\xf2\
+\x84\x68\x1e\x84\x00\xf4\xe9\x87\xef\x63\xe5\x7b\xef\x48\x5d\x4a\
+\x1d\x23\xbb\xaa\x0e\x5d\xd0\x85\x02\x80\x5d\xe6\x87\x46\x44\x23\
+\x2a\x36\x9e\x57\xb3\x96\x9f\x2e\xc6\xc9\x82\xe3\x92\xcc\xa7\x98\
+\x38\x8d\x80\x6f\xb6\xee\xc0\xc9\xe2\x32\x01\x04\x8c\xf9\xb4\x20\
+\xa2\xd4\x5a\x79\x62\xa2\x52\xe9\x41\xd9\x99\x3c\xd1\x44\xcf\x74\
+\x2d\xe5\x13\xac\xd9\xf4\x23\x4f\x2a\x21\xd2\xb3\x07\x5b\x97\x10\
+\x8f\xaa\x8c\x74\x54\x67\x65\xa0\x89\x59\xd8\x06\xcb\xd9\x45\xcb\
+\xe9\x64\xce\x7f\x83\x05\xb3\x05\xc6\x2b\xdb\xb4\x50\x9f\x2c\x86\
+\xe6\x60\x1e\x34\x87\x0e\x23\xe0\xd4\x29\xe1\x41\x33\x86\x65\x24\
+\xc5\xf3\xc0\x4e\x48\x70\x10\xd7\xf5\xad\xcc\x48\x25\x2f\xa5\xa9\
+\xb9\xb5\xcb\x7b\xa7\x15\xd2\xc2\x43\x35\x46\xe6\x03\x1b\xbe\xfe\
+\x1f\xde\x7c\xed\x65\xa9\x4b\xe9\xcb\x46\x40\x14\xf1\x73\x25\x00\
+\x64\x99\xef\xc3\x2c\xf3\xa9\x33\x6f\x44\x66\xef\x6c\x1e\xea\x25\
+\x06\x93\x55\x5b\x5a\x54\x08\x9f\x00\x0d\xcf\x77\xb3\x64\x3e\xad\
+\x12\xe2\xe3\xed\xc5\x1f\xc2\x27\x6b\x36\xe1\xd4\x69\x61\x63\x24\
+\x7a\x1f\xe0\xef\x2b\x3b\x4b\x66\x49\x74\xad\x29\x68\x44\x12\x84\
+\xef\x26\x5a\x59\x83\x2d\x3b\xf7\x60\xdb\x2f\x07\xd8\x48\xec\x5c\
+\x49\x4b\xc7\x40\xd5\x14\x15\x81\xa6\x88\x70\xb4\x32\x8b\xbb\x2d\
+\xd0\x9f\x19\x8f\x1e\x56\x00\x70\x6b\x6e\x83\x17\x73\x39\xbd\xcb\
+\xca\xe1\xc3\xee\x47\x7d\xba\x14\x4a\x8b\x91\x4c\x89\x99\xbd\xd3\
+\x92\xd1\x2b\x35\x99\x83\x97\x7e\x8f\xf6\x3a\xa0\xcc\x66\x52\x69\
+\x8e\x10\xf5\x9b\x62\x03\x82\xd8\x57\x60\xfb\x8f\x5b\xb0\xe8\xf9\
+\xa7\x79\xd4\x4f\x82\xe6\xb3\xb6\xc2\x51\xe6\x9c\x6f\x00\xd8\x65\
+\xfe\xec\x9b\xef\x46\x6c\x5c\xbc\x15\xf3\xd5\x3e\x2a\x6e\xe1\xd3\
+\x7c\x37\xb9\x41\xf4\xde\x92\xf9\x34\x45\x4c\x7f\x6b\x66\xa3\xe6\
+\x93\xb5\xdf\xe1\x54\x89\x00\x02\x32\x0c\xc9\xa0\x52\x79\x39\xb6\
+\xa8\x23\x49\x0c\x2a\x44\xa5\xcc\x22\x02\x18\xa9\x06\x6a\xc4\x98\
+\x03\x87\x8f\x21\xef\x44\x21\x0a\x8a\x4e\xf3\xad\xe6\x9d\x21\x8a\
+\xc9\x53\x15\x73\x7c\x4c\x14\x92\xe3\x62\x78\x3e\x03\x89\x76\xca\
+\xfa\xa1\x05\xae\x69\x89\xfb\xaa\x9a\x7a\x0e\x04\x47\x28\x28\xc0\
+\x97\x4b\x41\xd3\x8e\xe1\x9b\xbe\xf9\x1a\x6f\xbd\xbe\x48\x4e\x5d\
+\x90\x1b\xfd\x37\x67\xee\xf7\x7c\x02\xe0\x8c\x99\xef\xc3\x5e\x49\
+\xe4\x11\x93\x68\x37\x30\x3a\xb6\x64\x7e\x3b\x33\x98\xc8\x4d\xaa\
+\xac\xad\xc3\x96\xed\x7b\xd8\x43\xad\x16\x3a\xa3\x10\x36\x99\xf2\
+\x35\x66\xc3\x3a\x4a\x91\x61\xc1\xbc\x28\x95\x96\xa8\xa3\x60\x13\
+\x31\x87\x54\x04\xbd\x12\x08\xc9\x38\xab\xa8\xae\xe5\x3b\x90\xd7\
+\x10\x20\x68\x16\xcf\x20\x2c\x5c\x4d\x6b\xf2\x92\x37\x42\xfa\x9c\
+\xd2\xd8\x3c\xf9\x6c\x9c\x81\xef\x59\xac\xe7\x5b\xd5\xb7\xa1\x82\
+\x49\x18\x92\x32\x8d\x76\xfc\x7a\x31\xd1\x68\x0f\x65\xaa\x82\xfa\
+\x2d\x30\x5f\x81\x2f\x3f\xfb\x08\xab\xde\x7f\x47\xee\x23\x0e\x19\
+\x7d\x17\x0a\x00\x67\xcd\x7c\x6f\x95\x27\x7b\xb0\x5e\x3c\x43\x98\
+\x46\x0f\x3d\x64\x4b\xe6\x57\x33\xb1\x5b\xc9\xdc\x42\xda\x1d\xf4\
+\x58\x61\x91\x59\x8f\x9a\xe6\xcc\x09\x04\x5d\x19\x87\x52\x44\xc5\
+\x94\x54\x6d\x43\x35\x8a\xdc\xcb\xf0\x14\x18\x6a\x30\xba\x78\xa4\
+\xbb\xcd\xef\xf9\x39\xbd\xf9\x98\xee\x93\x98\x5c\xdf\xd0\xc4\xc5\
+\x7b\x15\x03\xa8\x23\xfa\x5d\x4c\xd4\x57\x72\x73\x29\xb1\x83\x18\
+\x44\x60\x7c\x77\xe9\xab\xd8\xbc\x71\xbd\xdc\x47\x68\xdd\x5f\xda\
+\x73\x59\xe7\xf0\x8f\x18\xe9\x7c\x00\xe0\x9c\x31\x9f\x74\x3a\x8d\
+\x48\x37\xa3\xce\x6d\xa7\x7d\x02\x44\xcc\x2f\x63\x23\xab\x92\x8d\
+\x4e\x6d\xbb\x16\xed\x5a\xad\x39\x58\x44\x23\x88\xa4\x81\x8a\x02\
+\x3e\x4e\x48\x03\x31\xd1\x88\x16\xee\x53\xc5\xb7\xae\xa7\x7b\xa3\
+\x1a\x46\x1a\xfd\x14\xd8\xa1\x11\x4e\xa0\x24\xdf\xbd\x99\xf9\xf5\
+\x1d\x3a\xfd\x19\xff\x16\xc5\x1c\x68\x21\x0c\xd3\xfe\x7f\xd4\x6a\
+\xaa\x2a\xb1\xf8\xc5\x67\x71\xfc\x48\x9e\xdc\xc7\xce\x98\xf9\x44\
+\xe7\x1a\x00\xe7\x9c\xf9\xee\xcc\xf2\x26\xa3\x89\x42\xa3\xb4\x55\
+\x0c\x89\x60\x29\xe6\xaf\xfb\xf4\x3d\xe6\x41\x14\x61\xd2\x8c\xb9\
+\xe6\xb0\x31\x35\x32\xfa\xe8\x3b\xdd\x1d\xf0\x12\x5c\x49\xf4\x1c\
+\x02\xfd\xfc\x78\x7c\xc3\xa4\xef\x7f\xd9\xb9\x9d\x8f\xfc\xc6\x06\
+\x59\x63\xf1\xac\x98\x4f\x74\x2e\x01\x70\xde\x98\x4f\x62\xb5\xb1\
+\xa9\x85\x8f\xb2\x7a\xee\x33\xdb\x32\xff\x78\xde\x01\xa1\x43\x0a\
+\x25\xb2\x73\x47\x62\xc8\xd8\xa9\x7c\x16\xd1\x04\x04\xfa\x2e\xfe\
+\x9d\xee\xce\xab\x85\xf3\x49\x94\xd0\x42\x15\x3e\xa4\xde\x84\x68\
+\xb3\x02\x4d\x8d\x0d\xf8\xf8\x83\x77\xf0\xe3\xf7\xdf\xd9\xfb\x28\
+\xad\xf8\xf4\xe0\xd9\xfe\xfe\xb9\x02\xc0\x79\x67\x3e\xbd\x92\x41\
+\x46\x2a\xa0\x90\x59\xe7\x04\x00\x31\xf3\x2d\x89\xe6\x0f\x46\x4c\
+\x9a\x6e\xce\x27\x30\x8d\x2a\x37\x25\xed\x45\xec\xc6\x25\x83\xab\
+\xa6\x42\x48\x3d\x11\xe3\xb9\x9d\xe2\xee\x66\xbe\x3f\xb2\x27\xb6\
+\x6d\xf9\x0e\xff\xfd\x70\x05\x1a\x1b\x65\xbd\x0f\x9a\xdd\xa3\x7a\
+\x8a\x77\x1c\xff\x45\x79\x3a\x17\x4f\xe0\x82\x31\x9f\x0c\x2a\xb2\
+\xc4\xc9\x35\xcb\x3f\x59\x82\x35\x1f\xbf\x2b\xc9\x7c\x4b\xa2\xcc\
+\xa2\x21\x63\xa6\x98\xd3\xcb\x84\xa9\x7f\x21\xc6\x4f\xf3\xed\x6e\
+\xdc\xb7\x76\x2e\xe3\xe7\x4c\x89\x0c\x4a\xf2\x64\xa8\xbf\x54\xd9\
+\x6b\x09\xcc\x7d\xbb\x77\xe0\xab\xff\xae\x42\xd1\xc9\x02\x7b\x5f\
+\x41\x69\x3e\x14\xdf\xff\xf9\x5c\xdd\xd3\xd9\x02\xe0\x82\x33\xbf\
+\xb9\xb5\x95\x1b\x5e\xc7\x0e\xe7\x61\xe9\x8b\x4f\x39\x7c\xa3\x04\
+\x84\xc1\xa3\x27\x99\xb3\x8d\x3b\x1f\xbe\x00\x06\x6a\x9d\x79\x06\
+\xe7\x46\x32\x10\xb8\xc8\x95\xa5\x0a\x1e\x4a\x64\x51\x1a\xa3\x78\
+\xa6\xdf\xa5\x6a\xa0\x5f\xf7\xed\xc6\xda\x2f\x3e\xee\x8a\xf1\x44\
+\x54\xce\x4d\x15\xd5\x55\x5d\xfe\xb0\x13\x74\x36\x3d\x75\x19\xf3\
+\xa9\x51\xb2\xc4\xdb\x8b\x9e\x46\x59\xc9\x29\xa7\x6e\x9a\xea\x0e\
+\xb2\x07\x8f\x44\x46\xdf\x81\xbc\x02\xc9\x0c\x04\xc0\x1c\x69\xe3\
+\xef\x8c\xe7\x60\xca\xc4\xb3\x88\xdb\x98\x0e\x4d\x3a\x1b\xc6\xcf\
+\x12\x83\x49\xb5\x90\x64\x21\xd1\x6e\x8a\xd9\x5b\x01\x8e\xfd\x6b\
+\xa8\xaf\xc5\x8e\x9f\xbe\xc7\x8f\x9b\xbe\x41\x75\x55\x45\x57\xb7\
+\x4c\x89\xfd\x94\xcb\xef\x70\x74\xef\x42\x00\xc0\xe5\xcc\x6f\x63\
+\x2e\xdf\x1a\xa6\xff\x0f\xed\xdd\x29\xbe\x85\x52\x38\x30\xe1\x44\
+\x65\x68\x09\x29\x99\x48\xeb\xdd\x0f\xc9\xe9\x7d\x78\x55\xb2\x18\
+\x04\x96\x22\xda\x52\x62\x08\x13\x82\xf6\xfe\xae\x30\x83\xc3\xd4\
+\x1a\xeb\xeb\x70\xf0\xc0\x2f\xd8\xb3\x73\x1b\x8e\x1f\x3e\xd8\xe5\
+\xc4\x8f\x91\x56\x19\x99\x5f\x76\x3e\x98\x7f\xa6\x00\xe8\x16\xcc\
+\xa7\xe9\xd3\x0d\x5f\xac\x44\xde\x3e\x1b\x00\xcc\x87\x90\x08\x41\
+\xcb\x62\xa6\xc0\x01\xa2\xd2\xf4\x88\xa8\x58\xc4\x26\xa5\x21\x2a\
+\x36\x11\xb1\x89\x29\x7c\xb9\x9a\xb3\x01\x41\x2d\xf3\xdf\x8b\x4e\
+\xe5\xa3\xf0\xf8\x11\xe6\xc3\x1f\xc2\xe9\xa2\x93\xce\x3c\xe3\x5d\
+\x46\xc6\xff\x78\xbe\x18\x7f\xa6\x00\xe8\x36\xcc\xa7\xe8\xdb\xca\
+\x65\x0b\x51\x55\x56\x22\xbe\x95\x69\xac\xad\x81\x90\x68\x4a\x4b\
+\xd2\x3e\xea\x28\x10\x2c\x89\x16\xae\xd2\x84\x46\x20\x28\x24\x14\
+\xfe\xcc\xa3\xf0\x61\x80\x50\xa9\x7c\xb8\x6a\xf0\xf3\x0f\xe0\xfa\
+\xbb\xb9\xb9\x91\x83\xa0\x85\xbd\x36\x37\x35\xa2\xbe\xb6\x06\x55\
+\x15\x65\xa8\x2c\x3f\x8d\xd6\x16\xc7\xc3\xbe\x16\x44\x89\x0a\x4f\
+\xb0\xb6\xfa\x4c\x3e\x7c\x26\xe4\x0c\x00\xba\x15\xf3\x4f\x9e\x38\
+\x8c\xd5\x1f\x2c\x93\xba\x4f\xba\x3f\x4b\x91\x49\x40\xa0\x15\x12\
+\x68\x9f\xbc\x21\xe8\x9e\x44\x19\x1d\x24\xb1\xd6\x9f\xed\x17\x39\
+\x4b\x8e\x02\xa0\x5b\x31\xbf\xba\xb2\x0c\x9f\xaf\x58\xc2\x46\x9d\
+\x8d\xaf\x6c\xb5\x35\xba\x04\x65\x43\xb0\xa4\x29\x7a\x16\x02\xd7\
+\x12\xb9\x74\xb4\x7b\x2a\x25\x6d\x16\xba\xea\x26\x1c\x01\xc0\xc5\
+\xc2\x7c\x22\x1a\xe9\x8e\x88\x4f\x92\x0a\x63\x20\xf8\xd4\x53\x20\
+\x5a\xa9\xe4\x3c\x12\x05\xf4\x29\xf5\x9d\x5c\xba\xf3\xae\xdf\x1d\
+\xa1\xae\x00\x70\x31\x31\x9f\xee\x73\x2c\xce\x8c\xa8\x9f\x94\x45\
+\x43\x2a\x82\xb2\x94\x7b\x43\xb4\x8e\xe1\x19\x10\xa5\xe8\x12\xc3\
+\xb7\x1b\x1b\xdd\x5f\xf1\x39\xe0\xd9\x39\x25\x7b\x00\xb8\x98\x98\
+\x5f\x00\x81\x79\xe7\xca\x5d\x22\x09\x41\x86\x63\x82\xb1\x45\x18\
+\x9b\x69\xd1\x22\x3a\xa6\x90\xac\x69\xed\xd5\x46\xe3\x71\x91\xf1\
+\x5e\xa8\x1d\xbf\x00\xfc\x3b\x6b\x92\x03\xc0\xc5\xc4\x7c\xf2\xfb\
+\xc7\xb0\x76\x18\x3d\xe4\x34\x49\x01\xa0\x87\xf9\x97\x11\x89\x01\
+\xd0\xc3\xfc\xcb\x8c\x2c\x01\x40\x15\x3b\x14\x88\xe8\x61\xfe\x65\
+\x44\x26\x00\x90\xd1\x43\x3e\xb4\x4d\xa0\xa4\x87\xf9\x97\x36\x99\
+\x00\x70\x17\x6b\xaf\x89\xff\xd8\xc3\xfc\x4b\x9f\x4c\x00\xb0\x59\
+\xb6\xb5\x87\xf9\x97\x07\x11\x00\x72\x21\x04\x2a\xac\xe8\xba\x5b\
+\xff\x0f\x29\x69\x19\x3d\xcc\xbf\xc4\x89\x00\x70\x0f\x84\x89\x08\
+\x33\xc5\x25\xa7\xe3\xa6\xdb\xef\xeb\x61\xfe\x65\x40\x04\x80\xc7\
+\x21\xda\xc6\x6d\xf4\xa4\xab\x30\x75\xfa\xec\x1e\xe6\x5f\x06\x44\
+\x00\xa0\xbd\x44\x1e\xb6\x3c\x39\x71\xda\xef\x30\x7d\xd6\x0d\x3d\
+\xcc\xbf\x0c\x88\x00\xf0\x10\x84\xa2\x42\x33\x0d\x19\x31\x16\xb7\
+\xff\xe5\x81\x1e\xe6\x5f\x06\x44\x00\x98\x0c\x21\x21\xc1\x4c\x94\
+\x0d\xf3\xda\xdb\xef\x23\x22\x3c\xa2\x87\xf9\x97\x38\x11\x00\x68\
+\xd3\x19\x7a\xe8\x56\xeb\xb2\x0f\x18\x94\x8b\x7f\xbd\xb4\x98\xd7\
+\xe8\xf7\x30\xff\xd2\x25\x53\x1c\x80\x96\x9a\xb8\x57\xfc\xc7\xb1\
+\xe3\x27\xe2\xc5\x45\x8b\xa1\x66\xc6\x60\x0f\xf3\x2f\x4d\x32\x01\
+\x80\xf6\x6a\xa5\xe4\x05\x9b\x34\xa9\x49\x53\xa6\xe2\x8d\xa5\x6f\
+\xc2\xdb\xdb\xbb\x87\xf9\x97\x20\x59\x4e\x06\x91\x2d\x40\xe9\x4a\
+\x36\x99\x30\x57\x4c\x9b\x86\xb7\xdf\xfe\x37\x74\xc6\xfa\xf7\x1e\
+\xe6\x5f\x3a\x24\x9e\x0e\x9e\x09\xa1\x18\xc1\x06\x04\x53\xaf\xbc\
+\x12\xff\x7a\xf1\x15\xb4\xb6\xb5\xf7\x30\xff\x12\x22\xa9\x84\x10\
+\x59\x10\x8c\x1b\x3f\x09\x8f\x3c\xf9\x0c\x03\x81\xb6\x87\xf9\x97\
+\x08\xc9\xa5\x84\xc9\x82\x60\xd8\xc8\xd1\xb8\xfb\xfe\xbf\xa3\x95\
+\x16\x6d\xe8\x61\xfe\x45\x4f\xf6\x92\x42\x65\x41\x30\x30\x77\x18\
+\xe6\xff\xe9\x5e\xb4\x31\x66\xf7\x30\xff\xe2\xa6\xae\xd2\xc2\x65\
+\x41\x90\x9e\xd5\x17\xbf\xfb\xfd\xad\x50\xba\x7b\x59\x31\xff\x74\
+\x51\x01\xd6\x7e\xf4\x6e\x0f\xf3\x2f\x12\x72\xa4\x30\x44\x16\x04\
+\xb4\x20\x53\x4a\x66\x5f\xf8\x07\x6a\x98\x87\xa0\x43\xc9\xc9\x13\
+\x28\xca\x3f\x2a\xf7\x3d\x3d\xcc\xef\x86\xe4\x68\x69\x98\x2c\x08\
+\x1c\xa4\x1e\xe6\x77\x53\x72\xa6\x38\xf4\x4c\x41\x40\xc5\x12\x13\
+\xd0\xc3\xfc\x6e\x49\xce\x96\x87\x53\xb0\x88\x0a\x1a\x1d\x2d\xac\
+\xa4\x4c\x23\xaa\xd7\x3b\x6f\x0b\x1c\xf4\xd0\xd9\xd1\x99\x2c\x10\
+\x41\x61\x63\xaa\xb9\xa7\x2a\x5b\x5f\x99\x6b\x0a\x20\x4c\x31\xbf\
+\x8d\xb3\x58\xc3\xae\x87\xce\x3f\x9d\xcd\x1a\x41\x34\x8b\x48\x05\
+\x95\xb4\x2e\xbd\xa9\x66\x8e\xd6\xb3\xa1\x51\x7f\xce\x56\xb1\xea\
+\xa1\xf3\x4b\xff\x0f\x3d\x8e\x98\x4e\x1a\xa0\x2a\xaa\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x01\x7c\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe1\x0a\x04\x0c\x36\x1b\xde\x7d\x4c\x8c\x00\x00\x01\x09\x49\x44\
+\x41\x54\x38\xcb\xa5\x53\x31\x4e\x03\x41\x0c\x1c\xdf\xdc\x49\x74\
+\xa9\x69\xf8\x00\x0f\xa0\x48\x41\x8b\x44\x43\xc1\x1f\x78\x06\xe2\
+\x3f\x08\xd1\x44\x20\x45\x42\x4a\x91\x22\x4d\x3a\x2a\xd2\xe7\x0b\
+\x34\x63\xd3\xec\x22\xb3\x4a\x56\x20\x56\x5a\xcd\x8c\x6d\xd9\x6b\
+\xfb\xce\x22\x02\xff\x39\x66\x66\x90\xf4\x00\xe0\x3c\x3b\x48\xde\
+\x4a\x7a\x6c\x79\x3a\xef\x24\xef\x61\x66\x70\xf7\x27\x33\x43\xbd\
+\x55\x67\xfb\xb1\x98\xa1\x64\x1b\x00\x40\xd2\x73\xd6\x00\x86\xd6\
+\xd6\xea\x1f\x09\x48\xde\x1c\xd0\xdd\x98\xb1\xb6\x2c\x69\xd1\x0e\
+\x48\xd2\x82\xe4\x75\x2f\xc6\xcc\xec\xe8\x84\x25\xbd\x96\xaa\x57\
+\xbd\x35\xc0\xdd\x97\xee\xbe\xcc\x3c\xeb\x1e\xd6\x04\x6f\x15\x7b\
+\x3c\xeb\xba\x0d\xe4\xd5\xb4\xd7\xdd\x57\xee\xbe\x3a\xa4\x2b\x8e\
+\xa5\xd7\x75\xdb\x1a\xc9\x39\xc9\xcb\xea\x93\xb4\x26\x39\x4f\xb1\
+\x63\x9e\xc1\xa6\xa9\xbc\xf9\x0d\xe6\x0f\x69\x2a\x55\xb6\x59\x03\
+\x98\x8a\x6d\x92\xb4\x25\x79\x91\xf1\xfb\xb9\x11\xf1\x91\x9f\x9f\
+\x75\xcb\xdb\xd8\x31\x22\xce\x00\xec\x22\x62\xdf\xce\x21\x22\xf6\
+\x66\x76\x5a\x7d\x99\x47\xc4\x0b\x80\x3b\x8b\x88\x19\x80\x13\x00\
+\xfc\xe3\x9f\x2c\x00\x9f\x5f\x24\x17\xfd\xbb\x8c\x4b\x89\x4f\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x5c\xfd\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x96\x00\x00\x00\x96\x08\x06\x00\x00\x00\x3c\x01\x71\xe2\
+\x00\x00\x5c\xc4\x49\x44\x41\x54\x78\xda\xed\x9d\x05\xb8\x55\xc5\
+\xfa\xc6\xf7\xe9\x00\xc1\x8e\x6b\x77\xa0\xd7\xba\x76\xb7\xd7\x6b\
+\x5c\x3b\xb0\x11\x14\x50\x50\xba\xbb\x41\x4a\x44\xa4\x91\x12\x03\
+\x45\x04\x0c\x44\xc5\xc2\xf6\x2a\x21\x28\x18\x18\x58\x08\x1c\x38\
+\x84\xc0\xfc\xdf\xdf\xac\x99\x7d\x66\x6f\xf6\x09\xe0\xe0\x1f\x63\
+\x3f\xcf\x7b\xd6\x3e\x7b\xaf\xbd\x62\xe6\x5d\x5f\xcd\x37\xdf\xc4\
+\x62\xe5\xf8\xca\xcc\xcc\x8c\xed\xb4\xd3\x4e\xb1\xca\x95\x2b\xc7\
+\x76\xd8\x61\x87\xd8\xb6\xdb\x6e\x1b\xdb\x6e\xbb\xed\x62\x79\x79\
+\x79\xf6\xff\x9d\x77\xde\x39\xb6\xcd\x36\xdb\xc4\x2a\x55\xaa\x14\
+\xdb\x75\xd7\x5d\x63\x7b\xec\xb1\x87\xdd\x9f\xcf\x77\xdb\x6d\xb7\
+\xf8\x67\xfc\xbf\xcb\x2e\xbb\xd8\xcf\x2a\x56\xac\x68\x91\x9f\x9f\
+\x6f\x8f\xfb\x8f\x7f\xfc\xc3\x82\xdf\x71\x2c\xf6\xe7\x7f\xf6\x65\
+\xbb\xfb\xee\xbb\x5b\xf0\x39\xe7\xdf\x7e\xfb\xed\xe3\x9f\x01\xf6\
+\xe1\x3b\xff\x9e\xed\x81\x07\x1e\xc8\xfb\x34\xa1\xb2\x8e\xb3\xab\
+\xb6\xfb\x0a\x55\x84\xa3\x85\x63\x75\x5d\xc7\x0b\x67\x08\x27\xeb\
+\xff\xe3\x84\x63\x84\x7f\x0a\x07\x6a\xff\xdd\xb5\xdd\x41\xdb\x9c\
+\x7d\xf6\xd9\x27\xb6\xdf\x7e\xfb\xc5\xaf\x65\xcf\x3d\xf7\x4c\xb8\
+\x26\x7f\xed\x15\x2a\x54\x88\xbf\x0f\xbf\xe3\x77\xbe\x4d\xb8\x37\
+\x3e\xe7\x33\xda\x83\xfb\xa7\x1d\xf8\xed\x8e\x3b\xee\x68\xdb\x8a\
+\xfd\xfc\x3e\xfc\x9e\xfd\x7c\x5b\xf2\x39\xfb\xd1\xf6\x7c\xc6\xbe\
+\xfc\x9e\xb6\xf7\xed\xe2\xb7\x9c\xab\x3c\x5f\xc6\x98\x72\x3d\xde\
+\x1f\x8d\x58\xd9\x7a\xbf\x97\xb6\x47\x89\x0c\xe7\xe9\x7d\x75\xa1\
+\xad\x8e\xd3\x4f\xdb\xb1\xc2\x8b\xc2\x0c\xe1\x7d\xe1\x63\x61\xb6\
+\xbe\x9b\xa9\xed\x47\xc2\x3b\xc2\x74\xe1\x69\x7d\x36\x58\xdb\xae\
+\xda\xd6\xdb\x7b\xef\xbd\x2f\x17\x4e\xd0\xfb\x83\xf4\xd9\x76\x7f\
+\x13\xeb\x4f\x4a\x2c\xf6\xe7\xfc\xfa\x3f\x77\xf7\xe8\x75\xb1\xf6\
+\x69\xa3\xed\xa3\xda\xbe\x25\x7c\x27\xac\xd3\x6f\x8d\xb6\xe5\x89\
+\x25\xc2\x2c\x9d\x67\xb2\xb6\x7d\xb5\xbd\x43\x38\x02\x89\xc8\x35\
+\x72\x2f\x7f\x13\xeb\x0f\x4a\x2c\x61\x37\xed\x73\xbd\x8e\xd5\x5b\
+\xef\xdf\xd0\x77\x05\xe5\x4c\x9e\x8d\xc5\x7a\x61\x9e\x30\x5a\x9d\
+\x79\xaf\x53\xa7\x69\x7f\x13\xeb\x8f\x41\xac\x2a\x6a\xd0\xda\xda\
+\x3e\x27\xfc\xa0\xf7\x06\x94\xd4\xe1\x48\x2b\x9d\xcb\xe8\x78\x46\
+\x1d\x61\x74\x9d\x46\x8d\x6d\xa1\x6b\x8f\x43\xf7\x92\xf0\x3f\x60\
+\x1f\xf6\x07\xfc\x5e\xd7\x6d\x74\x0f\xa5\x92\xcc\x5d\x13\x44\xff\
+\x58\xef\xbb\x0b\x67\xeb\x7d\xfe\xdf\xc4\xda\x4a\x88\xc5\xe7\xda\
+\x1f\xe3\xb9\x96\xf6\x7d\x49\x58\x0a\x51\x8a\x03\x9d\x0e\x89\x20\
+\x82\x8e\x65\xf4\x7b\x4b\x26\xd9\x45\xe6\xd0\x43\x0f\x35\xc7\x1c\
+\x73\x8c\x39\xe5\x94\x53\xcc\x45\x17\x5d\x64\x6e\xb8\xe1\x06\x53\
+\xbd\x7a\x75\x53\xab\x56\x2d\x53\xb7\x6e\x5d\xd3\xa0\x41\x03\x73\
+\xdf\x7d\xf7\x99\x7b\xee\xb9\xc7\xdc\x75\xd7\x5d\xe6\xd6\x5b\x6f\
+\x35\xff\xfd\xef\x7f\xcd\x59\x67\x9d\x65\x8e\x3b\xee\x38\x73\xc4\
+\x11\x47\x98\xfd\xf7\xdf\xdf\x9e\x47\xf7\x67\x74\x9f\x96\x7c\x10\
+\x8e\xf3\x82\x92\xae\x4d\xf8\x58\xe8\xab\xfb\x39\x51\xbf\xc9\xe2\
+\xde\xfe\x26\xd6\xef\x4c\x2c\x7d\x9e\xae\x7d\xcf\x57\xc3\x0d\xd7\
+\xfb\x9f\x4a\xea\x30\x3a\x96\x8e\x46\xe2\xd0\xb9\xf2\x00\xcd\x39\
+\xe7\x9c\x63\xee\xb8\xe3\x0e\xd3\xb1\x63\x47\x33\x7c\xf8\x70\xf3\
+\xec\xb3\xcf\x9a\x19\x33\x66\x98\xf9\xf3\xe7\x9b\x45\x8b\x16\x99\
+\x1f\x7f\xfc\xd1\xfc\xfc\xf3\xcf\xe6\x97\x5f\x7e\x31\x8b\x17\x2f\
+\xb6\xf8\xf5\xd7\x5f\x2d\x78\xcf\xe7\xe0\xa7\x9f\x7e\xb2\xfb\x2e\
+\x5c\xb8\xd0\xfc\xef\x7f\xff\x33\x53\xa7\x4e\x35\x8f\x3e\xfa\xa8\
+\xe9\xd3\xa7\x8f\xa9\x57\xaf\x9e\xb9\xfc\xf2\xcb\xcd\x91\x47\x1e\
+\x69\x25\x14\x64\xe6\x3a\x90\x72\xa5\x91\x4c\xf7\x3e\x4d\xf7\x76\
+\xb7\xee\x75\x97\xbf\x89\xb5\x85\x89\xc5\x31\x74\xdc\x8a\x6a\xc0\
+\xaa\xda\xef\x55\x7d\xb7\xb6\x98\x4e\xb1\x92\x02\x89\x04\x99\xaa\
+\x54\xa9\x62\xae\xbc\xf2\x4a\xd3\xbc\x79\x73\x33\x76\xec\x58\xf3\
+\xd1\x47\x1f\x99\x2f\xbe\xf8\xc2\xfc\xf0\xc3\x0f\xa6\xa0\xa0\xc0\
+\x02\xc2\x40\x14\x48\xf2\xfd\xf7\xdf\x9b\xaf\xbf\xfe\xda\x92\x6c\
+\xee\xdc\xb9\xe6\xd3\x4f\x3f\x35\xb3\x67\xcf\x36\xb3\x66\xcd\xb2\
+\xdb\x39\x73\xe6\xd8\xcf\x3f\xfb\xec\x33\xf3\xe5\x97\x5f\x9a\x6f\
+\xbf\xfd\xd6\x1e\x0b\x92\x41\xba\xa5\x4b\x97\x9a\xe5\xcb\x97\xdb\
+\xf7\x10\x8e\x7d\x5f\x7c\xf1\x45\x73\xff\xfd\xf7\x9b\x6a\xd5\xaa\
+\x99\x53\x4f\x3d\xd5\x5e\xa3\x27\x3b\x12\xb3\x04\xa2\x7d\x2b\x74\
+\x52\x7b\x1c\xca\xfd\x43\xaa\xbf\x89\x55\x4e\xc4\xa2\x21\xf5\x9b\
+\xed\x75\x9c\xba\xfa\x6c\x8e\x57\x2b\x21\xbc\x7a\x43\xb5\x61\xe7\
+\x9c\x74\xd2\x49\xe6\xee\xbb\xef\xb6\x44\xfa\xe0\x83\x0f\xac\x14\
+\x5a\xb6\x6c\x59\x9c\x40\x90\xe1\xf3\xcf\x3f\x37\x9f\x7c\xf2\x89\
+\x79\xf3\xcd\x37\xcd\x0b\x2f\xbc\x60\xc6\x8f\x1f\x6f\x46\x8e\x1c\
+\x69\x06\x0d\x1a\x64\x06\x0c\x18\x60\xfa\xf5\xeb\x67\xfa\xf6\xed\
+\x6b\xa5\x4f\xaf\x5e\xbd\xe2\xe8\xdd\xbb\xb7\xfd\xec\x81\x07\x1e\
+\x30\xfd\xfb\xf7\x37\x03\x07\x0e\x34\xc3\x86\x0d\x33\xe3\xc6\x8d\
+\x33\x93\x26\x4d\x32\xaf\xbe\xfa\xaa\x3d\x27\x84\xfc\xea\xab\xaf\
+\x2c\x59\x3d\xe1\x38\xbf\x27\x5a\xa7\x4e\x9d\xcc\xc5\x17\x5f\x6c\
+\xd5\x27\xd7\x0d\xc9\x42\xb5\x99\x84\xc5\xfa\x6e\x98\xda\xed\x9f\
+\x48\xae\xbf\x89\xb5\x99\xc4\xd2\xff\x15\xf4\xbb\xda\x7a\x3f\xdb\
+\xdb\x48\x21\x20\x11\x4f\x3e\xa4\xc2\x3e\xc2\x06\x7a\xe2\x89\x27\
+\x2c\x69\xe8\xcc\x25\x4b\x96\x58\x95\x46\x07\xcf\x9c\x39\xd3\x76\
+\xfa\x93\x4f\x3e\x69\x86\x0e\x1d\x6a\x49\xd3\xb9\x73\x67\xd3\xbe\
+\x7d\xfb\x0d\xd0\xa1\x43\x07\x0b\x54\x65\x49\xf0\xfb\xa5\xfa\x3d\
+\x12\xea\xe1\x87\x1f\x36\x63\xc6\x8c\x31\xcf\x3d\xf7\x9c\x79\xff\
+\xfd\xf7\xad\x14\x44\x22\x7a\xb5\x0a\xe1\xa7\x4f\x9f\x6e\x7a\xf4\
+\xe8\x61\x2e\xbc\xf0\x42\xa3\x7b\xb7\xb6\x19\xf7\x93\x7c\xaf\x4e\
+\x82\xad\xd0\x77\x03\x75\xdf\x87\xff\x4d\xac\x4d\x27\xd6\x65\xc2\
+\x07\x5e\x22\x85\xc0\x4e\xa1\x03\xb0\x5d\xae\xb8\xe2\x0a\xdb\x81\
+\xa8\x28\xd4\x11\x1d\x46\xe7\xcd\x9b\x37\xcf\xbc\xf1\xc6\x1b\xe6\
+\xa9\xa7\x9e\xb2\x12\xa8\x4b\x97\x2e\xb6\xc3\xdb\xb6\x6d\x6b\x01\
+\x01\x20\x07\x92\xa3\xbc\xe1\x49\xd7\xae\x5d\xbb\x84\x73\x21\xf1\
+\x46\x8f\x1e\x6d\x25\xd6\xc7\x1f\x7f\x6c\x55\x25\x12\x94\x6b\x46\
+\xfd\x4e\x99\x32\xc5\x3a\x0a\x38\x02\x3c\x30\xa8\x73\x1e\x9e\xf0\
+\xde\x5d\x7b\x2c\xd3\xb6\x9b\xda\x6e\xe7\xbf\x89\x55\x46\x62\xa9\
+\x41\x8e\x51\xc3\x4d\x48\x26\x93\x57\x77\x52\x8d\xe6\x80\x03\x0e\
+\xb0\x9e\xd9\xf3\xcf\x3f\x6f\xbe\xf9\xe6\x1b\x2b\x9d\x78\xfa\xb1\
+\x7b\x5e\x7f\xfd\x75\x2b\x25\x7a\xf6\xec\x69\x3b\xb4\x55\xab\x56\
+\xf1\xce\x0d\xa5\xd1\xef\x0d\xce\x0d\xd1\x5a\xb7\x6e\x6d\xda\xb4\
+\x69\x63\xba\x76\xed\x6a\xd5\x28\xf7\x00\xc9\xb8\x0f\xa4\x2b\xf8\
+\xf0\xc3\x0f\xad\x14\xc3\x1e\x83\x5c\x00\x35\x99\xdc\x1e\x6a\xb7\
+\xf9\x22\x54\x0d\xb5\x5b\xd6\xdf\xc4\x2a\x86\x58\xa8\x3d\x35\x48\
+\x1b\x35\x58\x41\x2a\x42\x61\x87\x1c\x72\xc8\x21\xa6\x49\x93\x26\
+\xd6\x2e\x42\x3a\xd1\x09\x3c\xe9\xa8\x99\xc7\x1f\x7f\xdc\xda\x3f\
+\x74\x1c\x64\xa2\xf3\xe8\xc8\x54\xaa\xae\x44\x40\x02\x20\x09\xd3\
+\x5e\xd2\x27\x0e\xd4\x26\xf0\xff\xf3\xbd\xdf\x37\x85\x3a\x2c\x0d\
+\x90\x9d\x6b\x6d\xd9\xb2\xa5\x55\xc9\xd8\x69\xaf\xbc\xf2\x8a\x7d\
+\x38\x70\x06\x90\x64\xbc\x47\x6d\xe3\xc5\x22\xa5\x21\x58\xaa\x07\
+\x4e\x78\x89\x31\xcd\xbf\x89\xb5\x21\xb1\xce\x11\xde\x45\xec\x87\
+\xc0\x63\x82\x50\x1a\xdc\xb5\xee\xfb\xdb\x6f\xbf\x6d\x8d\x61\x80\
+\xbd\x42\x47\x0c\x1e\x3c\xd8\x12\x88\x0e\xf2\xd2\x89\xff\x4b\x04\
+\xea\x90\xce\x15\x41\xda\x4a\x45\x5a\x88\x28\x6d\x5d\x87\xb7\x53\
+\x87\xb7\x6b\xd6\xcc\xb4\xd7\x39\x3b\xd4\xa9\x63\x3a\xd4\xae\x6d\
+\x3a\xd4\xac\x69\x3a\x28\xa6\xd5\x41\x71\xac\xf6\xf7\xde\x6b\xda\
+\x35\x6a\x64\xda\xe9\x7c\xed\x44\xe0\xb6\xa8\x3c\xd4\xac\x08\xd2\
+\x56\x92\xc8\x6e\xf9\xdf\x9d\xab\xb4\xeb\xe1\x21\xf0\xd7\x8f\xba\
+\x7c\xe6\x99\x67\xac\x4d\xf8\xdd\x77\xdf\xd9\x87\x87\x7b\x45\xba\
+\x9d\x76\xda\x69\xd6\x04\xc0\xd0\x4f\x6e\x2b\x61\xa9\xd0\x5c\xc8\
+\xfd\x9b\x58\x7b\xec\x91\xa3\xfd\xdb\xea\xf3\x35\x61\x23\x21\xf6\
+\x69\x40\xb6\xb7\xdc\x72\x8b\x99\x36\x6d\x9a\x7d\x8a\x51\x77\x18\
+\xe6\xd8\x28\x18\xdf\x74\x44\x8b\x16\x2d\xe2\xea\xa5\x24\xb4\x16\
+\x69\x5a\x8b\x3c\x16\x22\x54\x1b\x85\x1f\xda\x89\x28\xed\x6f\xa8\
+\x6a\xda\x5f\x70\x91\xe9\x70\xfc\x49\xa6\xe3\x81\x87\x9a\x4e\xbb\
+\xec\x6e\x3a\x57\xdc\xd6\x74\xce\xa9\x60\x3a\x67\xe6\x9a\x2e\x19\
+\x39\xa6\x4b\x7a\xb6\xe9\x92\x96\x25\x68\x9b\x9e\x63\x3a\x67\xe4\
+\x9a\xce\x59\xf9\xa6\x73\xde\x36\xa6\xd3\xf6\x3b\x9b\x8e\x7b\xef\
+\x6f\x3a\x1c\x79\x8c\x69\x7f\xe6\xd9\xa6\xfd\x15\x57\x99\xf6\x8a\
+\x91\xb5\xad\x5f\x3f\x3a\xa7\xce\x65\xcf\x29\xa2\xb5\x11\xc9\x4a\
+\xbb\x4e\x08\x46\x68\x04\x29\x46\x4c\x8c\xf8\x18\x6a\x12\x09\x8d\
+\xa7\x89\xad\x78\xf8\xe1\x87\x1b\x79\x87\x56\x92\x27\x13\x4c\x6d\
+\xf6\xa2\x88\x75\xd8\x5f\x99\x58\x87\x49\x84\xbf\x08\x79\x42\x10\
+\x83\x42\x4a\x9d\x79\xe6\x99\xe6\xb1\xc7\x1e\xb3\xa1\x01\x08\xc5\
+\x53\x4b\x58\x00\x75\xd7\x4c\xd2\x04\xf8\xa7\x3c\x25\x50\x89\x74\
+\x94\x3a\xb5\x25\x1d\xdc\xb0\xa1\x69\x7b\xeb\x6d\xa6\xc3\xb9\xe7\
+\x99\x4e\x07\x1e\x62\x3a\x6e\xbb\x93\xe9\x94\x9e\x6b\xee\x57\x3b\
+\xf4\x17\x06\x65\xc7\xcc\xc8\x4a\x31\x33\x7e\xb7\x98\x99\xb2\x5f\
+\xcc\xbc\x7c\x58\xcc\xbc\x79\x54\xcc\xbc\x73\x5c\xcc\xbc\x77\x7c\
+\xcc\x7c\x78\x52\xcc\xbc\x7f\x62\xcc\xbc\xab\xff\x67\x1c\x1b\x33\
+\xd3\x8f\x88\x99\x17\x0e\x8a\x99\x89\x7b\xc5\xcc\xd8\xed\x63\x66\
+\x68\x6e\xcc\x0c\x48\x8f\x99\x3e\x3a\x56\x97\x58\x86\xe9\x98\x57\
+\xd9\x74\xfa\xc7\x5e\x22\xec\x89\xa6\xdd\x35\xd7\x98\x36\x92\x7c\
+\x5c\x57\x4b\x11\xad\xa5\xa4\x54\x2b\xa7\xb2\x8b\x03\x0f\x4c\xd3\
+\xa6\x4d\xad\x7d\xe6\x09\x46\x5b\x20\xad\xdf\x7b\xef\x3d\x3b\x02\
+\x00\x91\xfc\x03\x18\x42\x9f\xff\xa2\xb6\xad\x46\x5f\xfc\x65\x88\
+\xc5\x8d\x09\x57\xe8\xfd\x77\xc9\x0d\x02\xa1\x18\x5e\x41\x02\xf1\
+\x74\x22\xa5\x08\x42\xbe\xf6\xda\x6b\x56\x42\x61\x5f\x01\x1a\xbd\
+\x58\xa8\x53\x9a\x8b\x48\x2d\xe8\x38\x85\x1f\xda\x5d\x72\xa9\xe9\
+\x70\xe8\xe1\xa6\xe3\x36\xdb\x9b\xee\xba\xe7\xbe\xc2\x88\x0a\x31\
+\x33\x79\x9f\x98\x79\xeb\x98\x98\x99\x7d\x56\xcc\x7c\x7d\x69\xcc\
+\xfc\x72\x5d\xcc\x2c\xbf\x25\x66\x56\xdc\x26\xdc\xee\x50\x2d\x66\
+\x0a\xef\x48\x81\x6a\xd1\x77\x76\x9f\xdb\x22\x2c\xbd\x31\x66\xbe\
+\xbf\x32\x66\x3e\xbb\x20\x66\x3e\x10\x09\x5f\x3e\x34\x66\xc6\xed\
+\x18\x33\x0f\x89\x6c\x3d\x75\xce\xce\x92\x7c\x1d\xf6\xda\xcf\x74\
+\x38\xed\x74\xd3\x46\x81\xd2\x96\x92\x4a\x2d\x44\xb0\x16\x22\x7f\
+\x49\xf7\xc3\x03\xd4\xb8\x71\x63\x6b\x97\x11\x6b\xa3\x5d\xf0\x7a\
+\x51\x93\xd8\x95\xa7\x9f\x7e\xba\x6d\x37\x6c\xb0\xe4\xf6\x14\xfa\
+\x4b\xb2\x55\xfc\xd3\x13\x4b\xdf\xa5\x49\x3c\xb7\x10\xd6\x25\x4b\
+\x29\xed\x63\xce\x3b\xef\x3c\x1b\xf3\xa1\xd1\x10\xff\x04\x1a\x87\
+\x0c\x19\x62\x55\x43\x23\xd9\x34\x6c\x8b\x43\x33\x11\xa9\x99\x54\
+\x4d\x73\x49\xa6\x36\x1a\x4a\x69\x7f\x48\x15\xa9\xab\x3c\x4b\x26\
+\xa4\xc9\xc4\xbd\x25\x71\xd4\xe1\xdf\xfe\x37\x66\x7e\xbd\x21\x66\
+\x0a\x44\xa2\x95\x90\xa4\xba\x08\xa5\x6d\x81\xb0\x0c\xe8\xff\x65\
+\x35\x84\x3b\xd3\xe2\x58\x5e\x33\xcd\x14\xd6\x4a\x33\x2b\x84\x82\
+\xbb\xd2\x12\xbe\xb3\xfb\x56\x8f\xb0\x5c\x58\x21\xac\xac\x11\x91\
+\x6f\xe9\x4d\x31\xf3\xd3\x35\x31\xf3\xe9\xb9\x31\x33\xad\x4a\x24\
+\xd9\xfa\x7a\x89\x26\x95\xdb\xee\xcc\xb3\x4c\xab\x3b\xef\x8c\xae\
+\x5d\x04\x6b\x86\x1a\x14\x91\x52\xdd\x1f\xd2\x8b\x36\xc0\x5b\x64\
+\xe8\x68\xc1\x82\x05\x96\x60\x8c\x06\xf0\xb0\xf9\x07\x33\x6c\x57\
+\x6c\x54\xe1\x65\xbd\xdf\xff\x4f\x4b\x2c\xdd\x60\x45\x61\x98\xbb\
+\xd9\x38\x7c\xb4\xbc\xa1\x08\x41\x2c\x0a\x52\xe1\x0d\x4d\x9c\x38\
+\xd1\x1a\xd2\xf5\x65\xab\xd0\x70\x34\xec\x06\x50\x27\x34\x91\x84\
+\x02\x2d\xb0\x6b\x4e\x3a\x45\x92\x69\xbb\x88\x4c\x79\x31\xf3\xfc\
+\x81\x31\x33\xef\xbc\x98\xf9\x59\x9d\x5b\x78\x5b\x44\xa4\xe5\xd5\
+\x22\x12\x15\x54\x77\xe4\x11\x69\x0a\x20\x4c\x6d\xe1\x6e\xe1\x1e\
+\xa1\x8e\x43\x5d\x11\xea\x3e\xed\x53\x27\xdd\x2c\xba\x33\xc3\xfc\
+\x5c\x3b\xdd\x14\xd6\x8b\x3e\xb7\xf0\xfb\xdd\xe3\x7e\x5b\x3b\x3a\
+\x96\x3d\xa6\x8e\x5d\x50\x23\x3a\x0f\xe4\x5d\x25\x2c\x11\xd1\x16\
+\x4a\x3a\xce\xf8\x97\x48\xb6\x43\x44\xb2\x4e\xb2\xd7\x3a\x1c\x70\
+\xb0\x69\x7d\xf5\xd5\xa6\x99\x88\xd3\x44\x04\x6b\xe2\x88\x94\x0a\
+\xb4\x13\x04\x63\x84\x80\xe1\x29\x62\x61\xb4\x19\x21\x16\x82\xc4\
+\xd8\x5e\x48\xaf\xa4\x76\x5e\xa0\x7e\x39\xf5\x4f\x47\x2c\xdd\xec\
+\x9e\x3c\x39\xc9\xa4\xa2\x11\x08\x06\x32\xf4\x82\xfd\x40\xe8\xe0\
+\x9d\x77\xde\xb1\xc3\x25\x64\x13\xd0\x88\x5e\xfd\x25\xa3\xb1\x9e\
+\xee\xc6\xd8\x59\x37\xde\x68\x3a\x1c\x72\xa8\xe9\x1c\xcb\x32\x0f\
+\xe8\x9e\x9e\xde\x23\x66\x66\x9d\x11\x33\x8b\xaf\x17\x91\x50\x57\
+\x77\x44\x92\x64\x59\xf5\x48\xc2\x14\xd4\x8c\x08\xb0\x5c\x64\x58\
+\x71\x6f\x9a\x59\x59\x2f\xdd\xac\xac\x9f\x6e\x56\x08\xcb\x41\x03\
+\xa1\x61\x84\xc2\x26\xe9\xe6\xc7\x3a\x99\x66\xd0\x3f\x65\xa4\xef\
+\x59\xd9\x74\xdd\xa7\xb2\x79\xf7\xea\x1c\xb3\xba\x99\x7e\xdf\xa8\
+\x68\x3f\xfb\x1b\x7e\xab\x63\x2d\xbf\x4f\xb8\x37\x3d\x22\xde\x3d\
+\x8e\x6c\x35\xdd\xb9\x6b\xe8\x9c\x22\xdb\x4a\xa4\xdb\xad\x31\xf3\
+\xe5\xc5\x31\xf3\xea\xe1\x31\x33\x44\x12\x95\x87\xa1\xe3\x8e\xbb\
+\x9a\x36\x8a\xc0\x37\x95\x0a\x6f\xcc\x03\x23\x22\xa5\xbc\x77\xa9\
+\x46\xbc\x64\x1e\x3c\x06\xd0\x7d\x44\x9f\xb0\xcb\x4d\x37\xdd\x64\
+\x1f\x56\xb4\x40\xd8\xd6\xea\x97\x5f\x45\xa0\xeb\xe8\x9b\x3f\x05\
+\xb1\x84\x03\xf5\xfe\x63\x9f\xb7\x04\xbc\xea\xbb\xe0\x82\x0b\x6c\
+\x40\x93\x27\x8e\xc6\x99\x3c\x79\xb2\x35\x5c\x19\x9a\xe1\xa9\xa4\
+\x01\x93\xd1\x08\xb5\x28\x42\xb5\x10\xa1\xda\xeb\x49\xef\xa2\xfb\
+\x18\x90\x19\x33\x2f\x1d\x1a\xd9\x4a\xd8\x49\x85\x5e\xb5\xa9\x03\
+\x51\x5d\x56\x22\x21\x51\x24\x79\x0a\x24\x81\x56\xd4\xd7\x7b\x11\
+\xe1\xdb\xbb\x33\xcc\x82\x9a\x99\xe6\xcb\xda\x99\xe6\x97\xfa\x19\
+\x66\x65\x8b\x0c\xb3\xa2\x99\xd0\x5c\xd0\xfb\xb5\x6d\xd3\xcd\x27\
+\xb7\xe5\x98\x86\x15\xb6\x35\xf7\xcb\x1b\x6c\x96\x51\xd1\x0c\x3b\
+\x7e\x1b\xb3\xb6\x5d\xba\xfd\xde\xa2\xb9\xfb\x4d\x33\x7d\x26\x22\
+\xae\x68\x0c\x32\x8a\x08\x27\x09\xb7\xfc\x3e\x77\xee\xbb\x9d\x44\
+\x43\x9d\xa2\x36\x6b\x44\xd7\xfa\x8b\x1e\x82\xf7\x4e\x8a\x54\x65\
+\x0f\x08\xb6\xed\x8e\xa6\xf5\xf9\x17\x98\xc6\x22\x4f\x43\xd9\x59\
+\x8d\x20\x53\x8a\xf6\xe0\xe1\x43\xa2\x23\xbd\x7c\x24\x1f\x15\x49\
+\x1b\x7a\x6d\x10\xb6\xbb\xb0\x96\x21\x32\xfa\xe7\x0f\x4d\x2c\x11\
+\xe8\x70\x5d\xf8\x9c\xf0\xe6\x08\xf2\x11\x3d\xbf\x53\xb6\x05\xb1\
+\x1a\x1a\x83\xcc\x01\x02\x81\x34\x12\x4f\x22\x92\x2a\x19\x0d\x68\
+\x48\x11\xaa\xa9\x54\x5e\xbb\xc3\x0e\x97\x84\xca\x30\x0f\xcb\x93\
+\x9b\x7e\x64\xcc\x2c\xba\x32\x52\x75\xa8\x39\x6b\x27\xdd\xe9\xc8\
+\x74\x4f\xa4\xb2\xac\x14\xa9\x5f\x24\x85\x16\x37\xc8\x30\x1f\xdd\
+\x9e\x65\x5e\xbf\x31\xcb\xbc\x76\x83\xb6\x55\xb3\xcc\x8c\x9b\xb2\
+\xcc\x97\x75\x33\xcd\xca\x76\x19\xa6\xb0\x5d\xa6\xc5\xda\x2e\x19\
+\xe6\x93\xea\xb9\xa6\xd9\x36\x95\xcd\x30\xab\xb6\xf2\xcc\xf0\x93\
+\x2a\x99\x75\x5d\xb5\x4f\xfb\x68\x9f\xc2\xb6\x40\xff\xb7\x11\x5a\
+\x0b\xad\x44\xb2\x96\x01\xe1\x9a\x44\x84\x5b\x09\x24\xe5\x56\xea\
+\x1a\x56\xdc\xe7\xae\xad\x96\x93\x64\x4e\x5d\x62\x93\xcd\x92\x33\
+\xf1\xe8\x8e\x4e\x82\x6d\xbf\x93\x69\x79\xe9\xa5\xf1\x7b\x4f\xd9\
+\x2e\x22\x17\xc3\x40\x0c\x25\xe1\xe4\xe0\xec\x60\x9f\xd2\x9e\xca\
+\xe5\x8f\x87\x25\x92\x1e\xec\xfa\x7f\x58\x62\xe9\x82\xff\x25\x2c\
+\xf4\xd9\x98\xc0\xa7\x8a\xe0\xf5\xf1\x64\x31\x38\x4c\xd0\xb3\x5b\
+\xb7\x6e\xa6\x8e\xdc\x71\x48\x05\xb9\x92\x51\x4f\x2a\xa1\x81\xdc\
+\xeb\x36\x27\x9d\x6c\x6d\x92\x07\xd3\xa4\x42\xe4\xee\xff\x70\x55\
+\xa4\xee\x96\xdf\xe1\x54\xdd\x5d\x4e\xf5\x84\x64\x6a\xe4\x24\x49\
+\x53\x75\xba\x24\x4c\x81\xf0\x61\xb5\x88\x50\x6f\xde\x58\x84\x37\
+\xaa\x46\x04\xfb\xae\x49\x96\x59\xd3\x2d\xcb\xac\xec\x9a\x65\xd6\
+\xf7\xca\x34\xb3\x6a\xe5\x59\x62\x0d\x89\xa5\x99\x8e\x22\xd6\x88\
+\x53\x2a\x19\xd3\x3b\xd3\xac\xea\x1a\xed\xb3\xb2\x8b\xd0\x59\x84\
+\xec\xe4\xd0\x41\xf0\xa4\x6b\xa3\xfd\x1c\xe1\x16\xdd\x93\x61\xe6\
+\xde\x94\x61\xe6\xdf\x92\x6e\x7e\xae\x13\x91\x8c\x6b\xb4\x2a\xb3\
+\xb6\xbb\x76\xa9\x4a\x0c\xff\x65\x92\xba\x33\xcf\x8c\x99\xd1\xdb\
+\xc5\x4c\x37\xf5\x53\xfb\xbd\xf7\x35\x4d\x95\x78\x48\x3b\xd4\x17\
+\x99\x52\xb5\x11\xe4\x42\x8a\x3d\xfd\xf4\xd3\x36\xd6\xc7\x03\x4b\
+\xb6\xc5\xd1\x47\x1f\x6d\xb5\x43\xd8\x0f\x0e\xad\xff\x88\xc4\x3a\
+\x22\x99\x54\xc4\x5b\x30\xd2\x09\x1b\x60\x4b\x41\xac\x97\x5e\x7a\
+\xc9\xba\xd3\xa4\xb8\xa0\xfe\x36\x80\x9e\xc6\x7b\xd5\x90\x4d\x35\
+\xd0\xdc\x41\x4f\x6f\x2f\x5d\xf3\xb3\xfb\x44\x06\x70\xe1\xed\x8e\
+\x50\x35\x5c\xa7\xa0\x66\x64\x33\x59\x3b\xa7\x61\xa4\x8e\x20\x13\
+\x92\x03\x29\x52\xd8\x3a\xd3\xac\x56\x87\x7f\xdb\x30\x22\x50\x48\
+\x2a\x0f\xc8\xf6\xbf\x3b\x45\x94\xfb\xb3\xcd\xea\xde\xd9\xc6\x3c\
+\x98\x65\x66\xd5\x4d\x22\xd6\xe9\x22\x56\xff\x2c\xb3\xba\x57\x76\
+\x84\x9e\xd9\x66\x55\xcf\x2c\xb3\xaa\x87\xd0\x5d\xe8\x16\x01\xc2\
+\xad\x16\x96\x89\x5c\x6f\x5e\x92\x61\x1e\x3b\x22\xc3\x8c\xad\x12\
+\xe1\xa9\x63\x33\xcc\xec\x1b\x74\x4d\x4d\x9d\xca\xd4\x35\x73\xed\
+\xdc\x43\x48\xb0\x25\x0a\x61\xbc\xad\x18\xda\xc0\x1c\x85\x2b\x62\
+\x99\xa6\xf5\x09\x27\x9a\x7a\x7a\xc0\xee\x95\x5a\x4c\x6e\x2b\x1e\
+\x4a\xc8\x45\x7c\x8b\xe1\x21\x1c\x21\xc8\x85\xa9\x41\x48\x22\x37\
+\x37\x77\x03\x72\x89\x58\xcd\xff\x10\xc4\xca\xc8\xc8\x80\x58\xfb\
+\x4b\xd4\x7e\x1a\xde\x00\xaa\x8f\xb4\x0f\xb2\x10\x20\x15\x5e\x1f\
+\xc3\x16\x88\x71\x48\x75\xaf\x86\x48\x92\x51\x47\x84\xba\x4f\x43\
+\x29\xad\x8f\x3c\x4a\x8d\x9a\x66\x86\x57\x8c\x62\x4e\xd8\x50\x71\
+\x95\xe7\x09\x55\xb7\x88\x50\xcb\x91\x4e\xcd\x22\x75\x64\x55\x13\
+\xaa\x0a\x29\xd2\x31\x53\x92\x28\xd3\xcc\xbf\x57\xc4\xba\x21\x35\
+\xb1\x90\x5a\x33\x6e\xce\x32\x05\x3d\xb2\xcd\x9a\xfe\x22\xd6\x40\
+\x11\xab\x5e\x12\xb1\xce\x10\xb1\x06\x89\x34\xfd\xb2\x23\x3c\xe0\
+\xd0\x57\xe8\x23\x92\xf5\x16\xb1\x7a\xe9\xfb\x9e\x11\xde\xbc\x34\
+\xc3\x8c\x3a\x24\xc3\x8c\x13\xb1\x1e\xfb\x67\x84\x47\x0f\x17\xf4\
+\xff\x67\xb7\x66\x98\x55\x22\x3d\xd7\x1c\x27\x58\x28\xc1\xaa\x47\
+\x86\x3e\xf1\xb1\x49\xfb\xc7\x6c\x30\xb7\xfd\x4e\xbb\x9a\xc6\xd7\
+\x5e\x6b\xdb\xa7\xae\xc8\x94\xdc\x6e\x90\xab\xb6\xda\xed\xc1\x07\
+\x1f\xb4\x41\x55\xb4\x02\xd9\xb1\x8c\x37\xe6\xe4\xe4\xc4\x73\xf5\
+\x7d\x4e\xbf\x6c\xb1\x7b\xb6\x5a\x62\x21\xa9\x40\x76\x76\xf6\xee\
+\xba\xe0\x4f\xc2\x09\x07\xe8\x78\xf2\x8c\x46\x8c\x18\x61\x49\x45\
+\x3a\x0b\xae\x31\x84\xa2\x01\x50\x81\x09\x50\xc3\xdc\x8d\x47\xa8\
+\xc6\x6b\xbf\x43\x24\xa5\xa6\x2a\x0a\xfe\xcb\xb5\x91\xda\x5b\x56\
+\xcd\xd9\x50\xb5\x13\x09\xb5\xa2\x89\x93\x4e\x10\xaa\x4d\xa4\x8e\
+\x20\x13\x6a\xca\x4a\x11\x49\x93\xdf\xfa\x64\x99\x2f\x1a\x66\x17\
+\x4b\x2c\x4b\x2e\x7d\xf7\x4b\xd7\x6c\xb3\x76\xa0\x88\x35\x3c\xcb\
+\xcc\x6e\x18\x10\x2b\x5d\xc4\x3a\xab\x92\xfd\x7c\xcd\xc3\x22\xdf\
+\x00\x87\x87\x44\x2a\x20\x32\xae\x7e\x30\x22\xdc\x5a\xfd\xff\x6d\
+\xf3\x6c\x4b\x22\x4f\xa8\x10\x63\x0f\xcb\x30\xcf\x9d\x96\x69\x0a\
+\x5a\xea\x1a\x5b\x3b\x9b\xac\x49\xc6\x86\x04\xd3\xbd\x12\x1b\x23\
+\x18\x3b\x4b\xea\x71\x48\x7e\x24\xbd\x5a\xc8\x2c\xa0\xad\xee\x91\
+\xb4\xda\xa0\x0d\x05\xb2\x3f\x18\xfe\x21\x4a\x8f\xdd\xc5\xf6\x3f\
+\xff\xf9\x8f\x25\x57\x38\x31\x44\xfd\xb5\x4e\x46\xfe\x2d\xbe\x0f\
+\xd3\xd3\xd3\xb7\x0e\x62\xa5\xa5\xa5\x59\x43\x5d\x17\x58\x49\x78\
+\x29\x24\x15\x5e\x09\x92\x8a\xbc\x72\x48\x45\xd4\x18\x82\x71\xd3\
+\x4c\x52\x40\x6c\x43\xb0\x38\x20\x95\xd0\x54\xc3\x39\x9d\x35\x3e\
+\x47\x23\xce\x3e\x3b\x30\xcc\x6b\x38\xa3\xbc\x4e\x0a\x42\xb5\x8a\
+\xa4\x93\x25\x54\xa7\xc8\x06\xb2\xaa\x09\xc9\xd1\x3b\x92\x26\xbf\
+\xa9\xd3\x17\xb5\xcd\xb6\x92\xa9\x38\x62\xbd\x7e\x7d\x96\xf9\xa6\
+\x8d\x88\x31\x3c\xc7\x98\x51\x39\x66\x4e\xd3\xfc\x04\x62\x3d\x72\
+\x8e\x88\x35\x5a\xc7\x1a\x92\x63\xd6\x78\x0c\x16\x06\x39\x88\x90\
+\x90\x6e\xfd\xd0\x6c\xf3\xce\x75\x99\x66\xcc\xa1\xa9\x89\xe5\xf1\
+\x4b\x33\xa9\xe8\x4e\xce\x09\x68\x95\x82\x60\xba\xd7\x65\xb5\x9c\
+\x7a\xbc\x53\x76\xa5\x62\x72\x13\xf7\x89\xbc\xc7\xb6\xfb\xee\x67\
+\xee\x55\x14\xbf\xb6\xc8\x95\xd0\x8e\x0e\x35\x6a\xd4\xb0\x21\x09\
+\xec\x58\xd2\xb1\x49\xc7\xc1\x13\xf7\x92\x2b\xc0\x72\xf5\xdd\x85\
+\x98\x31\x52\x99\x5b\x0f\xb1\x74\x51\x69\xba\xb8\x11\xc9\xa4\x22\
+\xfa\x4b\xfa\x2e\x4f\x0c\x3a\x1f\xdd\xef\x49\x95\xd0\x08\x92\x5c\
+\xb5\x20\x95\x32\x08\x5a\x55\x39\xdc\x74\xd5\xb5\x3d\xf1\x8f\xc8\
+\xdb\x8b\x4b\xa9\xd0\x8e\xc2\x28\x6f\xec\xbc\xae\x56\xce\x23\x73\
+\x84\x5a\xd9\x35\x33\xb2\x77\x7a\x45\x6a\xc9\xaa\xa8\x7e\x91\x24\
+\xf9\x4d\x1d\xbe\x54\x9f\x17\x47\x2a\x6b\x67\x5d\x97\x65\xe6\x35\
+\x12\x31\xc6\x88\x58\xe3\x44\xac\x16\x29\x88\xf5\x98\x8e\xf5\x48\
+\x4e\x84\x11\xc2\xf0\x08\x6b\x86\x09\x43\x73\x2c\x29\x57\xf4\xcf\
+\x31\x93\x4e\x8b\x24\x53\x71\xa4\x7a\xe2\xa8\x0c\xf3\x6b\x5b\x5d\
+\x63\x8f\xe8\xda\xad\xa7\xd9\x26\xba\x27\xee\x8d\x7b\xe4\x5e\x43\
+\xfb\xcb\x46\xf7\x6f\x8b\x6c\x2f\xc6\x25\x3b\x6c\xbb\x83\xa9\xa7\
+\xe0\x6a\x4d\xa9\xc5\xda\xae\x2d\x93\xc9\x85\xb3\x84\x3a\xc4\xae\
+\x85\x64\xd8\x5c\xc9\x92\x4b\xf8\x4e\x6a\xf1\xf0\xad\x8a\x58\xba\
+\xa8\xe6\x5c\xa4\x07\xa4\xc2\x58\x27\x3d\x97\x9b\xf1\xa4\xe2\x26\
+\x6b\x8a\x3c\x10\x2b\x44\x4d\x44\xfa\xed\xb7\x9b\x36\x7b\xee\x65\
+\xc7\xd5\x5e\x56\xd0\x70\x69\xd5\x68\x3c\xce\x7a\x7b\x35\x93\xa4\
+\x54\x53\x67\x43\xb5\x89\xbc\xaf\xb8\x84\xea\xe1\x24\x54\x40\x26\
+\x54\x94\x55\x5b\x03\x23\x29\xb3\xf2\xe1\x1c\xf3\xde\x1d\x59\xc5\
+\x4a\x2d\x88\xf5\xf1\x3d\x59\x66\xdd\xd8\x5c\x63\xc6\xe7\x9a\x39\
+\x6d\x2a\x24\x12\xeb\xbc\xca\xf6\xf3\xb5\x63\x22\xfc\x36\x3a\x27\
+\xc2\x28\x87\x91\x22\xe4\xd8\x1c\xb3\xb0\x75\x76\x89\xa4\xe2\xbb\
+\x17\xcf\x55\xec\x4c\xc6\xff\x9a\xde\xee\xda\xf1\x32\x3b\x15\x79\
+\x94\x36\x6c\xd1\x34\x49\x7a\xd1\x16\x6a\x13\x6c\xaf\xb9\x1a\x55\
+\x18\xa4\xe0\x6a\xa7\xcc\x1c\xd3\xe0\xdf\xff\x36\x77\xc9\xc6\xaa\
+\xc5\x43\x9a\xd4\xbe\x4c\x63\x0b\xc9\x45\x3a\xf4\xb1\xc7\x1e\x6b\
+\x64\xba\x98\xb0\xdf\x24\x20\x3e\x12\xe1\xb6\xfb\x7f\x27\x16\x43\
+\x35\x22\xd1\xe5\xf2\x02\xd7\xfa\x8b\x23\xa4\x80\x7b\x8b\xb7\x87\
+\xdb\x8b\xfa\x7b\xe4\x91\x47\xec\xcd\x41\x2c\x24\x56\x88\x1a\x52\
+\x87\x75\xae\xbb\xce\xb4\xdf\x6e\x47\x3b\xc4\xf1\xc1\xc9\x92\x52\
+\x7a\x22\x0b\x42\x5b\x0a\x29\xd5\x20\x50\x7b\xad\x43\x09\x15\x75\
+\x8a\xf5\xd0\xfa\x38\x43\xba\xbf\xb3\x7d\x20\xd4\xa0\x48\x5d\xfd\
+\x86\x24\x19\x11\x49\x98\x59\xf7\x66\x5b\x95\x97\x52\x15\xca\xc6\
+\x7a\xaf\xba\x3a\x58\xfb\x99\x89\x22\x56\xbb\x0a\x45\x71\x2c\x6c\
+\xac\xf3\x45\xac\x67\x44\xaa\x71\x01\x1e\x0d\x00\x21\x1f\xcb\x35\
+\x33\x74\x9c\x31\x87\x14\x4f\x2c\x54\xe4\x27\x22\xf8\xfa\x41\xee\
+\x21\xe8\x13\x79\x99\xdc\xcb\x4a\x4f\x30\x24\x58\x68\x7f\x35\x70\
+\xd2\xcb\xd9\x5e\xa8\xc6\xef\x15\x72\x19\xb3\x63\x34\xfe\xd8\xf8\
+\xd4\xd3\xcc\x9d\x92\x52\x77\xe9\xe1\x4d\x6e\x67\x66\x08\x91\x96\
+\xf3\xee\xbb\xef\xc6\x83\xd1\x4c\x85\xc3\x5b\x0c\xc9\x25\x81\x30\
+\x4e\x7d\x9a\xbe\x39\xb6\xd6\x66\x13\x4b\x24\x3a\x58\xd2\x6a\x91\
+\x4f\x9f\x85\x54\x59\x59\x59\xe6\x76\x49\x1f\x08\x05\x48\x7d\x61\
+\x0e\x1f\x20\x28\x0a\xb9\x3c\xee\xc0\x78\xbf\xea\x6a\xd3\xa1\x42\
+\x25\x33\x40\xc1\xce\x4f\xcf\x71\xaa\xef\x0e\xe7\x15\x11\x44\xbc\
+\x2f\x1a\x3e\x59\xd1\x34\x3d\x32\xcc\xdb\xba\x58\x51\x17\x67\x43\
+\xe1\x81\x79\x42\x3d\xe8\x08\x35\x30\xb2\x7b\x20\xd3\x9a\xe1\x4e\
+\x65\x49\x92\xac\x1d\x9d\x6b\xd6\x8b\x08\x0b\x5a\x64\x9b\xd7\xae\
+\x2d\x46\x1d\x4a\x92\xbd\xa5\x60\xe9\xd2\x41\x22\xc8\x94\x3c\x33\
+\xb7\x63\x05\xd3\x20\x77\x5b\x05\x2c\x73\x4c\xd3\x58\x45\x33\x12\
+\x62\x4d\xca\x33\xeb\x9e\x70\x78\x3c\xcf\xac\x7d\x3c\x37\x82\x08\
+\xb5\xfe\x89\x5c\xb3\x42\x2a\xf1\x99\x93\xe5\xf9\x55\x49\x4d\x2a\
+\x3c\xc2\x27\x15\x72\xb0\x4e\x82\x48\xbf\x7a\x80\x93\xae\x0f\x38\
+\xcf\xb2\x57\x74\x6f\x36\x46\xd6\xc1\xd9\x5f\x56\x7a\x45\x71\xb9\
+\x02\x17\x60\xa5\x8d\x0a\x45\xb0\x5f\x34\xa8\xfe\x94\x86\xb2\x30\
+\x21\x9a\x1c\x7f\xbc\xa9\x2e\x29\x55\x83\x87\x36\x68\x6b\xc0\x24\
+\x5b\x02\xa9\xd8\x5a\x90\x6b\xd4\xa8\x51\x36\x04\x84\x83\xe5\xfb\
+\x10\xa8\x4f\x9b\x6d\x36\xb1\xbc\x37\xb0\xb1\x10\x81\x72\xc4\xee\
+\x69\xb0\xdc\x5f\x10\xa4\x22\x8f\x8a\xa7\x82\x69\x4e\x04\xe8\x78\
+\x5a\x20\x1a\x12\x2b\x44\x35\xec\x2c\xe2\x53\xf9\xdb\x98\x81\x12\
+\xe7\xf3\x2f\x14\xa9\x6e\x8f\xec\x29\x0c\xf4\xe5\xf2\x8a\x56\xe8\
+\x09\x2d\xd4\x10\x09\x81\x4d\x3c\x27\xaf\xf6\xac\x94\xba\xdf\x19\
+\xe5\x7d\x1d\xa1\x1e\x0a\x24\xd4\x50\x67\xf7\x3c\xe2\xd4\x93\xec\
+\x25\x24\x09\x12\xc5\x3c\x99\x6b\xbe\xeb\x96\x63\x5e\xbb\xbe\x04\
+\x3b\x4b\xa4\xfb\xa9\xaf\xf6\x9d\x9c\x67\x0a\x46\xe5\x8b\x4c\x95\
+\x4c\x8f\xc3\x2a\x9b\xde\x47\x56\x36\x33\xa5\x1a\xcd\xb3\x22\xd4\
+\x53\x01\xc6\x0b\x4f\x46\x30\x13\xf3\xcc\x82\x56\x91\x1a\x0c\x43\
+\x0c\x09\x6a\x50\xd2\x6a\xda\x7f\x32\x23\x09\x3a\xd4\x19\xfd\x0f\
+\x3b\xef\xf2\x41\x77\x4f\xba\x37\xee\x71\x65\xa8\x1e\x9d\x71\x6f\
+\xc7\x29\xeb\x47\xaa\x91\x71\xc8\x15\x77\x46\x03\xdc\x13\xf7\x73\
+\xe4\x92\x9a\xab\x26\xa9\x75\x87\xc8\x94\xdc\xee\x24\x4e\x12\x4b\
+\x64\xea\x1b\x1a\x85\x7c\x2f\x34\x0c\xa6\x8b\xef\x47\xf5\xe9\x2a\
+\xa9\xc4\xd3\xd5\x9f\x9b\xc4\x0d\x4b\x2c\x62\x18\x9b\x02\x5d\x40\
+\x1b\x24\x94\x9f\x4d\x82\x31\x78\xd0\x41\x07\xd9\x64\x3c\x42\x0a\
+\x2f\xbf\xfc\xb2\xf5\xfa\xb8\x11\xc4\x70\x88\xdb\x24\xb9\x6a\x43\
+\xaa\xdc\x8a\xd6\xf3\xfb\xfa\xe2\x28\xcd\x84\xd8\x94\x15\xf3\x6a\
+\xb0\xef\xab\xa7\x9b\x2f\x6e\x49\x33\x5f\xdc\x9a\x6e\xbe\xa9\x95\
+\x61\x96\xb5\x48\xf2\xf4\xbc\x1d\x55\x1c\xa1\x64\xf7\x58\x3b\xc8\
+\x11\xca\xaa\x2c\x24\x8a\xec\xa3\x5f\x07\xe4\xda\x78\x55\x49\x76\
+\xd6\x97\x1d\x73\xac\x64\x82\x44\xeb\x45\x96\xc2\xc7\xf2\xcd\x9a\
+\xf1\xf9\x96\x6c\xeb\x9e\x09\x30\xc1\xe1\x69\xed\x37\x21\xc2\x1b\
+\x22\xe6\x98\x83\x8b\x57\x83\xa3\xf5\xdd\x67\x72\x10\xcc\xe3\xee\
+\x5a\x87\x47\xd7\x6e\xbd\xcb\x87\x5d\xe8\xa2\x5f\x44\x30\x1b\x7c\
+\xf5\xd2\x2b\x54\x8d\x8d\x1d\xb9\xea\x46\xe4\x5a\xee\x22\xf6\x64\
+\x74\x10\xad\x6f\x7c\xec\xbf\xcc\xed\x6a\xe7\x6a\xd2\x14\x61\xdb\
+\xf3\x90\xd3\x27\xa4\x23\x91\x72\x03\x6e\xbb\xed\x36\x23\x42\x18\
+\xdf\x9f\xae\x4f\x67\x6a\xbb\xc3\xa6\x70\xc3\x12\xcb\x25\xde\x6d\
+\x2c\xce\x14\x56\xfb\x8b\x20\x00\x8a\xe4\xe2\x62\x09\x7e\x92\xa1\
+\x80\x8d\x75\xa3\x06\x8a\xb9\xe8\x10\xb7\xe8\xa9\xb9\xcb\x4a\xaa\
+\x8a\x66\x60\x9e\x23\x95\x08\xb5\xf4\x8e\x48\x52\x2d\xd5\x70\xc7\
+\x82\x1b\xd3\xcd\xec\xab\xd3\xcd\x9c\xeb\xd2\xcd\xa7\x55\xb5\xd5\
+\x50\xc8\xbc\x6a\x99\xe6\xd7\x36\x8a\x1f\xf5\x4d\xa1\xf6\xb0\xa1\
+\x86\x15\x49\x28\x4b\x28\x6f\xf3\x3c\x16\xa9\x29\xab\xb6\x24\x51\
+\xd6\x8b\x00\xab\xc6\xe6\x99\x77\xab\x65\xdb\x98\x55\x71\x21\x87\
+\x39\x74\xfc\xa4\x7c\xb3\xfe\xd9\x7c\xbb\x35\x53\x20\x55\xf4\x7f\
+\x02\x26\x16\x81\xef\x0b\x46\xe4\x99\xf1\xff\x92\xb4\x2a\x26\x7e\
+\x85\x7a\x7c\xea\x04\x3d\x28\x0f\x47\x6a\xd3\x3a\x00\xa3\xdc\xb5\
+\x0f\x73\xe1\x0b\xee\x69\x40\x0a\xe9\xe5\x54\xe3\x0a\x9c\x96\x16\
+\x91\xdd\x55\xc0\x40\x77\xdd\x74\x1b\x92\x80\x5c\x05\xb2\x4f\xa7\
+\xec\x1f\x49\xae\x86\xaa\x29\x41\x7b\xdf\x26\x32\x25\xf4\x81\x88\
+\xc5\x96\xc4\x41\x1c\x2b\xfa\x0b\x4f\x11\x8d\x13\x92\x4b\x18\xb4\
+\x29\xfc\xb0\xc4\x72\x19\x08\x65\x02\x63\x80\x42\xbe\x7e\xfc\x41\
+\x78\x01\xd2\xc7\x36\xd8\x89\x4d\xc5\x28\x3b\xde\x20\x45\x35\xd0\
+\xe9\x21\x6e\xe6\xe9\xb9\xfe\x7a\xd3\xb6\xf2\x76\xd6\xa6\x42\xfd\
+\x79\x49\x45\xc3\x30\x38\xbb\xf0\xb6\x88\x54\x9f\xde\x90\x6e\xc7\
+\xd6\xe6\x2a\x3a\x3d\xb7\x9a\x50\x5d\x51\xf3\x5a\x99\x66\x59\xf7\
+\x28\x6c\x60\xe3\x45\x83\x23\x17\xdf\xba\xfc\xa3\x9c\x94\x1a\x5b\
+\x24\x9d\xac\xdd\xa3\xce\xb3\xaa\x6a\x7c\x24\x55\xac\x74\x91\xa4\
+\xf9\xe8\xee\x12\x0c\x78\x7d\xfe\xc1\x5d\xd9\xda\x4f\x84\x99\x94\
+\x02\xcf\xa6\x86\x99\x9a\x6f\xe6\x35\xcd\xb1\xaa\xae\x58\xa3\x5d\
+\x06\xfd\xf4\x2b\xb3\x8c\x79\xda\x5d\xe3\xb8\x48\xaa\xc6\x3d\xcb\
+\x11\x2e\x6c\x31\x38\x8a\x89\xd9\xa0\xab\x33\xee\x23\xd5\x28\x52\
+\x75\x8c\xec\x2e\xbc\xc6\xe5\xde\xa8\xaf\x9b\x16\x27\xd7\x52\x49\
+\xae\x09\x7b\xc7\x6c\xe6\x47\x5d\x11\xe6\x26\x67\x5f\x85\xe0\xa1\
+\xa7\xcf\x98\x90\x82\xd9\x02\xc9\x98\xb3\x88\x31\x1f\xf4\xed\x5a\
+\xf5\xf7\xc5\x3e\xd5\xa6\xac\xb0\xc4\xc2\x48\x2b\x2b\xd0\xb9\x12\
+\x75\x6d\xd1\xc7\x1e\xb0\x9c\xf9\x6f\xc4\x47\xc8\x52\x60\x42\x26\
+\x17\x7d\xf3\xcd\x37\xdb\x27\xc3\xe3\x66\x3d\x21\xb7\x2a\x57\xa8\
+\xd5\xae\xbb\xdb\xbc\x29\xb2\x2a\x19\xef\xb3\xea\x0f\x51\x2e\x6f\
+\x67\xa9\x06\x65\x3f\x93\x84\xfa\xf4\x7a\x47\xaa\xdb\x20\x55\xa6\
+\x99\x77\x67\xa6\xf9\x4c\xe9\x2d\xf3\x44\xac\x05\xf5\xd4\xa8\x44\
+\xb6\x87\x39\xb5\x37\x32\x20\x94\x57\x79\x48\x28\x67\xf3\x58\x1b\
+\x28\x20\x94\x95\x2c\x22\xc7\x67\x2d\x73\xcc\xeb\xd7\x15\x1f\x7d\
+\x7f\xe7\xb6\x2c\xb3\xe2\x51\xed\xfb\x9c\x48\x33\xb9\x14\x4c\x72\
+\xd2\x4c\x78\xe5\x8a\x92\xd5\x20\xc4\xfa\xa2\x4d\x4e\x64\xa7\x3d\
+\xe9\x0c\xff\x71\x45\x1e\xa5\x25\xd8\x48\xa7\x1e\x87\x38\x72\x3d\
+\x14\x90\x4b\xaa\x71\x65\x37\x67\x77\x79\xa3\x3e\x20\x97\x6d\x4b\
+\xd9\x5c\x8b\x15\xae\x79\x6c\xd7\x28\x4a\x5f\x5b\x41\xd1\x1b\xf5\
+\x50\x87\xfd\x01\xae\xd7\x43\x4e\x8a\x0d\x51\x79\xc8\x85\xd7\x48\
+\x08\xc2\x87\x8b\x80\xc8\x35\x5b\xc6\xfd\x0e\x1b\xc3\x13\x4b\x2c\
+\xe2\x50\x65\x85\xf4\xf0\x51\x3a\x59\xa1\x3f\x29\x46\x1f\x5e\x05\
+\x9e\x1f\xd2\x0a\xfb\x0a\x03\x11\x69\x45\xc2\x59\x1c\x22\xd9\x8d\
+\xba\x91\x26\x07\x1e\x64\xe3\x54\x36\xa4\xe0\x02\x9f\xb6\x21\x24\
+\xca\x57\x28\x56\xb3\x44\x39\x51\xf3\x6e\x2c\x22\xd5\xbc\x3b\x44\
+\x28\x47\xaa\xcf\xee\x16\x94\x74\x37\xb7\x4e\x96\xf9\xaa\xb9\x6c\
+\x2c\xc2\x07\xa3\x03\xb5\xe7\xa4\x94\x55\x79\xe3\x03\x42\x39\x32\
+\x59\x78\xc9\xf2\x7c\xbe\xf9\xb6\x67\x6e\x89\x9e\x21\xf8\x65\xa0\
+\x0c\xf8\x17\xf5\x9b\x29\x11\xc1\x2c\xc9\xa6\xa4\x06\xd2\x6a\xc9\
+\xb0\x3c\x1b\xf4\x2c\x56\x0d\xea\xf3\x67\x4e\x16\x19\x46\xe7\x15\
+\x39\x00\xe3\x23\xef\x32\x2e\xbd\x1e\x0d\xd4\xe3\x08\x67\x7b\x39\
+\xe3\x3e\xee\x39\x12\x96\xe8\x1e\x91\x6b\xa5\x33\xea\x0b\x9b\x45\
+\xe4\xb2\x49\x86\xce\xa0\xff\x51\xc3\x60\x8f\x54\x56\x10\x35\x27\
+\xdf\xdc\x71\xc5\xe5\xe6\x46\x49\xaa\x84\x7e\x11\xae\x53\xa8\xe7\
+\xa1\x87\x1e\xb2\xc6\x3c\x63\x8b\x0c\xfb\x68\xcc\x37\x24\x16\xfd\
+\xdc\x6b\x63\x78\x62\x89\xe5\x2b\x98\x94\x05\x62\xf2\xb3\xb0\x19\
+\x60\x57\x71\x01\x8c\xac\x63\x00\xc2\x7a\xec\xaa\x6b\x34\x23\x05\
+\x89\xe5\x51\xb5\x6a\x55\x73\xbd\xa4\xd5\xbd\xc7\x1d\x6f\xf5\xfe\
+\xcb\x55\x9c\xa4\xaa\xe6\xb2\x39\xeb\xba\x18\x15\x81\x40\x35\xce\
+\xe7\xb7\x67\x98\x4f\x51\x7f\x9e\x54\xb5\x1c\xa9\x94\x2b\xf5\x99\
+\x06\x90\x3f\xab\xaf\xc8\x78\x3d\xa5\xb7\x74\xcc\xb6\x4f\xf8\xba\
+\x90\x50\x81\x84\x5a\xff\x4c\x22\x99\x2c\x9c\x2a\x33\x2f\xe4\x9b\
+\xc5\x43\xf2\x4a\xf4\x0c\xa7\x8b\x74\xdf\xf4\x90\x64\x99\x56\x44\
+\xaa\x12\xf1\x72\xbe\x99\xd3\x38\xc7\x1a\xe6\x25\x19\xed\x6f\xdf\
+\x92\x15\x11\x94\xeb\xd1\x35\x5a\x49\xfa\x94\xbb\x76\x4f\x30\x27\
+\xbd\x08\x8f\x58\x35\x3f\x2c\x20\x57\x7f\x67\xd4\x8b\x5c\x6b\x24\
+\xbd\xd6\x88\x60\xd8\x5c\x4b\x1b\x47\xe4\x5a\xc5\x80\x76\xdd\xa2\
+\x50\xc4\x57\x97\xc4\xcc\x83\x72\xd4\x5a\xee\xb8\x8b\xb9\x49\x12\
+\xaa\xaa\xc8\x74\xa3\xfa\xc4\xf7\x0f\x42\x00\xe9\x45\x52\x00\xfd\
+\x48\x49\x02\x8a\xc3\xe1\x8c\xf9\xbe\x16\x96\xab\xbf\x8f\x2a\x2b\
+\x4f\xca\x4c\x2c\x2a\x9c\x68\xfb\x5f\x9d\x60\xbd\x3f\x19\x5e\x04\
+\x05\xc7\x98\x95\x0c\xdb\x31\xdc\xaf\xd6\xd0\x02\x17\x1a\xe2\x5a\
+\x49\xab\xea\xca\x86\x44\x24\x3f\xb9\x7b\x14\x51\x8f\x07\x3f\xeb\
+\x44\xc3\x33\x36\xf0\x29\x91\xbe\x4a\x5e\xcf\x77\xf7\x65\x9a\x4f\
+\x6f\x77\xea\x2f\x24\xd5\x7d\x59\xe6\x73\x91\xea\x73\xa5\xbd\x7c\
+\xde\x18\x64\x9b\x9f\x7a\xe5\xa8\x33\x9c\xda\xf3\x52\x2a\x50\x79\
+\xc9\x84\xf2\xaa\x0b\x62\xad\x78\x34\xcf\xbc\x7d\x5b\x56\xb1\x06\
+\x3c\xd2\xec\xb3\x56\x39\x76\x5f\x24\x5c\x89\xa4\xd2\xf7\xeb\x85\
+\x17\x2f\xca\x32\x63\x8b\x09\x8a\x12\x7a\x40\x92\x2d\xec\xe6\xa4\
+\x60\x60\xf8\xc7\xc9\x95\x2c\xbd\xbc\x6a\xf4\x86\xfd\xe0\x22\xc9\
+\x65\xa4\x22\x7f\x69\x9d\x65\x9e\x3f\x25\xdd\x0c\x52\xde\x56\x7f\
+\x39\x63\x63\xf6\x49\x33\x9f\x5c\xae\xc0\x29\x1e\x63\x9d\x88\x5c\
+\xa4\xdf\x7c\x78\x6a\x34\x53\xa8\xd1\x41\x07\x9b\xeb\xd4\x1f\xc9\
+\x7d\x44\xbf\x91\xd3\xe5\xcd\x19\xd2\x9f\xe9\x5f\x84\x47\x40\xae\
+\xa7\x1c\x0f\xca\x46\xac\xb2\xe8\x4c\xb1\xb7\xa2\x7e\xf0\x31\x27\
+\x02\xa8\x40\x82\x6a\xa4\xc1\xc0\x72\x54\x20\x5e\x06\x17\x88\x68\
+\xf5\xb8\x96\x0b\xbf\xfa\x2a\xd3\xba\xd2\x76\x66\x68\x7e\x34\xf6\
+\x17\x1f\x4c\xbe\xc7\x91\xca\xc5\xa9\x6c\xe0\x53\x99\x08\x85\x0a\
+\x29\x7c\x75\xaf\x88\x75\x57\x0a\x52\x35\x8a\x48\x35\xbf\x69\xb6\
+\x99\xdf\x2c\xdb\x7c\x2e\x2c\x7e\x48\x1d\x35\xa1\x04\x42\x79\x3b\
+\x68\x4a\xa0\xd2\x44\x96\xb5\xfa\xfe\xc3\xda\x25\x1b\xf0\x1f\x2b\
+\x42\xbf\x6e\x52\x19\x88\x25\xa9\xf6\xb3\xbc\xbc\xc7\x8f\x2a\xd9\
+\x1b\x9c\x74\x86\x52\x77\x9e\x72\x2a\x75\x52\xa2\x67\x19\x0f\x5d\
+\x38\xe9\x85\xd3\xe1\xa5\x57\xb2\xdd\x65\x1e\xc9\x36\x5f\x35\xc8\
+\x32\x03\x77\x48\x33\x9d\x62\x51\x78\xa1\x7b\x2c\x32\xd6\xd1\x0a\
+\x6f\x9e\x9d\x66\x56\xa9\x5d\x6d\x80\xf9\xce\x68\xf8\x67\xea\xa1\
+\x7c\x97\x66\x6a\xca\x1e\xbe\xda\xa9\xc0\x10\x14\x57\xf1\xa9\x36\
+\x0c\xfd\x9c\x70\xc2\x09\xd6\x29\xf3\xda\x09\xa8\xcf\x2f\x25\x35\
+\xaa\x4c\x36\x56\x59\x5e\x22\x56\xad\xf0\x04\xd2\xa3\xb6\x4c\x22\
+\xb3\x44\x48\xe0\x67\x1c\x8a\xff\x51\x83\x71\x28\xed\xe5\x2a\x89\
+\xde\x06\xfb\x1f\x68\x7a\xc7\xa2\x2c\x85\x95\x6e\xec\xcf\x0e\xd3\
+\xf8\x88\x7a\x73\x37\xe6\x47\x8a\x8b\x8c\xd2\x35\x72\xad\x57\x28\
+\xd1\xee\x0b\x91\x69\xde\x3d\x89\xa4\xf2\x84\x9a\xaf\x74\x14\xa2\
+\xe7\xf3\x85\x05\x1a\x93\x2b\x18\x91\x1b\x85\x06\x26\xa6\x26\x54\
+\x4a\x32\xbc\x94\xaf\x94\x98\x9c\x62\xed\x2c\x62\x5c\x48\xb4\x35\
+\x52\xab\x56\x6a\x95\x20\xad\x38\xd6\x27\x22\xe1\xe8\x83\x4a\x50\
+\x83\xfa\x0e\x22\xb3\xaf\x25\x79\x60\xf8\x97\x2a\xbd\xe2\xaa\x31\
+\x1a\x87\x9c\xad\x87\xae\x6f\x7e\x44\xa4\xfb\x93\x00\xc9\xfa\x48\
+\xf5\x2d\xac\x16\xa5\x41\xdb\x89\x23\x7a\x90\x7f\x55\xd2\xe0\x18\
+\xe5\xd4\xb7\xcf\xce\x37\x37\x5e\x7a\x89\xb9\x5a\x64\x0a\xfb\x8b\
+\xe2\x74\xd8\x5c\x4c\xbf\x43\x6a\x61\x77\x21\x3c\x10\x22\xbe\xdf\
+\x85\xb7\xd4\xf7\xd9\xe5\x32\xa4\x23\x06\x56\xd2\x01\xe7\xfa\x83\
+\xe3\x8e\x92\xb5\x80\xc1\x8e\x0a\xa4\xe0\x18\x92\xea\xaa\xab\xae\
+\xb2\x5b\x8f\x2b\x45\xaa\x3b\x54\x6b\x80\x24\xbd\xa9\x87\x44\xa9\
+\x2f\x36\x4b\xa1\xa6\x1b\xfb\xf3\xa4\x52\xc0\xcf\x0e\xd1\x90\x95\
+\x40\xe0\x53\xc6\xe9\x6f\x8a\xe1\x14\xc8\x86\x80\x50\x96\x58\x90\
+\xaa\x49\x76\x11\xb1\x20\x54\xab\x88\x54\xf3\xb5\xfd\xb2\x83\x06\
+\x97\xc7\xe5\xd9\x58\x53\xb1\x52\x2a\x05\xb1\xbe\xec\x98\x6b\x83\
+\xa1\xc5\x19\xf0\x90\x6b\xe9\xc8\x3c\xab\xba\x4a\x22\xd6\x3a\x11\
+\xe2\xb9\x73\x33\x8b\x1d\xc2\xb1\x52\x4c\xaa\xf0\x87\x07\x72\xad\
+\x74\x8b\x1b\xfd\xc9\xe4\x7a\x36\x20\xd7\xd3\x11\xb9\xac\xe4\x1a\
+\x17\xc5\xbc\x8c\xf0\xf6\x0d\x99\xa6\x47\x7a\x44\xa0\xfb\x8b\x01\
+\x52\xec\xfd\x0b\xd3\x8d\x69\x93\x6e\xd6\x37\x4c\x33\xeb\xa4\x16\
+\x4d\x6d\x19\xf3\x97\x6b\x5a\x5c\x56\xcc\xb4\xd8\x73\x6f\x73\xad\
+\x88\x75\xb5\x08\x15\xf6\xd9\x65\x97\x5d\x66\x85\x04\xf6\x32\x02\
+\x03\x43\x1e\x21\xe2\xfb\x1e\xe1\x22\x21\x73\x5d\xb9\x10\x4b\xee\
+\xe7\x3d\x30\xd7\x83\x13\xa1\xf6\x10\x99\xd8\x57\x64\x2c\x72\x41\
+\x10\xcb\xe3\x4a\x5d\xf0\xb5\x92\x60\xad\x35\x78\x3b\xa2\x52\x94\
+\xa4\xb7\xdc\xa7\xbe\xd4\xf1\xc6\x7a\xba\xf5\x66\x6c\xfe\x54\x17\
+\x37\x44\xd3\xc7\x05\x3e\x1f\x8e\xc2\x09\x4b\x24\xbd\x22\xf5\xe7\
+\x48\xd5\x3c\x91\x54\x0b\x94\x37\xb5\xa0\x6d\x8e\xf9\x5c\xee\xfb\
+\xd7\x5d\x73\x8b\xd4\xcc\x94\x32\x18\xdb\xf2\xe2\x7e\x94\x1a\x7d\
+\xbd\x04\x03\x9e\x01\xe9\xef\x19\xda\x99\x5a\xb2\x1a\x5c\xa4\x7d\
+\xbc\x1d\x55\xdc\x10\xce\xf3\xe7\x65\xa6\x54\xab\x71\x82\x4d\x4a\
+\x94\x5e\x56\x35\x3a\x72\x31\xe8\xfd\x9b\x24\xd7\x8b\xe7\x67\x5a\
+\x29\xd5\x3d\x05\x99\x48\x88\x24\x8d\xa6\x9f\x7b\x3f\xf5\xf4\x34\
+\x79\xd0\xe9\xe6\x55\x85\x6f\x1e\xbb\x38\xcd\x3c\x7c\x56\x9a\x19\
+\x26\xad\xd1\x5e\x76\x6e\x1d\x7d\x5f\xf5\x5f\xff\x32\x57\xeb\xe1\
+\x47\x0d\x7a\xc1\x80\xd4\x62\x8b\xb0\x40\x68\x30\x96\xc8\xa8\x0a\
+\xc2\xc4\xf7\xbf\x24\xd8\x7b\xe2\x40\x6e\xa9\xc4\x12\x03\x4b\x42\
+\x25\x1d\x6c\x9e\x3f\x28\x31\x0e\x3c\x86\x09\x13\x26\x58\x35\x48\
+\xa5\x97\x4b\x2e\xb9\xc4\xea\xe7\x10\x97\x89\x58\xf7\x1e\x7c\x48\
+\xa2\x0a\xac\x11\xe5\x14\x91\xfe\x81\xb1\x6e\x07\x94\xdb\x39\x52\
+\xf5\x70\xa4\xea\x57\x34\x3c\x83\xb1\xba\x4e\xa2\x7f\x71\x1f\x11\
+\xa7\x89\x53\x7f\x2d\x03\x52\x89\x50\x0b\xda\x39\xb4\xd7\x3e\xfa\
+\x9f\x10\xc2\xda\x67\xcb\x60\x13\x01\x49\xa1\xe5\x8a\xc0\x97\x94\
+\xf4\x87\x9a\x9c\xdf\xb6\x04\xcf\xf0\xf9\x88\x58\xef\xd5\x28\x45\
+\x0d\xca\x1b\x9c\x59\x2f\xdb\x7a\x8e\xa9\x8e\x53\x92\xf4\x62\x20\
+\x7c\xf9\x23\xb9\xe6\xa9\xe3\x32\x24\xfd\xa3\x04\xbf\x90\x50\x3d\
+\x1d\x99\xf8\xbc\xb1\x70\x95\x70\xb4\x70\xb0\x8c\xf9\x1d\x64\x47\
+\xd3\xcf\xa9\xb0\x8d\x04\xc4\xae\x2a\x81\x44\xfa\xcc\xa5\x9a\x09\
+\x64\xb5\x8c\x88\x45\x7f\x92\xe2\x8c\xd0\x60\xb0\x9a\xcf\x11\x26\
+\xa1\x70\x11\x2f\xae\x2a\x89\x37\x65\xf1\x0a\xaf\x0f\x0f\xc8\x05\
+\x11\xa7\x82\x54\xe4\xf4\x90\xad\x40\x1d\x4d\xaa\x03\x7b\x5c\x2a\
+\xc6\x5f\xaf\xa9\xf2\x1d\x32\xb2\xec\xc4\x07\x72\xd4\xfd\xc0\xb2\
+\x55\x81\x0d\x9d\x0a\x6c\xeb\xf2\xa8\xba\xbb\xc1\x64\x3f\xee\x37\
+\xa8\x68\x78\x66\x2d\x83\xc7\x7a\x52\x7f\xec\x19\x19\xea\xc9\x92\
+\x0a\x42\x85\x80\x04\x3f\x3e\x98\x6b\x3d\xb4\x52\xc9\x25\xbb\x09\
+\xfb\x89\x14\x99\x62\xc7\x0c\x45\xac\x59\x0d\x72\x8a\x3f\x96\xc8\
+\xb9\x5a\xe3\x87\xcf\x9e\x51\x82\x1a\x44\x8a\x1d\x99\x61\x16\x0f\
+\xce\x2b\x31\x74\x91\xa0\x1e\x7d\x58\x44\xfb\xff\xa2\x71\xcd\x91\
+\x07\xa4\xa7\xb4\xa7\xfa\x3a\x42\xdd\x24\x1c\x24\x64\xc4\x8a\x27\
+\x52\x49\x20\x5e\xc5\x6c\x1e\xfa\x0f\xc1\x00\xb9\xa8\xc7\x4a\x3f\
+\x93\x46\x4e\x7e\x3c\x42\x25\xe0\xc2\x34\x71\x23\xad\x44\xaf\x10\
+\x17\x32\x15\x24\xf2\x32\x84\xd7\x31\xde\x00\xee\x27\xb6\x15\x35\
+\x3c\x39\x21\xe5\x17\xa9\x87\x0e\xdb\x51\x85\x1e\x17\xeb\xc2\x1a\
+\xfc\x63\x0f\xf3\x50\x86\x0c\xc8\xcb\x9c\x0a\x0c\x43\x0b\x4d\x43\
+\xbb\x2a\x18\x50\xee\xef\x86\x69\x86\xba\x21\x1a\x82\x9f\x8f\xba\
+\x61\x19\xa5\xa6\x7c\x7f\xbf\xa4\x52\x8b\x80\x54\xed\x02\x52\xc9\
+\xc6\xfa\xa2\x63\x04\x3e\x5f\x3c\x38\xb7\x64\x83\xdb\x87\x08\xd4\
+\x89\xb3\xea\x67\x17\x1b\x81\xb7\x43\x3b\x35\xb3\x8b\x37\xe0\x25\
+\x81\xbe\xe9\x91\x5b\xf2\x10\x8e\xa4\xd5\x4b\x97\x28\x71\x70\x4a\
+\x7e\xe9\xd7\xe4\xd5\x38\x78\x35\xdf\x2c\x94\x7a\x7f\x78\xe7\x34\
+\x2b\xa9\x92\x49\x85\x94\x6a\x20\x1c\x58\x46\xf2\x40\x0c\x50\xd2\
+\x3e\x68\x23\xfa\x14\x61\x41\xf6\x29\xa5\x36\xb1\xb7\x70\xcc\xf8\
+\xde\x73\x41\x58\x23\x9c\x56\x1c\x77\x4a\x0c\x37\xe8\x22\xce\xf5\
+\x5e\x01\xe0\xc0\xe8\x5f\x8c\x3a\xa4\x15\xf9\x55\xff\x56\xd6\x22\
+\xec\xf6\xb8\x48\x17\x50\x55\xee\x2c\x06\x3b\x53\xc7\xe3\x69\x30\
+\xb5\x5d\xf6\x67\x63\x97\xf9\x19\xda\x55\x3e\xed\x05\x52\x0d\x89\
+\x48\x45\x60\xd0\x7a\x41\x2e\xf0\xc9\xc0\xf1\x6f\x72\xc1\xbf\xe9\
+\x2e\xa9\xd4\xba\x78\x52\xc5\xc9\xa5\xcf\x97\x3e\x92\x57\x7a\x47\
+\xca\x76\x42\xca\xbd\x76\x4d\xf1\x9e\xe1\x9b\x37\x65\xd9\x98\x57\
+\x4a\x3b\x4b\xc4\x62\xbf\xd2\x86\x70\xe6\x34\x92\xd4\x7b\xa5\x8c\
+\x81\xd6\x17\xa2\xe3\xce\xd6\xa4\x8f\x3e\x79\xa9\x3d\x3f\x48\x75\
+\x97\x90\x5f\x02\x41\x28\xb0\x42\xb5\x1a\x9c\x2c\xfa\x8b\x7e\x83\
+\x24\x8c\x0d\x32\xef\x80\xa0\xa8\x16\x91\xda\xe0\xb7\x48\xa7\xf3\
+\xcf\x3f\xdf\xf6\x27\x09\x9a\xd8\xd2\xcc\xb4\xc6\x78\x87\x98\x01\
+\xb9\x46\x94\x18\x6e\x20\x2e\x91\x0a\x3a\xc8\x30\x8c\x36\x40\x14\
+\x96\x03\x33\xbb\x16\xbd\x8b\x6d\x05\xa9\x60\x37\x9e\x83\x85\x58\
+\x7e\x91\x2e\xa6\xe1\xce\xbb\x9a\xc1\x39\xd1\x64\xd2\xe5\x7e\x8a\
+\x56\x5d\xa7\x02\x9b\xb9\x19\x34\x49\x2a\xd0\x66\x29\x0c\x76\x63\
+\x7f\xa3\x82\x81\x64\x3f\x80\xfc\x54\x34\x04\xb2\xfa\x89\x7c\xf3\
+\x55\x97\x5c\x4b\x86\xe2\x48\x65\x89\xe5\x3e\x5f\x31\xb6\x14\x8f\
+\x4e\xaa\xe6\xbb\x5e\xb9\x91\xc4\x2a\x41\x1d\xfe\x32\x28\x05\xb1\
+\xf4\x7f\xe1\xb8\x7c\x33\xe1\x14\xa9\xc1\xc3\x8a\x57\x83\x4f\x1e\
+\xa3\x81\xf3\x91\x79\x25\x3b\x00\xc1\x31\xf1\x56\x67\xdc\x9a\x65\
+\xba\xa7\xa5\xf6\xfc\x18\x67\xad\x97\x82\x54\x10\x82\x34\x25\x22\
+\xe8\x54\x01\x2c\xcb\x8b\xfd\x18\x1f\xc4\xdb\x0b\x8f\x85\x66\x3a\
+\xf7\xdc\x73\xed\x5c\x45\x6c\xad\xb7\xde\x7a\xcb\x8e\x07\xf3\x9d\
+\xe7\x84\xb0\x58\x3c\xd9\x33\x15\x77\x2c\xb1\x48\x2f\x4e\x81\xdd\
+\xf5\xc3\x9f\xfc\x41\x38\x20\x65\x0a\x11\x8d\x4c\x80\xe4\x06\x38\
+\x31\xc4\xf2\xb8\x40\xa4\xba\x5e\xf5\xd5\x91\x56\xd3\xff\x99\xc2\
+\x60\x6f\xec\x26\x3e\x78\x83\xbd\x97\x93\x56\x0f\xb9\xd4\x97\x61\
+\x2e\xcb\x73\x4c\x51\xba\x4b\x7c\x88\x66\x82\x1b\x3c\x96\x8a\x28\
+\x94\xf4\x80\x34\xf3\xdb\xa5\x26\x55\x48\x2e\x48\x88\x0d\x54\xac\
+\xe4\x52\x47\xfe\x3a\x34\xcf\x66\x8c\x96\x94\x9b\xf5\x75\xb7\x14\
+\x9e\xa1\xa4\xca\x97\x9d\x72\x4a\x95\x56\xd3\xaf\x52\xfa\xf1\x73\
+\x65\x50\x83\x22\xf9\x5a\xdd\xdf\xd4\xff\x14\xef\xf9\xe1\x0c\x75\
+\x14\x76\x4f\x22\xd5\x19\x67\x9c\x61\xbd\xb8\x4d\x7d\x31\x71\x38\
+\x59\x7a\x31\x3d\x1f\x9b\x0b\x89\x47\x39\x29\xea\x73\x61\xc4\x07\
+\xc4\x42\x6a\xd5\x4f\xc5\x9f\x92\xbc\xc2\x5a\x48\x29\x80\xf8\xc3\
+\xbe\x62\x14\x9c\x13\xe0\x8a\x42\x24\xea\x92\x23\xb5\xc0\x85\xc2\
+\xf9\x92\x58\xf5\x77\xd9\xcd\x56\xc6\xfb\xe1\x4a\x57\x73\xaa\x66\
+\x20\xad\x9a\x47\xb3\x69\xac\xb4\xea\x91\x18\x5a\xf0\x2a\x30\x6e\
+\x57\xf9\xb1\xbf\x80\x54\x3e\xf0\x89\x6d\x54\xa0\x41\x5c\x2b\x95\
+\x3a\x94\x4c\xae\xf9\x92\x6a\x8c\xf7\xfd\x36\xb1\x98\x8e\xd5\x67\
+\xab\xc6\x97\x3c\xb4\x83\x34\xfb\x54\xa1\x8e\x0d\x88\x25\x49\x88\
+\x0a\x2d\x6e\x08\x87\xb8\x15\xc4\x22\x14\x52\xaa\x1a\xd4\xf7\x2b\
+\x1e\x53\x1e\xd7\x09\xa9\x3d\xbf\x50\x05\x9e\x99\x44\x2a\x6c\x5c\
+\x56\xc2\xd8\xdc\x17\x9a\x88\xb4\x19\x7f\x5c\xa2\xee\x94\x45\x42\
+\xa2\x31\xd4\x43\x8e\xfc\xbe\xfb\xee\x6b\x3f\xf7\xdc\x10\x66\x08\
+\xb9\x29\xbd\x42\x11\x67\x03\xe8\xcb\x69\xfe\xc7\x1c\x88\x2a\x7b\
+\x1c\x98\x13\x50\x04\x8c\x85\x89\x98\x9f\xe6\x71\x9e\x88\x76\xe5\
+\x29\xa7\x2a\x30\xa7\x94\xdb\xc3\xdc\x20\x73\xf5\xa4\xf0\x42\xab\
+\xc8\xb6\x22\x0b\xd4\x4e\x7c\x78\xc0\xa5\x13\x7b\x2f\x30\x50\x81\
+\xf1\x0c\x85\x24\x52\xc5\x23\xe9\xea\xd4\x25\xc3\x73\xad\x3a\x2c\
+\x89\x58\x5e\xb2\x7d\xd7\x3b\x37\xf5\xd0\xcc\xf3\xd1\xf1\x4a\xcb\
+\xcd\xfa\xb0\x56\x76\x74\xde\xe7\x8b\x48\x55\x30\x2a\xcf\x86\x00\
+\xc6\x55\x29\x7e\x08\x67\xc2\x89\x1a\x51\x18\x57\x8a\x3a\x9e\x9e\
+\x6f\x7e\x52\x16\xc5\x88\x7d\xd2\x53\x1a\xe9\x1e\xc4\xa8\x9a\x60\
+\x84\x07\xa4\x62\x32\x04\xd5\x95\xcb\xeb\x85\xd0\x48\x56\xaf\x84\
+\x1b\xa8\x3f\x8f\x7d\x86\x5d\xe6\x1d\x01\xc7\x8f\x75\x7a\x7f\x6c\
+\x32\x7f\x8a\x23\xd6\xc1\xfa\xc1\x52\x4f\x2c\x0e\x74\xad\x86\x67\
+\x18\x3f\x22\xdc\x4f\xf8\xff\xec\xb3\xcf\xb6\x06\x9e\xc7\xd9\x92\
+\x58\x35\x55\xb8\xe2\xa1\xb4\xa8\xae\xc2\xf2\xdb\x9d\x6d\x95\x2a\
+\xbc\x90\x2c\xad\x86\x06\xa9\xc4\x8f\x06\xb9\x54\x4f\x25\x8d\xfd\
+\x85\xc4\x72\xe1\x84\x9f\x1e\x2e\x3b\xb9\x08\x86\x9a\xe4\x30\x84\
+\xfb\x7f\x6e\xf3\x52\x72\xb3\x94\x6d\xba\xf2\xf1\x40\xea\xbd\x12\
+\xfd\x06\x89\x34\xae\x04\x6f\x10\xbb\xad\x58\xdb\x8a\x73\xbf\x26\
+\xcf\x4f\x0e\xc9\x00\x79\x7e\x5d\x4a\x20\x15\x78\x50\x38\x3b\x49\
+\x5a\x11\x12\x28\xcf\xd7\xba\x75\xeb\xac\x47\xe8\x8f\x8f\xea\x3b\
+\xea\xa8\xa3\xec\x18\x22\xf3\x18\x18\x1b\xc6\xa1\x43\x83\x05\x1a\
+\xad\x7d\x59\x89\x55\xcb\xbb\xa6\x1c\x80\x44\x3e\x32\x42\x61\x2c\
+\x25\xa1\xd1\xe7\xd8\x57\x1e\xe7\xc8\xfb\xb8\xe0\xac\x33\x4d\x2b\
+\x95\x65\x7c\x66\x4f\x17\xb7\xba\x23\xf4\x04\xa3\xd9\x35\x36\xc2\
+\x1e\x0c\xdb\x58\x83\x7d\xd0\x86\x06\x7b\x71\x2a\x70\x83\x71\x3f\
+\x27\x6d\x16\xf5\xcb\x2d\xd5\xde\xf2\x9e\x22\xa9\x32\x1b\xa8\xc4\
+\xa9\x51\xc8\x60\x7a\x49\x9e\xa1\xb6\xbf\x0e\x73\x76\xd6\x0b\xd1\
+\x79\x5f\xfe\x6f\x56\xa9\x99\xa2\x0b\x35\x69\x23\x65\x50\xf4\xc5\
+\x88\x9c\x33\x1b\xc8\xf3\xcb\x4d\xb3\x03\xc7\x25\x91\xaa\x97\x1b\
+\x60\xde\x29\x20\x15\x05\xeb\x56\xae\x5c\x69\xca\xfb\x85\x0d\x4d\
+\x3a\x54\x68\xc8\x93\x69\xca\xe7\xd8\x62\x54\x6c\x0e\xc3\x17\xc2\
+\x7b\xd9\xd1\x2b\x91\x58\xc9\x49\x5a\x22\xd2\xf3\x61\xcc\x83\x55\
+\x20\x90\x54\x1c\x98\x88\x2c\xde\x01\x12\xcb\xe3\x74\x49\xac\xaa\
+\x87\x55\xb1\x0d\x40\x6d\x81\x42\x5f\x9f\x2a\x95\x27\xe8\xa5\x55\
+\x7f\x67\xb0\x0f\xf5\x81\xd0\x20\xf3\x73\x7c\x90\xa0\x57\x1c\xa9\
+\xc2\x71\xba\x49\x51\xd2\x5e\xa9\x92\xab\x43\x44\x2e\xeb\xa1\xbd\
+\x90\x38\x66\xf8\xcb\x20\x37\x66\x58\x9c\x01\x2f\xd2\x7d\xaf\x11\
+\x00\xf6\x05\x4b\x64\xf0\x3f\x7e\x64\x64\x47\x15\x37\x84\x33\xf1\
+\x74\xdd\xef\x93\xa9\xbd\x49\xc0\x71\xbb\xa5\x95\x3c\xe6\x17\x7a\
+\x82\x84\x17\xd2\x03\x62\xb1\xb2\xc5\x96\x7a\x11\xaa\xf0\xe7\x41\
+\xb0\xf0\x3f\xa9\xcb\xe4\xc6\x33\xeb\x2a\x89\x58\xab\x44\xc4\x63\
+\x37\x48\xf4\x4b\x72\x15\x77\x95\x94\xfa\x89\x83\x01\x0e\x40\x24\
+\x16\x77\x93\x9a\xa0\xc4\x36\xf0\x0e\x99\xe2\x65\x21\x5b\xeb\x0c\
+\x91\xab\xee\x0e\x3b\xdb\x4a\xc4\x94\x67\x2c\x70\x93\x22\xa8\xfd\
+\xb4\xc2\xc5\xad\x56\x3a\xdb\x8a\xe4\xb4\x78\x78\xc1\xdb\x56\xa1\
+\xc1\xee\x54\x60\x42\x92\x9e\xcf\xa1\x2a\x31\x82\x9e\xaf\xb1\xc2\
+\x9c\x52\xc9\x15\x0f\x43\x84\x76\xcf\xd4\x68\x68\xe7\xed\x5b\x4b\
+\xce\xcd\xb2\x43\x3b\x2f\x44\x81\xcb\x99\xf5\x72\x4a\x9e\x8c\x4a\
+\x42\xdf\x6d\xd9\x1b\x1a\xed\xf2\xfc\xd6\xe8\x9e\x9e\xbf\xb0\x78\
+\xcf\x2f\x19\xec\x43\x3d\xb0\x9b\x76\x4f\x4f\x08\x76\x92\x81\xb0\
+\xa5\x5e\x84\x95\x42\x95\x7b\xf0\xc1\x07\xdb\x85\x0d\x20\x16\x6a\
+\x11\x75\x88\xed\xed\x79\x22\xce\xd4\xde\x20\xdc\x10\x32\x4d\x1f\
+\x5e\xad\x1d\xd7\x87\xc4\x62\x71\x21\x8c\x76\x0e\xc8\x4c\x0e\x80\
+\x3a\x04\xa7\x8b\x5c\xe7\x2b\xc4\xd0\x2e\x2d\xd3\xbc\x70\xa0\x4f\
+\x8b\x89\x15\xe5\x5a\xf9\x28\x7b\xc7\x28\x6e\x65\x6b\x29\x38\xdb\
+\xea\xb7\x50\x5a\x85\xb6\xd5\xd3\xa5\xa8\xc0\x62\x86\x56\x0a\x15\
+\x9d\xff\xb2\x73\xf1\xb1\xad\x84\x30\x84\x48\xb8\x7a\xbc\x23\x17\
+\xb9\x59\x9a\xde\xf5\x7e\xcd\xac\x12\x0d\x78\x3b\xd6\x87\x84\x9c\
+\x1c\x25\xf4\x95\x44\x2c\xe2\x5a\x38\x0c\x09\x43\x38\x22\xe4\x72\
+\x85\x4a\x9e\x3c\xae\x64\xcf\x2f\x04\x2a\xb2\x97\xb2\x11\x3e\x96\
+\x73\x71\xdd\x59\x99\xf1\x8e\x66\x7c\xaf\xb0\xb0\x70\x8b\x11\x8b\
+\xc9\xac\x18\xee\xfe\x7c\x78\x8b\x4c\xd5\x67\xf5\x34\xb4\x17\x4b\
+\xe1\x79\x69\xe6\x88\x35\x71\x03\x89\x95\x34\xd9\xb0\x8f\xdf\x19\
+\xc3\x0d\xfd\x4a\xf1\x7a\xe2\x57\xd4\x04\x25\xf9\xcb\x93\x0b\x9c\
+\x2c\x89\x75\xa3\xe6\x12\xd2\x08\x9f\x9d\x17\x15\x92\x2d\xf0\x01\
+\xd1\x46\x81\xd1\xde\x39\xb3\x68\xe8\xc6\x79\x82\x6b\x42\xdb\xea\
+\x31\x17\x0c\x7d\x2a\x50\x81\xcf\x96\x21\xf5\x25\x89\x5c\x84\x21\
+\xbe\x28\x43\x18\x62\x81\x0d\x43\xe8\x9c\x3e\x0c\x31\x15\x7b\x27\
+\xa7\xd8\x14\x1a\xec\xac\x77\xaa\x45\x29\xc5\xbf\x0c\xcc\x8b\xc6\
+\xff\x8e\x28\x9e\x54\x93\xcf\xce\xb4\x52\x34\x2e\x15\xe5\xf9\xfd\
+\xa8\x31\xbf\x61\xa5\x78\x7e\x21\x90\x68\xfd\x2a\xa6\xd9\xc1\x75\
+\xec\xb4\x03\x03\x89\xc5\xfc\xc0\x2d\xfd\x42\x33\xf9\xf3\x11\x40\
+\x45\x5b\x11\x7c\x25\x60\x4a\x88\x23\x24\x96\xb0\x48\xdc\xd9\x31\
+\x61\xc2\x2a\x33\x6f\x1c\xb2\x85\x37\x30\xd8\x01\x3f\x3c\xf1\xc4\
+\x13\xed\xc4\x53\xdc\x4d\xdc\x4e\x16\x98\xc4\xc6\xf2\x38\x49\x52\
+\xeb\x1e\x2d\xf9\xf1\x48\x85\xa8\x9e\x39\x85\xc2\xe2\x6a\xb0\x49\
+\x94\x16\x43\xd1\x33\xea\x53\xc5\xa3\xec\x3e\x6e\xf5\x88\x9b\xa1\
+\xfc\x68\x91\x6d\xc5\x64\x50\x2f\x45\xcc\xe4\x32\x4a\xab\x24\x72\
+\xfd\xaa\x09\x0d\x0b\xda\x95\xcd\x53\x24\xdd\x05\x09\x84\xdd\x04\
+\x19\x4b\x22\x16\x41\xd4\x95\x92\x8a\x1f\xd7\xcd\x2e\x31\xaf\x1d\
+\x35\xf8\x51\x6d\x67\xb4\x3f\x1f\x49\x2a\x02\xa9\x0f\x6d\x9f\x56\
+\x66\x52\xb1\xdf\xe0\x3d\xd2\xec\xf5\x99\xb7\x2a\x98\x2f\x94\xc8\
+\x58\x29\xff\xf7\xb1\xaf\xfc\x8b\xfa\xa6\xa1\xea\x3d\x5e\x53\xf7\
+\xa9\x1c\x44\x74\x80\xd8\x16\x9f\x7b\xae\x08\x6b\xc5\x9d\xf3\x3d\
+\x97\x12\xf2\xb1\x24\xa1\x76\xd7\x0e\x05\x21\xb1\xc8\x87\xe6\x40\
+\x8c\x70\xc3\xe0\x93\x4f\x3e\xd9\x2e\xc6\x0d\x4e\x14\xb1\xce\x3c\
+\xfe\x04\xd3\x5c\x35\x41\x27\xef\x9b\x14\xbb\x8a\xab\xc1\xcc\xb8\
+\x1a\x8c\x87\x18\x06\x3a\x35\x38\xb2\xc8\x13\x24\xb5\xd8\x48\x52\
+\x91\x08\xb7\x40\x01\xc5\x45\x7d\x72\xa3\xd9\x34\xaf\x44\x11\xee\
+\xb2\x0c\xde\x86\xe1\x03\xd2\x84\xe7\x97\x91\x5c\x3f\x0d\xc8\xb3\
+\xc4\x22\x2b\xe2\xf5\x12\x92\xfe\xd8\xa2\xde\x9e\xbf\x20\xb3\xd8\
+\x2a\x32\x48\xb2\xc7\xb5\xfd\xb1\x7f\xae\x25\x14\xd7\xff\xf1\x7d\
+\xd9\xa6\x77\x4e\xac\x54\xcf\xef\x7e\xa7\x1e\x49\xd2\x7b\xf4\xf0\
+\x74\xf3\x2b\x63\x9d\xee\xfe\xa7\x69\x30\x3a\x2b\xb3\x88\x58\x44\
+\xc3\xb7\xf4\x8b\x71\xc2\x30\xec\x80\x9d\x45\x0c\x13\xed\x45\xe5\
+\x20\x6f\x67\x79\xbe\xe8\x7d\xa3\x84\x7c\xac\xc0\xe8\xba\x18\x37\
+\x13\xf0\x03\x40\x18\x9f\x03\x51\x7c\x16\xc6\x22\xc1\x90\x5a\xe0\
+\x78\x91\xeb\x3f\xaa\x65\x45\x83\xb0\xe2\x43\xdc\x1b\xac\x93\x9e\
+\x18\xbb\xea\x5c\x14\x62\xb0\x6a\xd0\x8d\x09\xda\x81\x66\x6a\x29\
+\x68\xe2\xe6\x12\x11\xe1\xe9\x93\x32\x4c\xdf\x0a\x69\x51\x3a\x48\
+\x5e\x9a\x19\xb9\x7f\xba\x79\x5b\x95\x8d\x21\x89\x25\xd6\x6b\x15\
+\xe2\xee\x7e\x59\xb2\x16\xbe\x87\xa4\x65\x21\x17\x03\xd6\x9a\xbd\
+\xcc\xb8\xe2\x1b\x25\x54\xfa\x43\x62\xbd\x79\x53\xb6\x2d\xe6\x31\
+\xae\x04\x35\xf8\x82\x88\x67\x27\x4b\xbc\x54\xc1\xbc\x21\xdb\xac\
+\x5b\x19\x8d\xf4\x1e\x4e\x52\x3d\x7b\x66\x86\x59\x33\x21\xbf\x28\
+\x4c\x21\x82\x8e\x6d\x92\x13\xef\x64\x3a\x11\x5b\x67\x4b\xbf\x70\
+\xd8\x42\x03\x1e\xbb\x8a\x89\xae\xcc\x6f\xc0\x91\x63\x89\x3f\x3e\
+\xf7\x9c\x11\xc6\x09\x69\x71\xe3\x3d\x20\x56\x1b\xbf\x93\xcf\xd1\
+\xa1\xb0\x3f\x25\x9e\x19\x1b\x24\x5f\x07\x1b\xcb\xe3\x58\x49\xaf\
+\x9b\x54\xcf\x0a\x8f\xe5\x5b\x97\x1e\x63\xe7\x08\x92\xcb\xee\x63\
+\x57\x1d\xa2\x3c\xf6\x54\x6a\x10\xa3\xdd\x56\x66\xd1\x50\xce\x23\
+\xfb\xa6\xdb\x27\xb5\xbb\x6b\xe0\xee\xce\x70\xa5\xa1\x7b\x4b\xb2\
+\x3e\xa6\x49\x0a\xef\x2b\x7d\x25\x9e\xb1\x30\xbd\x42\xa9\xe9\xc2\
+\x24\xfc\x7d\x53\xc6\x30\x04\xaa\x8a\x39\x84\xef\xdf\x59\xfc\xb4\
+\x7b\x88\x55\x52\xde\x95\x4d\xe8\x3b\x30\xc3\x06\x4e\xd7\xe9\xfc\
+\x53\x94\x31\xda\x79\x23\x3c\x3f\xf0\xea\xd5\x1a\x57\x7c\x21\x3f\
+\xd1\xe8\xd7\x03\xd5\xa7\x66\x51\xba\x0b\x92\x82\x39\x9c\x5b\xfa\
+\xc5\x04\x56\x38\xe0\xcf\xbb\xd7\x5e\x7b\xd9\xb0\x03\xd3\xc3\x08\
+\x3d\x31\xe2\x12\x12\x4b\x84\x9f\xad\x6d\xc5\x38\xb1\x82\xdc\xf6\
+\x47\x43\x62\xc1\x48\x98\xe9\xa3\xed\x44\x60\x91\x5a\x1e\x47\x4b\
+\x7a\xd5\xd5\xec\x9b\xd1\x95\xdd\xda\x34\xf1\xa0\x68\x5a\xdc\xbe\
+\x8a\xab\xc1\xde\x41\x6a\xcc\xd0\xa2\xc1\x66\x52\x6e\xdf\x95\x9b\
+\xdf\xa9\x0c\x4f\x32\x44\x7b\x20\x3f\xcd\x3c\xa3\x2a\x79\x04\x16\
+\xed\x50\xc9\xcb\xd1\x13\x9d\x32\xba\xad\x0e\x5a\xad\x54\xe5\xaf\
+\xca\x10\x86\x00\x90\xeb\xc3\x9a\xc5\x0c\xed\xdc\x14\x79\x86\x48\
+\xab\x62\x8d\x76\x86\x70\x4e\xca\x54\x24\x3d\xd7\x8c\x3f\x3e\xc3\
+\xde\x53\x59\x3d\xbf\x9e\x8a\x29\xfe\xef\x6e\x97\x61\x9a\xfc\xc0\
+\xbc\x5e\xc1\xb4\xb8\x21\x2b\xde\xc1\xd4\xc8\x28\xcf\x61\x9c\xe2\
+\x5e\xac\x74\xc1\x3a\x91\xa1\x67\x88\x40\x21\xfa\x8e\x16\x23\xe1\
+\xd3\x8f\x29\x3a\x2d\xb7\x4a\xff\xef\x95\xac\x0a\x73\xf4\xc5\x0c\
+\xaf\x02\xf9\x8e\xa8\x3a\x8b\x01\xb1\xc2\x14\xef\x21\xd6\xbf\x94\
+\x27\x0d\x8e\xd5\x7c\xc2\x13\x34\x40\xd9\x4c\xf6\xd5\xf3\x2a\x40\
+\x51\x70\xab\x33\xdc\xef\x71\xf9\xec\xf1\xa0\xa8\x4b\x3b\xee\x1b\
+\x55\x21\x5e\xa3\x22\x63\xe1\xb8\x20\x85\xcd\xc6\x1f\x9b\x5e\xea\
+\x70\x46\x2a\x92\x0d\x90\x31\x3c\x45\x39\xe0\xc4\x97\x6c\x20\x12\
+\x55\x99\xdc\x31\x84\x21\x34\xb8\xfb\x45\xa7\xd2\xc3\x10\x84\x2a\
+\x3e\xa8\x95\x3a\x96\x45\x65\x9a\xa9\x17\x67\x16\x4b\x2a\x40\xc0\
+\xf4\xe9\x13\x32\xcd\x88\x7d\xcb\x7e\x3f\xd6\xf3\xab\xe4\x3c\xbf\
+\x57\x53\x8f\x65\x9a\x57\x2b\x98\xda\x97\x64\x26\x48\x8e\x2d\x11\
+\x71\x4f\x7e\x2d\x5b\xb6\xcc\x1c\x79\xe4\x91\xf1\xf3\xfa\x05\xdd\
+\xa9\x69\x8a\x16\xc3\x4c\x82\x2b\xd8\x5f\x9e\x37\xe2\xd1\x79\x71\
+\x89\xe5\x92\xb3\xf6\x12\xbe\x0b\x89\xc5\x84\x09\x44\x1e\x13\x51\
+\x39\x20\xaa\x90\x2d\x38\x52\xe4\x3a\xfb\xb0\xc3\x4c\x5b\x0d\x3a\
+\xbf\xf3\xaf\x68\xf1\xa3\x84\x68\x7b\xf3\x28\x3d\x66\x55\x97\xac\
+\xc4\x21\x9c\xc1\x41\x22\x1f\xf6\x95\x72\xac\x9e\x38\x3a\xa3\xcc\
+\x1d\x11\x12\xac\x5b\x40\xb2\x21\xf2\xa0\x5e\x54\xc7\x7f\xad\x21\
+\x94\xd5\x38\x02\xa8\xca\x69\xce\x1e\x13\xb9\x96\x8d\x2a\x3d\x0c\
+\x01\xb1\x3e\xa9\x97\xba\xf0\x2d\x6a\x70\xa2\x2a\x1c\x17\x57\x01\
+\x99\xf9\x84\xa8\x73\xd4\x76\xb7\x8d\xf0\xfc\x86\xee\x25\xcf\x4f\
+\x4e\x03\x63\x86\x29\xd3\x9f\x9f\x8f\x6c\xad\x6a\x17\x14\x11\x0b\
+\x4d\xb2\x6a\xd5\xaa\x2d\x4e\xac\xdf\x7e\xfb\xcd\x3a\x69\xfe\xbc\
+\x4c\xaa\x60\x18\x89\x52\x9f\xd8\x78\x14\x2c\xf6\x09\xa0\x9e\x37\
+\x42\xbd\xe4\x79\x85\x47\x89\x79\x6b\x61\x9f\x3f\x10\xab\x1c\x40\
+\x2c\x0c\xf7\x2a\x55\xaa\x58\x62\x79\x1c\x21\x62\xfd\x67\xbf\x03\
+\xa2\xc9\x12\x67\x39\x62\xd9\xc2\x1e\x22\x55\xa3\x20\xda\xde\xcd\
+\x05\x45\x53\xd8\x57\xd6\x1b\x14\xb1\x66\x54\x2d\x59\x15\xde\x5f\
+\x46\x75\xd2\xd9\x91\x6d\x84\x8c\x7e\xbc\x3b\x8c\xf7\xf5\x2e\xc5\
+\x17\x90\x77\x55\x92\x31\x0f\xb1\x3e\x95\x91\xbc\x01\xa9\x6e\x8e\
+\x22\xef\x8f\x1f\x9d\x82\x54\x47\x46\xa4\x1a\xaa\x18\xd3\xfd\x69\
+\x65\x53\x7d\x5e\xea\x3e\x76\x64\xba\x59\x4a\x0d\x87\x57\x4b\x9f\
+\xb3\x78\xd3\x39\x45\xc4\xfa\xe7\x3f\xff\x69\x56\xaf\x5e\x6d\x7e\
+\x8f\x57\x38\xb4\x83\xbd\xc5\x38\x21\x09\x09\x2c\x90\x4e\x8a\xba\
+\x56\x71\x8d\x7b\x8d\x0e\x7d\x93\xa7\x7f\x9d\xeb\xbf\xf4\x71\x0b\
+\x62\x25\xa4\xb1\x32\xdd\xfa\x30\x49\x27\x54\xa1\x47\x15\x49\xad\
+\x6b\x76\xdb\xdd\x0c\xcc\x74\x0b\x23\x25\x0c\xe3\x38\xfb\xaa\x43\
+\x40\xac\x64\xfb\x2a\x08\x33\x14\x28\xfd\x65\xc8\x3f\xd2\x6d\x02\
+\x5b\x8f\xcd\x24\x58\x48\xb2\x5e\xb2\x5b\x46\x1f\x92\xae\x19\x34\
+\xaa\xce\x37\x28\x8a\x82\xe3\xfd\x41\x20\x54\x63\x2a\x1b\xeb\x73\
+\xa5\x3d\x5b\xef\x2f\x89\x58\x44\xda\x93\x67\x38\xdb\xb1\x42\x61\
+\x90\x32\x13\xca\x7a\xdd\xdd\xdd\xb5\x4d\x3a\x3b\xc3\xac\x7e\x26\
+\xaf\xf4\xba\x10\xcf\x47\x12\xf7\xda\x33\x8a\x88\x45\x59\x83\x35\
+\x6b\xd6\xfc\x2e\xc4\x22\xd7\xce\x9f\x97\x0c\x62\x88\xc5\x67\x18\
+\xf0\xd8\xdf\x70\x21\x89\x58\xe3\x43\x62\x51\x6c\xb2\x7a\xe8\x5a\
+\x52\x61\x84\x4c\x06\x46\xb3\xc9\x6d\x47\xfc\xf2\xa4\x78\x54\x91\
+\xee\xbd\x45\xc5\x68\x87\xe7\x47\x2b\x93\x16\x04\xd9\x0c\xd6\x70\
+\x6f\x9d\xb1\xe1\xa0\xf3\x20\x97\x25\x3a\x2a\x71\x6c\x90\xa0\xe8\
+\x4f\x8a\xfb\x8c\xd3\x13\xdc\x23\x2d\x16\x9f\x32\xbe\xb9\x24\xeb\
+\xe1\x48\xc6\xf1\x1e\x50\x00\xf7\x89\x63\x32\xcc\xff\x14\xdc\xa4\
+\x1e\xc3\x57\x5d\x54\xd5\xb8\x47\x4e\x22\xc9\x3a\x45\x46\xfe\x3b\
+\xb7\x27\xaa\x43\x88\x45\x3d\xd1\x90\x58\x7e\x2a\xfd\x43\xdb\x95\
+\x9d\x54\x3e\xec\x80\xf4\xb3\x9e\xdf\x4b\x65\xcc\x7f\x17\xb9\x2e\
+\x3f\xa5\x28\xdb\x80\xc0\xf4\xda\xb5\x6b\x7f\x17\x62\x85\x29\x34\
+\x78\xa3\x10\x0b\xf5\x48\xa9\x2a\xa4\x16\xb6\x77\x52\xce\xfc\x1b\
+\xac\x27\xe1\x89\x55\x49\x68\x17\xee\x40\x69\x22\x8c\x76\x7e\xcc\
+\x20\x34\x25\x20\x39\x68\x1c\xd2\xb5\x77\x6b\xc1\xc9\x31\xdb\x05\
+\x69\x32\x3e\x30\xaa\xe5\xd5\xe2\x99\xa2\xf7\x27\x0e\xe3\xc4\x53\
+\x64\x92\xc6\x06\xed\x93\xa9\x71\xc1\xaf\x3b\xe7\x9a\x69\x97\x67\
+\x99\x41\xbb\xa4\xc5\x55\x46\xf7\x72\x90\x62\xbe\xae\x81\x1f\x26\
+\x79\xf4\x30\x4d\xe4\xbc\x26\xd3\x86\x06\xf0\x1a\x2d\xc9\x1c\xc1\
+\x98\x95\xe3\x0d\x78\x48\x45\x08\x00\x22\x79\xc3\x9d\xf7\x64\x2f\
+\xf4\x53\xcc\xad\xfb\x46\x48\x51\x82\xa4\x04\x4b\x6d\xd0\xf3\xc5\
+\x8d\x98\x58\xa1\xed\xa5\x27\x65\x24\xa4\x21\xff\x5e\xc4\x62\x18\
+\xc7\x9f\x17\x7b\x8a\xbe\x47\xb0\x30\xb9\x82\x11\x19\xd4\x62\x12\
+\xb1\xe6\x08\xdb\x7b\x62\xed\x2a\xf4\x0b\x77\x20\xff\x99\x05\x16\
+\xc9\x1a\x25\x91\x8f\x4c\x45\xec\x2c\x70\x98\x70\xb8\xd0\xac\x42\
+\x65\x33\x61\xf7\xa8\x98\xfd\xb2\x64\x8f\x50\xcb\xac\xd9\xc0\xe8\
+\xfd\x59\x45\x99\xa2\x7e\xb2\xc4\xe8\xc4\x61\x9c\x78\x3e\xfb\xa4\
+\xfc\x78\x5a\x0a\x79\xea\xf3\xa4\x96\xa6\x9c\x1b\x49\x05\x4f\x8a\
+\x6e\xe5\x44\xb2\xae\x6e\xdb\x5f\x1e\x19\xa1\x01\x08\x44\x0a\xf1\
+\x37\x9a\x62\x36\xab\x61\x91\xc4\xc2\x1b\x7c\xe1\xc2\x22\xa3\x1d\
+\x3b\x6b\x94\x62\x55\x7d\x72\xca\x2e\xa9\xb8\xee\x07\x75\x0f\x38\
+\x0e\x8c\x19\x96\x69\x32\x6d\x48\x2c\x91\xf0\x9a\xd3\x8b\x88\x45\
+\xa8\x07\xc3\xfa\xf7\x78\x91\x82\x9e\x2c\xb1\xe0\x02\x0b\x95\x32\
+\xf3\xc7\x87\x1c\x02\x7c\x23\xec\xe1\x89\xb5\xaf\x30\x36\xdc\x01\
+\x9b\x0a\x52\x91\x83\xc3\x13\x42\x4e\xd6\xa1\x87\x1e\x6a\x71\x88\
+\x50\x45\x68\xa5\x50\xc3\x0b\x07\x44\x6b\xbc\xb0\x2e\xb2\x9d\x37\
+\x18\x46\xdc\x59\xfd\xaa\x67\xf6\x06\x1e\xa1\x25\x16\x09\x7d\x8f\
+\xa7\x48\xe8\x63\x6c\xd0\x37\xe8\xab\x91\x47\x44\x4a\xcb\x6c\xd5\
+\x9d\x7a\xea\xc4\x0c\xeb\x9a\x77\x76\x9d\x55\x9e\x92\x0c\x92\x3c\
+\xac\xf0\xc5\xc4\x33\x32\x6c\xbc\x0a\xbc\xa3\xa8\xff\x0c\xd5\xb2\
+\x9a\x70\x52\x14\x69\xc7\x7b\x25\x7d\xb8\x57\x66\xd9\xcf\xcd\xb5\
+\x0e\xdb\x2b\xdd\xfc\xf0\x50\x6e\xc9\x46\xfa\x46\x18\xef\x78\x66\
+\xbf\x97\xf1\x8e\x50\x09\x89\x05\x2f\x88\xc0\xb3\x46\x0f\xf6\x37\
+\xe5\x15\x92\x88\xb5\x54\x38\xd8\xa7\xcd\x1c\x26\xbc\x18\x1a\xef\
+\x04\xc2\x18\x78\x66\x8c\x90\xe1\x1b\x0e\xc6\x58\x11\x38\x48\xf6\
+\xd6\x11\x62\x6d\x1b\x85\x1a\x98\x3b\x68\x89\xe5\x27\xa4\x36\x8c\
+\x56\x1e\xb5\x33\x71\x7c\x6e\xbb\xcf\xbf\x4a\xf2\x08\xe3\x99\xa2\
+\x29\xb2\x19\x92\x1b\x36\x22\x59\x05\xb3\x78\x68\xae\xf9\xb8\x4e\
+\xb6\x22\xf1\xe9\xa6\x4f\x7e\x51\xb8\xa1\x3c\xec\xb1\x6e\xc1\xb1\
+\x06\xa8\x3c\x10\x12\x6a\xca\x39\x91\x91\x6e\x3d\xbf\x7f\x6c\x9c\
+\xe7\x87\x6d\xf7\x84\x02\xaa\xa4\xca\x6c\x12\xa9\xfc\xfd\x2b\x74\
+\x52\x2b\x88\x63\xe1\x89\x6d\xc9\x94\x99\xf0\x45\x22\x67\x68\xbc\
+\x63\x6b\x33\xa1\x82\xf9\x8a\xd3\xa6\x4d\xb3\x6b\x54\x87\xe3\x89\
+\xc2\x7a\xe1\x68\x4f\x2c\x42\x0d\x6f\x85\xc4\xe2\x80\x10\x8b\x84\
+\x2f\x72\x7f\x98\x0a\x84\x9d\x05\x0e\x10\xb9\x8e\xd1\xc1\xdb\xc6\
+\xd2\xcd\x8c\x63\xdd\xb2\x24\x2e\x86\xc5\x2a\xa2\xbf\x69\xb9\x5b\
+\x56\x26\x65\x11\x49\xd6\xfb\x63\x69\x36\x33\x2c\xcb\x2e\x78\xc4\
+\xda\x34\xd4\x59\x67\x7c\x90\x7a\xe8\xe6\x99\x52\x88\x95\xa2\x4e\
+\x82\xed\xa4\x17\xa3\x34\x94\xb7\x6f\x55\xa6\xc1\xa1\xe9\x71\xe9\
+\xd0\xb5\x1c\xa4\x58\x8f\x60\x88\xc5\xe6\x9a\xab\x06\xc2\x80\xed\
+\xd2\x36\xea\xf7\x5c\x0b\x43\x3a\x24\xf5\x15\x57\xaf\xa1\xcc\x78\
+\xa3\x82\x69\x7d\x63\x56\x82\xdb\x4f\x54\xfc\xf7\x8e\x63\x91\x3a\
+\x83\x60\x21\x1a\xcf\x7c\x43\x82\xe7\x64\x39\xf8\x84\x85\xc0\x33\
+\x3c\xd1\x47\xde\x8f\x55\x50\xeb\xfd\x30\x38\x8a\x08\x84\x58\x4c\
+\x4c\xc5\xa5\x84\xa5\xe8\x56\xb0\x9f\xc8\x75\x82\xfe\xef\x20\x89\
+\xf5\xee\xf1\x45\x83\xcf\x54\x3c\xfe\x51\xab\x88\xce\xd4\x1a\xca\
+\x33\xef\xcc\x35\xb3\xef\x96\x0a\xbb\x57\xa8\x9f\xa7\x12\x8a\x79\
+\xe6\xd3\xe6\x1a\xe3\x6a\x29\xb4\xad\x60\x3e\x6d\x5f\xc1\xcc\xeb\
+\x58\xd1\xce\x74\x59\xff\x6c\x19\x89\x95\x8a\x64\x7a\x9a\x7f\x93\
+\x6d\x46\xd6\xc1\x2b\x57\x64\x9a\xe1\x0a\x52\x76\x0b\x62\x5a\x3d\
+\xca\x91\x68\x1b\xe3\xf9\xbd\x21\x22\x98\xb2\x7a\x7e\xa5\x41\x23\
+\x0a\x0f\xd6\x2e\x1a\x2b\x64\x3e\x1f\x53\xb5\xb6\xf4\x6b\xc9\x92\
+\x25\xf1\xfc\x76\x1f\x29\x40\xb0\x30\x63\x8b\x65\xeb\x5e\x7c\xf1\
+\x45\x1b\x7d\xf7\x29\xec\x41\xf4\xfd\x14\x1f\x79\x3f\x41\xf8\x38\
+\x1c\x27\xc4\x68\x83\x58\xd4\x67\x40\xa7\xc3\x52\xd4\x21\xd8\x5b\
+\x38\x43\xcc\xed\xa4\x55\xe3\xdf\x3f\x31\x22\xd6\xf2\x9a\x51\xd1\
+\xff\xc1\x5a\xed\xbd\x91\x16\xe6\x6e\x5e\xa9\xb2\x69\x11\xa0\x79\
+\x32\x54\xde\xa8\x61\xde\xb6\x66\x94\x56\x7c\x58\x87\xe4\x9a\xb4\
+\x11\xd9\xa2\xa9\x0c\xdc\x69\x51\x07\xac\x96\x87\xf9\xb5\x26\xa9\
+\xbe\x70\x51\xa6\xf5\x2c\xbb\x05\x9e\x65\x79\x90\xac\x54\xcf\x4f\
+\xb1\xb3\x4f\xea\x6f\xa4\xe7\xf7\x5c\xe9\x73\x0e\x9f\x52\x88\x24\
+\x2d\x50\x39\x2c\xcf\xbb\xa5\x5f\xdf\x7e\xfb\xad\x1d\x3e\xf2\xc4\
+\x62\x85\x30\x6c\x6d\x1c\x3b\xb2\x49\xc9\x72\x60\xf9\x14\x1f\x7d\
+\x0f\x32\x63\xce\xf0\xaa\xf0\x74\x7d\x30\x2b\xcc\xc3\xa2\xb8\x07\
+\xc4\x22\x1d\x19\x83\x8d\x13\x20\xb5\xc0\x1e\x22\xd9\x79\x32\xde\
+\xbb\xa6\x65\x9b\x0f\x4f\x8e\x88\xb5\x42\xc1\xd1\x45\x77\x6a\xe0\
+\x75\xcf\xca\xa6\xa7\x3e\x1f\xce\x70\x45\x09\x18\x6e\x9f\xee\x6c\
+\x2d\x23\xb2\xad\xf2\xcf\xf3\x13\x0a\xa6\x6d\x56\x27\x4c\x75\x39\
+\x5c\x02\xe3\x87\x94\xdc\x9e\xac\x82\x68\x78\x7f\x9e\x64\x5b\x82\
+\x60\x38\x00\x0f\xed\x98\x66\xe3\x63\xe6\xf5\x8d\xf4\xfc\x4a\x83\
+\xa4\xde\xff\x64\xfc\x57\xc8\x2d\x32\x92\x7b\xf4\xe8\xb1\xc5\x89\
+\xc5\xcc\x6a\x3f\xfd\xcf\x8f\x15\x62\x12\x61\xe3\x11\xdb\x24\x39\
+\x81\x20\xba\xaf\xf0\xe8\xf9\xa3\xf7\xe7\x7b\x62\x9d\xac\x0f\x66\
+\x86\x79\xee\xc4\x2f\xf0\x0a\xc9\x18\x24\xc4\x80\xf8\xe3\xa0\x60\
+\x4f\x91\xeb\x02\x49\xb1\xae\x19\x8a\xf9\x9c\x54\x44\xac\x9f\x6b\
+\x49\x0d\xed\x23\x69\x94\x5e\xd1\x74\x96\xc7\xc8\x8a\xef\xac\x96\
+\xb5\x01\xd2\xf2\x4c\xe7\xb4\x5c\x4d\xbe\xac\xa8\xb5\x69\xb6\x8d\
+\xa6\xc0\x4f\x2e\x27\x62\x25\x4f\xb1\x9a\x1e\xe5\x70\x2d\x1f\x93\
+\x67\xeb\x89\xe2\x59\x12\x2a\xe8\x52\x8e\x9e\x25\x46\x3a\xde\x22\
+\x36\x9f\x0d\x27\x3c\x57\xce\x90\x44\x5e\xaa\xb0\xcc\x2e\xdb\x16\
+\x0d\xb7\x31\x1d\x6b\x4b\xbf\x88\x53\x85\x1e\xdf\x2e\xaa\xa5\x85\
+\xc4\x62\xb5\x5c\x16\x7c\x82\x58\x78\x87\x10\x2b\x9c\x58\x21\x3e\
+\x9d\xed\x53\x93\xff\x25\x3d\xf9\x51\x38\xe5\x8b\xb0\x3d\x0b\x2c\
+\xa1\x0a\xb1\xb1\x20\x94\xb7\xb1\xf6\x91\x2a\xfc\x8f\xc6\x0a\xbb\
+\x65\xe6\x15\xd9\x58\x94\x7e\x56\xba\xcc\xbb\x57\xe7\x98\x11\xc7\
+\x6f\x63\x57\x7a\x1f\xa9\x45\xb9\x47\x9e\x29\x68\xa9\xdb\x91\x5a\
+\x3c\x72\xe4\x79\x95\xec\x62\x47\xa3\x2e\xa8\x6c\x46\x5e\x58\xd9\
+\x6e\x67\xb1\xe0\xd1\xe4\xbc\x8d\x9b\x38\xb1\x29\x9e\xd5\x8b\x45\
+\x39\x5c\x8c\x19\x92\xdb\x45\x96\x26\xaa\xab\xd3\x26\x7a\x96\xde\
+\x48\x67\x72\x04\xd3\xe3\xcb\x5c\x4d\x66\x13\xae\x9f\x10\xcc\x71\
+\x07\x15\xe5\xbc\xf3\xe0\xaf\x5f\xbf\x7e\x8b\x12\xab\x5f\xbf\x7e\
+\x09\x1e\x1f\x5a\x0b\xe3\x1d\x21\x03\xb1\xd1\x68\x2c\xf0\xe4\x27\
+\xb0\x7a\xfe\x88\x4f\xa7\xfa\x09\xab\x47\x0b\xef\x84\xc4\xc2\x78\
+\x67\x2c\x88\x08\x2b\x63\x53\x10\xca\xc7\xb1\xf0\x0a\x2f\x51\x92\
+\x5f\x37\x15\x49\xc5\x2b\x2c\x0c\xbc\xc2\xd5\xcd\x54\xef\xb2\x9d\
+\xbc\xc2\xae\x19\xc6\xf4\xce\xb4\xab\xbd\xb3\x30\x37\x6b\x28\xb3\
+\xdc\xad\x19\x97\x6d\x17\x91\x24\x0f\x8b\x01\x68\x5b\x44\xff\x99\
+\x4d\x30\xde\x37\xe3\xe9\x8f\x8c\xfe\x48\x6d\x32\xd9\x95\xca\x2e\
+\x64\xab\xf6\xcc\x2c\x32\xfa\xcb\x42\x2a\x24\x1e\xd9\xa2\xbf\x4d\
+\x2e\x07\xcf\xaf\x0c\x0f\x46\xd5\xb3\x12\x07\xa2\x97\x2e\x5d\xba\
+\x45\x89\xc5\x84\x8d\x70\x6e\x21\x1c\xf0\x71\x2c\xe6\x97\x42\x2c\
+\xbc\x42\x5f\x3b\x2d\x98\x67\x78\x92\x2f\x0a\x72\x84\xf0\x6a\x38\
+\xa5\x9e\xa0\x28\xe5\x20\x09\x37\x90\xe7\x4e\xfc\xc2\x0f\xe7\x1c\
+\xa2\x83\x5f\xa4\x19\x3a\x5d\x44\xac\xd7\xfe\x99\x18\x6e\xb0\x33\
+\x73\x5a\x24\xd5\x68\x70\x99\x0d\xbf\x6d\x4a\x1c\xeb\xb9\x2d\xdc\
+\x61\xce\x1e\xe3\x9c\x0c\xeb\xe0\x59\x0e\xdd\x33\x2d\x3e\x58\xdc\
+\xad\x18\xcf\x0f\x10\xad\xf7\x65\x87\xb6\xf8\x75\x8a\xb8\x6d\x6e\
+\x4c\x4c\xf6\x63\x75\xfa\x2d\xf9\x22\x96\x99\x1c\x75\x07\x78\x86\
+\xcc\xd8\xc2\x54\x62\xc1\x08\x38\x03\xf1\x82\x42\x21\xc7\x59\x62\
+\xc9\x7d\x3d\x50\x78\xda\x97\xa6\x41\x5f\x12\xbb\x62\x04\x9b\xe2\
+\xa6\x14\x00\xc1\xce\x22\xe9\x0b\x30\x4e\x78\xc1\x39\xe7\x98\xce\
+\xb9\xdb\x98\x17\x0f\xda\xc2\x01\xd2\xe7\x7e\x6f\x92\x55\x30\xab\
+\x9e\x8a\xca\x24\x3d\xa7\x38\x14\x33\x6b\x3a\x05\xf5\xd3\xad\xf1\
+\x2f\xc9\xf6\x7e\x8d\xec\x8d\x9b\xe4\x51\x0e\x21\x87\x91\x8d\x72\
+\x12\x6c\x1e\x4a\x1e\x6c\xa9\x17\x75\xb3\xc2\xb2\x46\xcc\x31\xb4\
+\x79\x78\xea\x7f\xa4\x16\xc9\x7e\x48\x2c\x24\x17\x9e\x20\x84\x72\
+\xfc\x59\x29\x54\xf1\xf5\xb1\xf6\x10\x06\xfb\x4a\x6d\xec\x88\x84\
+\x22\xdf\x86\xd9\x20\xd8\x5b\x88\x5e\xc8\x06\x8e\x52\x3e\xd6\xb9\
+\x22\x56\xb7\x9d\x76\x33\x13\xf7\x72\x63\x85\xe5\x39\xa4\xf3\xff\
+\x41\xac\x90\x60\xde\xb3\x94\x4d\xb6\x4c\x46\xff\xff\x14\xe9\xa7\
+\xf0\xc7\xa8\x03\xd3\xcd\xb3\x67\x65\xd8\x42\x6d\xbf\x2b\xa9\xdc\
+\x84\x8a\x57\x54\xa3\x2b\x3d\xad\x88\x58\x8c\x8a\x6c\xc9\x89\x14\
+\x3e\x8c\xe0\xc7\x8e\x31\x89\xe8\x7f\xb8\xd0\xbd\x7b\x77\x6b\xbc\
+\x53\x83\xd6\xc7\xd6\x1c\x7f\x7e\x10\xf6\xf5\x35\x48\x77\x10\xba\
+\xf9\xc2\xa5\x88\x35\x0c\x34\x2e\x9c\xb1\x42\xb2\x1b\x48\xee\xf3\
+\xb9\xee\xc7\x09\xa7\x4b\x55\xf6\x3a\xe4\x70\x33\x76\xfb\xf2\x1b\
+\x84\x8e\x1b\xf0\xff\x9f\xc4\x4a\x51\x0c\xcd\x92\xec\x25\x47\xb8\
+\x69\xf9\x45\xf3\x05\x7f\xe7\xeb\x98\xa7\x82\x26\x3b\x54\x2a\x22\
+\x16\xf5\xd8\xb7\xd4\x8b\x2a\x36\xa1\x74\xc4\xb6\x66\x86\x16\xfd\
+\x4f\x6a\x3a\x2b\xb4\xe2\xdc\xb1\xe0\x80\x57\x95\x0e\xf3\x85\x9d\
+\x7d\xd5\xe4\x1c\xa1\x5e\x58\x72\x7b\xb7\xdd\x76\xb3\x1e\x21\xc4\
+\x22\x45\x19\xb6\x62\x6b\x81\x93\x84\xd3\xa4\x1e\xfb\x9d\x7e\x96\
+\x19\xaa\xd8\xca\xd2\x1b\x1d\xb1\x36\x23\x6d\x66\xab\x25\xd6\xd6\
+\x02\xd6\xfe\x51\x3b\x1d\xb4\x47\x51\xc8\x81\x09\xc4\x5b\xea\x85\
+\xed\xe4\xcf\x83\x06\x43\x52\xd9\xb9\xa4\x22\x17\xa6\x11\x13\x2a\
+\x30\x95\xf0\x4e\xc3\x52\xdd\xe2\xd1\xfb\x42\x7e\x58\x8e\xfb\x72\
+\xbf\xa4\x09\x62\x0d\x9d\x4a\x8c\x82\x08\x2f\x7a\x94\x03\x32\x61\
+\x15\x90\x68\x76\x96\xa6\xfe\x3c\x70\xe9\xe5\x66\x80\xd2\x9b\xbf\
+\xf7\xd5\xfb\x36\x31\xd1\x2f\x55\xad\x06\xf3\x37\xb9\x52\xaa\xc3\
+\xf3\x8f\x29\x4a\x9f\xc1\xde\xd9\x12\x59\x0e\x84\x31\xc2\x74\x19\
+\xc6\x26\x21\x95\x2d\xa9\x20\xa1\x02\x99\xc8\x77\xa7\xf4\x02\x5c\
+\x40\xc3\xf9\x55\x2b\xb4\x7d\x2e\x5e\x8e\x9b\x97\x58\x79\x3c\x07\
+\xf0\xe4\x62\x4b\xce\x3b\x9e\x21\x22\xd7\x92\x49\x4c\xf5\x55\x66\
+\xce\xd6\x89\xbb\x5d\x5f\xd5\xd6\x19\x9f\x77\xbe\xd4\x61\xf5\x4d\
+\x4b\x4d\x4e\xa8\xd7\x30\x71\x0b\xc6\xb2\xfe\x24\xc4\x6a\x72\x4d\
+\x56\x42\x24\x9c\xc5\xc2\xcb\xfb\xb5\x62\xc5\x8a\x04\xc3\x9d\x69\
+\x5f\xe7\xc8\xa6\xa6\xff\xe1\x01\xb3\xe3\x99\x6f\x4a\xe5\x65\x0c\
+\x79\x04\x91\x5f\x38\x53\xef\x87\xc6\x53\x93\xf5\x21\x38\x48\x58\
+\x1a\x2e\x19\x47\x74\x95\x22\x10\x14\x5d\xe3\xa0\x1c\xdc\x17\x5b\
+\x3b\x5b\xf5\x47\xdb\xdf\x51\xdd\xf4\x95\x67\xf8\xfe\x09\x31\xbb\
+\xba\xd4\xa6\x4c\xa6\xb0\xea\x70\x6b\x33\xe0\xb7\x56\xc8\xd6\x7b\
+\xac\x59\x62\x9d\x76\x06\x82\xcb\xfb\x45\xf9\xed\x70\x28\x87\x68\
+\x3b\xf5\x66\xe9\x77\xc2\x50\xf0\x02\xc7\x8e\xd4\x75\x48\x87\x0a\
+\x0c\x96\x9e\x6b\x03\x9f\xc2\xc5\xc6\xb7\x15\xe3\x66\xf9\x55\x35\
+\x21\x16\x45\xe3\x9f\x78\xe2\x09\x6b\x6b\x21\xfe\x38\xb0\x2f\x0d\
+\x79\xa6\xde\x37\xd7\xec\xe8\x87\xf7\xdc\xdf\x4c\x3d\x28\x0a\x92\
+\x16\x6c\xc2\xf4\xaf\x3f\x84\x01\xbf\xb5\x40\x4e\xc3\x2c\xcd\xd6\
+\xce\xca\x28\x22\x16\xe6\xca\x96\xae\x8d\x45\x98\x81\xc8\x00\xfd\
+\x8e\x70\x41\x93\x61\x7b\x53\xde\x8a\x29\x61\x49\xcb\xfc\x5e\x1f\
+\x5f\x6c\xdc\x49\x2c\xd6\x7a\x9e\xec\x17\x99\xc6\x75\x24\x40\x86\
+\x77\x80\xc8\x63\x25\x4e\xa6\x54\xfb\xd2\xdb\x9c\xa0\x9e\x82\x64\
+\x43\x8e\x39\xce\x8c\xd5\xda\xd2\x4b\x6f\x12\xb1\x6a\x6c\xfc\x84\
+\xd5\xd0\xce\x2a\x53\x05\xbf\xbf\x32\x28\xd3\xa4\x99\xdd\x47\xee\
+\x5f\x34\xb4\x43\x87\x97\xf7\x8b\x79\x83\x61\xc4\x9d\xbe\xa6\x8e\
+\xbf\xaf\x90\x0d\x99\x21\x16\x19\x0e\x9e\x54\x8e\x37\x2c\x92\x5a\
+\x25\x2e\xb1\xf4\x85\x85\xbe\xe8\x03\x03\x3d\x0b\x09\x39\x20\xee\
+\xa8\xa4\xcb\x41\x10\x87\x7e\x05\x8a\x7f\xeb\x7d\x75\x49\xac\x91\
+\x57\x5d\x63\xfa\xcb\x80\xff\xd1\x97\x30\xda\xc8\x29\xf6\x7f\xdb\
+\x59\x1b\x6b\x67\x55\x30\x37\x9d\x5b\x34\xb4\x83\xf7\x4e\xde\x54\
+\x79\xbd\x70\x06\xc2\xba\x58\x38\x71\xd4\xc2\x02\xcc\xd8\x41\x8b\
+\xf9\x68\x01\x35\xe0\xb1\xaf\x3c\x67\x84\xef\xc4\xa3\xed\xe1\x92\
+\x25\x56\xb0\xac\xfd\x1d\x0c\x15\x00\x76\xe4\xa0\x0c\x32\x32\x5b\
+\x87\x54\x54\x0e\xce\xc1\x38\x38\x69\x35\xd7\x48\x8a\x8d\x6b\xde\
+\xd2\x4e\x5a\x65\x45\x7a\x6b\xc0\x6f\x64\x51\x90\x84\xba\xa3\x29\
+\x8a\xd9\xfe\x4d\xa6\xe4\xd2\xdd\x15\x4c\xdf\xa0\x40\x08\x12\x85\
+\x14\xe1\xf2\x7a\x7d\xfd\xf5\xd7\xb6\xff\x43\xfb\x8a\x15\xc1\xe8\
+\x73\xfa\x9f\x6a\x33\x68\x30\x46\x64\x88\x14\xa0\xd9\x3c\x67\x84\
+\x97\x84\x2c\xb8\x64\x89\x25\x02\x79\x1c\xae\x64\xae\x75\x24\x74\
+\xb1\x23\x22\x8e\xbc\x1b\x88\x45\xfd\xc9\x70\x3d\x3b\x70\x99\xf0\
+\x48\xef\xde\xe6\x81\xca\xdb\x9b\x97\x0e\x75\x06\xfc\x26\x94\x31\
+\xfa\x5b\x1d\x6e\x5c\x6e\xd6\x07\x9a\x92\x9f\x11\xd8\x59\xd8\x3a\
+\xe5\xf5\xc2\x59\x0b\xed\x2b\xcc\xa1\xeb\xb5\xa6\x21\xfd\x0d\xb9\
+\x1a\x34\x68\x60\x3d\x42\x66\xe9\x90\xfc\x89\x0a\x84\x2f\x40\xfc\
+\xe9\xe1\xb9\x64\x89\x25\xb7\xd5\xa3\xa2\x3e\x9c\x8b\xa4\x02\x58\
+\xfb\x18\x6c\xa8\x42\x52\x94\x29\x1e\x4f\xd5\x19\xe6\x92\x81\x2b\
+\x74\xb2\x9e\x3d\x7b\x9a\x51\xa7\x9d\x65\x46\x4a\xad\x2e\xb1\x76\
+\xd6\xa6\x15\x5e\x2b\x76\xc1\x80\xbf\x89\xb5\x41\xa0\x74\xa5\x0a\
+\xff\x1e\x1c\x04\x4a\xb1\x7b\xca\x6b\x3a\x18\x2b\x7e\x85\x55\xfc\
+\xb0\xa7\xed\x82\xf1\x2c\xc3\xac\xfe\xf6\x1a\x8c\xac\x06\x04\x8f\
+\x23\x94\x85\xf8\x73\xbd\xe7\x52\xb2\xc4\xe2\xc3\xb1\x7e\x47\x7e\
+\xc8\xe0\x33\x51\x56\x8c\x78\x12\xe8\x39\x41\xd5\xaa\x55\x2d\x20\
+\x59\xd3\x76\xed\xcc\xa4\x3a\x75\xed\x62\x8c\x76\xe1\x80\x1a\x9b\
+\x57\x2a\x72\x93\x0a\xdb\xfe\x95\xe0\x66\x2d\x55\xff\x77\x66\x42\
+\xca\x70\x79\x4c\xae\x60\xda\x3e\x11\xf6\x30\x4e\x86\xd3\x46\x5f\
+\x23\xb5\x10\x2c\xe4\x68\x11\x18\x65\x28\x07\x3b\xdc\x73\x45\x58\
+\x26\x1c\x92\x20\xb1\xc2\x6a\xb7\x92\x52\xf7\xee\xb8\xe3\x8e\xf6\
+\xa0\xfc\x80\x8b\x26\x61\x9e\x83\x21\x72\x39\xc9\xcd\x37\xdf\x6c\
+\xc1\xfb\x3b\xc5\xf0\x29\x32\xf0\x7b\x66\xa9\xe6\xc1\x31\x51\xd2\
+\xdf\xff\x5b\x71\xdb\xbf\x90\x9d\x35\xa2\x7e\x62\x3c\x0b\x15\xb6\
+\xb9\x2f\xca\x7b\xa3\xda\xfc\x31\x11\x2a\xe4\x64\xd1\xd7\x10\x0c\
+\x69\x46\xc4\x1d\x21\xc3\x98\x21\x82\x07\x9e\xc0\x17\xf1\xe4\x43\
+\xf1\x27\x3d\xa1\x6a\xb2\x0f\x37\x00\x19\xed\x47\x69\xc7\x15\xec\
+\x0c\xf8\x31\x07\x46\xaf\xc2\x56\x6c\x2e\x0c\xb8\xdb\x6f\xbf\xdd\
+\x8e\x21\xde\x28\xe6\x3e\xae\x0c\x88\x11\xc7\x9e\x60\x46\xa9\x00\
+\xdb\xf2\xb0\x4e\xd6\xef\x59\x8e\xfb\xaf\x04\x0d\x86\xcf\xd5\x32\
+\x77\x95\x2b\x94\x6f\xaa\x32\x46\x79\x48\x56\xc2\x4b\x04\x43\xe9\
+\x6b\x38\x80\xfa\xa3\x66\x03\xf9\xf6\x44\x0c\xb0\xc3\x3d\x4f\x44\
+\xb0\x2e\x21\x8f\x2c\xb1\x44\xa6\x10\xe9\xda\xe9\x63\xff\x03\xbc\
+\x43\xac\x7f\x4e\x4a\x41\xd3\xfa\xf5\xeb\x5b\x16\x33\xb5\x1a\xdc\
+\x22\x92\xf5\x7e\xe8\x21\xf3\x5c\xe3\x26\x36\x09\xee\xcb\x8b\xe5\
+\x1d\xd6\xf8\x9d\x17\x10\xf8\xab\xc1\x49\xf0\xe3\x0e\x2c\x8a\x67\
+\x91\x71\xb0\xb9\x35\xb3\x50\x77\x61\x9d\x06\x34\x12\xab\x50\xf8\
+\xbe\xee\xd3\xa7\x8f\xd5\x5c\x94\x0d\x85\x17\x9e\x23\x40\x24\xbb\
+\x20\xe4\x91\x25\x96\xbe\x48\x80\x6a\xbb\xf7\xa0\xbe\x3b\x40\xd4\
+\x31\x6e\xd4\xb9\x73\x67\xeb\x62\x32\xdd\x87\x93\x70\x42\x40\x3e\
+\x4e\x3d\x45\x62\xdf\xd2\x60\x75\xcf\xfc\xca\x66\xda\xa1\xbf\xf3\
+\x92\x27\x7f\x31\xf8\x35\xa3\x91\x5a\x4d\xae\xce\x4a\x20\xc2\xcc\
+\x99\x33\x37\x99\x54\xbf\xfe\xfa\xab\x5d\x95\xd5\x1f\x8f\xd9\x58\
+\x48\x41\x82\xa5\x48\x2d\xec\x6b\x1c\x38\x54\x21\x23\x30\x10\xcb\
+\x73\x44\x58\x28\xde\xec\x1c\x72\xc8\x12\x4b\xb3\x2f\x92\x71\x96\
+\xaa\xcd\xac\xa3\xe2\x0c\x40\xef\x22\xa5\x20\x16\xd3\xc1\x60\x2c\
+\x01\x53\x4e\xcc\xf6\x4e\x11\xec\x59\x55\xa5\x79\xe2\x3f\x97\x98\
+\x01\x99\x6e\xd9\x93\xea\xbf\xc3\x22\x4d\x7f\x51\x52\xad\x77\xc5\
+\x53\xa6\x75\xca\x4d\x50\x5d\x04\x2e\x37\xf5\x45\x0a\x4c\x58\x74\
+\x8f\x4c\x86\xfb\xee\xbb\x2f\x4e\xae\x76\x72\xd2\xd0\x5a\x84\x9d\
+\x08\x33\x20\xa5\x3c\x3f\x84\x31\xc9\x1c\xb2\xc4\xd2\x40\x62\x32\
+\xf2\x85\x59\x4c\xf7\x01\x18\xf1\xc4\x33\x60\x2c\xe3\x48\x04\x4b\
+\x31\xe4\x98\x0d\x0b\x38\x71\x1f\xdd\xd4\x8c\x61\xc3\x6d\x0a\xef\
+\x7b\x27\xb9\x98\xd6\x96\x5c\x56\xee\x2f\x4c\x2a\x5b\xff\x5e\x9f\
+\xfd\xa0\x59\xe4\xfb\xec\x5c\x44\x06\x22\xe3\xe5\x11\x66\x60\x9c\
+\x18\x0f\x10\x62\xd1\xbf\x14\xfe\x20\x6e\x85\x60\x41\x43\xa1\xc5\
+\x3c\x37\x80\xb8\xf2\xdf\x64\x0e\x59\x62\x69\x58\x60\x03\xe8\xcb\
+\x3e\x8c\x5c\x03\x58\x89\x98\x24\x7d\x86\xa2\xf2\x64\x3b\x90\xa3\
+\x85\x78\x04\x5c\x40\x03\xa9\xc3\xf7\xb4\xde\xce\x90\xc3\x8f\x34\
+\x8f\x54\xf2\x63\x87\x5b\x70\x21\xcc\xbf\x3a\xb1\x28\xfb\xf4\x7c\
+\x9e\xb9\x2a\xa8\x9b\xc5\xf0\xce\xa2\x45\x8b\x36\x9a\x54\xac\xce\
+\x8a\x07\x18\x16\x1d\xa1\x4f\xb1\xa7\x21\x55\xcb\x96\x2d\x6d\x1d\
+\x5a\x66\x6c\x31\xdc\x83\xa0\xf1\xdc\x10\xbe\xd5\x79\x77\x4a\xe6\
+\x8f\x25\x96\x26\x20\x6e\x00\x1d\xfc\x04\xfd\x68\x9d\x3f\x00\x61\
+\x07\x22\xaf\x88\x43\xe2\x5a\xac\x50\x40\x14\x96\x65\x31\x00\x17\
+\xf0\xb8\xea\x69\xbd\x29\xd2\x31\xe1\x60\x96\xea\x92\x16\xd6\xd8\
+\x32\x4b\xf7\xfe\xd5\xc8\x95\x4c\x2a\xda\x82\x36\x31\xcf\xe5\x99\
+\x41\xc1\xf0\x0e\xaa\x0c\x95\xb6\xb1\x2f\x16\xe0\x0a\x55\x2a\x6a\
+\x90\xfe\xa5\x5f\xe9\x63\x04\x09\xb6\x15\x13\x28\x20\xaf\x93\x52\
+\x16\x12\x38\x0f\xa7\xe2\x4f\x71\x36\x56\x4c\x52\x2a\x53\xdb\x57\
+\x39\x10\x40\x6a\x31\x59\x91\x48\x3b\xea\x90\xa9\xd5\xa4\x4f\x50\
+\xc6\x86\x1a\xa5\x5c\x44\x47\x8d\x7a\xcf\x56\xb1\x8a\x87\xf7\x3b\
+\xc8\x8c\x52\x8c\x6c\xd9\x2d\x49\x52\xab\x1c\x16\x1b\xff\xab\x91\
+\xab\x38\x52\xd1\x26\x54\xea\xf9\xac\x5f\x8e\xc9\xcb\x2e\x52\x87\
+\x3c\xe0\x1b\xfb\xc2\xb4\x09\xd5\x20\xc1\x4f\xa4\x14\xfd\xcb\x3a\
+\xe0\xd8\x6e\x10\x0b\xc1\x82\x1a\xf4\x9c\x10\x7e\x13\x2f\x4e\x4b\
+\xc5\x9f\x84\x05\x04\x92\x5f\xb2\xfc\x6b\x33\x9d\x1a\x35\x08\xf0\
+\x00\x30\xe2\x39\x09\x46\x3c\x27\x85\xd9\x5c\x18\x68\xa4\x0b\x99\
+\xaa\x2a\xcb\x6f\xf4\x88\xa4\xd6\xcc\x33\x64\x6b\xd5\x70\xb6\x56\
+\x2a\x0f\xb1\x7b\x10\x8d\x1f\x10\x4c\xb6\x18\x95\x94\x61\x9a\xec\
+\x25\xfe\x45\xc8\xb5\x01\xa9\x04\xda\xc0\xb6\x05\x0b\x5a\xa9\x1c\
+\xd4\x2a\x3d\x84\x67\x1c\x96\x9e\xb0\xae\xe0\xc6\xd4\x80\x27\xda\
+\x4e\xba\xb1\xff\x3d\x46\x39\xf9\xee\x10\x8b\x2d\x31\x2b\x04\x09\
+\x52\x8b\x63\x7b\xb3\x08\x48\x62\x51\xfa\x2a\x23\x15\x77\x2c\xb1\
+\xb4\x73\x4a\xe8\x87\x3b\x4b\x25\x7e\x8f\xce\x05\x1c\x94\xa4\x2f\
+\x48\x85\xbe\xa5\x4c\x37\xe4\xc2\xf6\x02\x90\xab\x8f\x8c\xbc\x2f\
+\xe6\xcd\x33\x0f\x1f\x70\x88\x19\xbe\x8d\xb3\xb5\x12\x3c\xc4\x28\
+\xae\x85\xad\x65\xa5\x96\x9f\x6c\xe1\x53\x6a\x86\x04\x89\x80\x63\
+\x03\x95\xe8\x87\x7b\xc2\xf8\xd6\x9f\x98\x5c\xa9\x24\x15\xf7\xee\
+\x49\x45\x9b\xd0\x36\x46\xff\xb7\xbb\x3a\x33\x61\xe5\x79\x4a\xa8\
+\x6f\x4c\xb4\x9d\xa1\x99\x70\xd0\x99\x11\x16\xfa\x15\x4f\x90\xda\
+\x1d\xc4\x2f\x09\x86\x43\x3a\xcf\x05\x20\x72\xd5\x28\x8e\x3b\x96\
+\x58\xda\x29\x25\xf4\x43\xb6\x1d\x91\x5a\x80\x83\xa1\x5f\x11\xb7\
+\xb0\x98\x48\x3c\x83\x92\x5c\x00\xc3\x3e\x6c\x21\xd8\x74\xe9\xec\
+\xf7\x87\x0e\xb3\x13\x3d\x67\x68\x71\x81\xc2\xea\xc1\x84\xd6\x30\
+\x1a\xdf\x21\x9a\xd4\x1a\x1f\xea\xe9\x1f\xe4\x6b\x25\xd9\x5b\x1b\
+\x48\xae\x3f\x31\xb9\x52\xaa\xbf\x40\x52\xd1\x16\xb6\x4d\x58\x80\
+\x41\x1e\xf4\x9b\xad\x35\x0b\x39\x58\x19\x0c\xd3\xa4\xac\x2f\x84\
+\x44\x68\x5f\x31\xaa\x82\x99\x43\x5f\x22\xa5\xc8\xc7\x63\xaa\x17\
+\x13\x37\xb0\xa9\x3c\x17\x84\x2f\xc5\x8f\xed\x8a\xe3\x4e\xb1\xc6\
+\x7b\x80\x83\x84\x65\x04\x49\x01\x07\x27\x12\xcf\x05\xa1\x7b\xb1\
+\xb9\x60\x38\x81\x53\x00\xc1\x1e\x54\x24\x7e\xa1\x6a\x2b\x8d\x3e\
+\xf3\x6c\xf3\x80\xd2\x3b\xbe\xbf\xaa\x28\x1a\x9f\x32\xfc\x10\xa8\
+\xc4\xd5\xa1\x97\xf8\xc8\xc6\x93\xeb\x8f\x4c\x30\x7f\xfd\x65\x25\
+\x15\x6d\xb3\x4e\x36\xe9\x6a\x49\xf9\xc3\xf7\x28\x52\x87\x94\xf6\
+\x2c\x6b\x14\x3e\xac\x31\x8a\xb7\x87\x0a\x84\x58\x64\x89\xd2\xc7\
+\x78\x83\x0c\xe3\x79\x52\x79\x1e\x08\x6d\x4a\xe2\x8d\x49\xb1\x26\
+\xf4\x06\xd0\x41\x47\x50\x69\x84\x03\x7a\x9b\x8b\x20\x29\x71\x2d\
+\xa4\x16\x2a\x91\x8b\x01\xbc\x87\x68\xaf\x68\x15\xce\x79\x2a\x25\
+\xd8\x2d\x3b\xd7\x3c\xbb\x8f\xcb\x89\x4f\x0e\x3f\xb4\x74\x86\x7c\
+\xe7\xac\x22\x2f\xd1\x67\x3f\x78\x72\xf9\xd4\x9a\xd0\x98\x4f\xce\
+\x82\x08\xc9\xf5\x07\x95\x5e\x71\x42\x25\xd9\x54\x96\x58\x5e\xfd\
+\x3d\x99\x17\x27\x15\x6d\x62\xdb\x46\x6d\x64\x24\xdd\x1b\x5e\x50\
+\x14\x76\x60\x22\x44\x59\xa2\xf0\x24\xf5\x61\x8c\xfb\xdf\x51\x55\
+\x88\x61\x1b\xfa\xb1\xb7\xf2\xec\xf0\xfe\xd9\xf2\x39\xc6\xba\x27\
+\x95\xb8\xb0\x58\x91\xfe\xbd\x4b\xe2\x4c\x89\xc6\xbb\x7f\x29\xe3\
+\xe1\x70\x45\x5b\x0b\x21\x17\xe0\x24\x8c\x6e\x73\x11\x0f\x49\x3a\
+\xb1\x45\x6c\x7a\x40\xae\x7e\x62\xfb\xd7\x3f\xfc\x60\xa6\xd6\x6f\
+\x60\x0d\xf9\x4f\xb4\xba\xfd\xca\x3b\x9d\x21\x5f\xc7\xe5\xc5\x37\
+\x75\xe9\xcb\xa1\xbd\x15\x86\x20\xbc\x31\x9f\xe4\x29\x96\x28\xb9\
+\xfe\x80\xaa\x31\x41\x4a\x95\x41\x52\xd1\x16\xb4\x09\x6d\x43\x1b\
+\x99\xe1\xd9\xe6\xf9\x3a\x99\x09\xd3\xef\xd1\x24\x1b\xb3\xd0\x25\
+\xc0\x1b\x44\x50\xf0\x5b\xa4\x15\x82\x03\xd5\x88\xb4\xf2\x7d\xcf\
+\xe0\xb3\xcc\xa1\xee\x64\x30\x94\xf4\x2a\x13\xb1\x58\x74\x47\x64\
+\x1a\x44\x28\x9f\x03\x7b\x72\x31\xea\x8d\x0e\xe6\x62\x60\x76\x08\
+\xc8\xf5\xac\x8a\x46\xfc\xf0\xcd\x37\x66\x50\x95\x23\xcd\x00\xd5\
+\xa1\xfa\x41\x79\xf1\xcb\xdd\x34\x31\xab\x12\x1b\x05\xb1\xad\x0e\
+\x89\xc3\x3d\xd6\xde\xf2\xf1\x2d\xef\x29\x16\x47\xae\x09\x25\x4b\
+\xaf\xad\x91\x64\x09\x6a\x2f\x95\x94\x9a\x50\x02\xa9\x46\x39\x52\
+\xa9\x6d\x68\xa3\x75\xc2\x4f\xdd\xb3\xcd\x01\x3b\x15\x85\x1d\x98\
+\xae\x57\xda\x8b\x54\x98\xb0\x9a\x0c\x76\x15\x11\x76\x6c\x2a\xcc\
+\x1c\xd4\x21\xf3\x06\xb1\xad\xe9\x77\x87\x1f\xa9\xf5\x51\x1a\x67\
+\xca\x44\x2c\x5e\xd2\xbf\x87\xaa\xf8\xda\x72\x7f\x02\x54\x22\xd1\
+\x5a\xc4\x26\x52\x8b\x0b\x0a\x81\x14\xeb\xab\xed\x6c\x4d\xa8\x9c\
+\xab\x5a\x95\x5d\xb3\xf3\xcc\xd3\x7b\x44\x75\x1e\xac\x4a\x94\x97\
+\x58\x70\x5f\x48\xae\x4c\x53\xd8\xd1\xe5\xc7\x13\x95\xef\x5b\x8c\
+\xe4\x0a\xd5\xa2\xf7\x16\xc3\x38\xd7\xc4\x3f\x06\xb9\x52\x92\x6a\
+\x62\x62\x9c\x2a\xf4\xfe\x42\xf5\xe7\x25\x15\x6d\x43\x1b\xd1\x56\
+\x46\xb6\x69\xd5\x63\xd3\x12\x92\xf4\x16\x2c\x58\x50\x2c\xa9\x58\
+\xef\x10\x49\x14\xce\xaa\x46\x40\x00\xfa\x0f\x35\x48\xcd\x0e\x04\
+\x88\x17\x28\x40\xaa\xb0\x5d\x59\xf8\x52\x16\xe3\xdd\x42\x07\x8f\
+\x89\x58\xdd\x38\x89\x07\xb6\x16\x27\xe7\x22\xb8\x18\xc4\x27\xee\
+\xa9\x07\x9f\x51\x58\xe4\xa7\x82\x02\xf3\x72\xcb\x56\x76\x11\xa6\
+\x04\x2f\xd1\xe7\x6c\x31\x48\xdd\x22\xc9\x98\xf7\xe4\x4a\x96\x5c\
+\x23\x53\x18\xf4\x41\x10\x35\x41\x35\x6e\x65\x04\x2b\x55\x4a\x3d\
+\x93\x57\x44\xaa\x27\x37\x34\xd4\xe3\xa4\x72\x92\x8a\xb6\xb1\x6d\
+\xa4\xb6\x32\x7d\xb2\xcc\xa8\xeb\x33\x12\x54\x5b\x49\x51\x78\x8a\
+\xea\x85\xfb\x92\x32\x43\x58\x81\x3e\x43\x5a\x11\x9f\xa4\xc0\x1a\
+\xda\x29\xe8\xf3\x05\xe2\xc0\x4e\x22\x57\xa9\x7c\xb1\xc4\xd2\xce\
+\xa5\x42\x69\x14\x90\x6b\x07\x6d\xbf\xf0\x45\x6e\x7d\x5d\x52\x22\
+\xb4\x9e\x48\x90\x0c\xf5\xe8\x01\xd9\xa6\x69\x6d\xbb\x25\xaa\x3e\
+\xf7\xd8\xbf\x2f\xb2\x05\xcb\xe6\x9e\xe7\x02\xa7\x61\x08\xa2\x89\
+\x8b\x6f\x39\x72\xad\xec\x1e\x84\x21\x42\xc9\x15\x7a\x8b\x3e\x88\
+\x9a\x42\x7a\xa5\x24\xd8\xff\x03\xc9\x52\x92\x29\x89\x50\xc5\x4a\
+\xa9\x71\x4e\x4a\x8d\x76\xf7\x3c\x6c\x43\x49\xc5\xe8\x05\x6d\xb5\
+\xbe\x5b\xa6\x99\x57\x3f\xd3\x6c\x1b\x14\xc0\x45\xa3\xe0\x64\x85\
+\x60\x50\x99\x01\xe7\x70\x6c\x90\x94\x1b\x3c\x7a\x2f\x20\xd0\x36\
+\x8c\x09\xa2\x95\x7c\x5f\x03\xf5\xff\xad\x4e\xc0\x94\x8a\x32\x13\
+\xcb\x43\x2c\xae\xe6\x8b\xdc\xda\x42\xb7\xf2\x12\x70\x6f\x31\xf8\
+\xbc\xd4\x22\xc6\x85\x9b\x0a\x30\x00\xc1\x9c\xcf\x3e\x33\x8b\xa4\
+\x16\x07\xec\x7f\x90\xe9\x9f\x13\x15\x12\x29\x14\xb1\xe2\x51\x79\
+\x19\xf3\xcb\x9d\xa7\xb8\xd2\x79\x8a\x2b\x53\x49\x2e\xef\x2d\x86\
+\x41\xd4\x50\x35\x7a\xe9\x95\x1c\x4c\x2d\x85\x60\x96\x64\x53\xca\
+\x2f\x09\xaf\x54\x42\x85\x41\xcf\x40\x4a\x25\xab\x3e\xee\xd1\xc6\
+\xf4\x20\xd5\xa0\x0d\x25\x15\x6d\xc4\xa4\xe0\x35\x72\x80\x68\xbb\
+\xb3\xf7\x4d\x4b\x5e\x82\xa4\x54\x50\xa2\x88\x3e\xc2\xb6\xc2\xac\
+\xc1\x60\xa7\x5f\xc3\x7e\x16\x5e\x17\x72\xca\xca\x13\x4b\x2c\xd5\
+\x96\x2c\x33\x34\xcf\x2c\x4b\xe4\x9a\xe6\x6b\xbe\x7b\x71\xc9\x4c\
+\x0e\xa4\x16\x86\x3b\x92\x8a\xc1\x6a\x86\x7e\x00\x44\xa3\x80\xdb\
+\x0f\x4a\x26\xfb\x4c\xcb\xd4\xdd\x9f\x5f\xc9\x3c\xa2\x55\xc3\x16\
+\x57\x75\x93\x2f\x98\x8b\x58\x37\x3d\xf2\x14\x5d\x16\x84\x25\x57\
+\xa8\x16\x1f\x08\x24\xd7\xa0\x9c\xa2\x20\x6a\x68\xd4\xa7\x92\x5e\
+\xa1\xe7\x98\x4a\x82\x85\x24\x4b\x22\x5a\x32\x36\x90\x42\xa9\x30\
+\x39\x05\x99\x52\x49\x28\xef\xf1\xa5\x92\x52\xa1\x91\x3e\x22\xba\
+\xd7\x38\xa9\x1e\x74\x6d\xa1\x36\x59\x65\x49\x95\x69\xdb\x0a\x52\
+\x19\x39\x41\xed\xce\xd8\x78\x62\x91\x29\xea\xb5\x0b\x11\x77\x0c\
+\xf6\xb0\xae\xbf\xb0\x4a\xfd\x7e\xda\xc6\xf0\xc4\x12\x4b\x2a\x6d\
+\xa3\xa0\x93\x1e\xa9\x93\x15\xf8\x13\xc3\x68\x26\x36\x32\x12\xce\
+\xc5\x41\x2e\x88\x45\x7e\x34\x39\x3c\x00\x82\x51\x01\x6e\xa9\xc6\
+\xa6\xfe\x37\x7a\x8c\xf2\xb6\xb4\xe8\xd1\x1e\xd1\x94\xb1\xe5\x61\
+\x16\x44\x83\x20\xc6\xe5\xd5\x62\x37\xa7\x16\xfb\x04\x41\xd4\x81\
+\x29\xec\xae\xb1\x01\xc1\x7c\x4e\x57\x20\xc1\x12\x54\x64\x71\x92\
+\x2c\x15\xd9\xca\x82\x49\xc5\x10\x29\x99\x4c\x13\x02\x42\xb9\xeb\
+\xe3\x5a\xe3\x84\x1a\x9b\xc2\x9e\x1a\x18\xdd\xb3\xbd\x77\xb5\x01\
+\x6d\x41\x9b\xd0\x36\xb4\x11\x6d\x45\x9b\xad\x92\x23\xf4\x9d\x82\
+\xd0\x97\x69\x8d\xee\x7d\x95\x1d\xbc\x93\xc2\x49\x95\x45\x9c\x3d\
+\x34\x14\xb7\xaf\xfa\x08\x43\xdc\x67\x7d\x32\x3c\xc3\xd6\x6b\x1b\
+\x8c\x76\x92\xf8\xc8\x6c\x40\x50\x84\x82\x43\x6a\xb0\xcb\xc6\x72\
+\xc4\x12\x4b\x07\xda\x28\xf0\x43\x11\xa9\x39\x64\xf2\xc0\xde\xa2\
+\x38\x1b\x2e\x2a\x3a\x9a\x0b\xc5\x70\x27\x47\x9a\x89\x18\x00\x82\
+\x51\x82\xb0\x50\xf5\x97\xde\xd4\xe0\x66\x07\xdd\xf4\x84\x7d\xa2\
+\x2c\x08\xc8\xb5\xac\x56\xa2\x5a\xf4\x36\x97\xf5\x16\xbb\x66\x16\
+\xc5\xb9\xfa\x05\xc3\x3f\x83\x5d\x56\x84\x93\x5e\xc9\xb6\x97\xed\
+\x34\x1f\x9a\x18\x9f\x48\xb0\x04\x92\x25\x13\xed\xd9\x52\x88\x57\
+\xd2\xbe\x13\x93\xc8\x14\x12\x6a\x7c\x10\x42\x78\x3c\x77\x43\x5b\
+\xca\x4b\x29\x6f\x4f\x0d\x74\xaa\xcf\x93\x4a\x6d\x40\x5b\xd0\x26\
+\xb4\x0d\x6d\x44\x5b\xd1\x66\xb4\x5d\xa1\x02\xd0\xe6\xee\x98\xf9\
+\x50\x09\x00\x5d\x95\xf5\x30\xf0\xe4\x53\xcd\x4c\xd5\x2a\x9d\xf1\
+\xce\x3b\xd6\x86\x42\xd5\xa1\xf6\x98\x69\xe3\x4b\x11\xf9\xfe\xc2\
+\x80\x87\x54\x61\xbf\x0a\x1f\x8b\x58\x95\x36\x96\x23\x96\x58\xfa\
+\xf1\xa6\x20\x57\x22\xef\x15\x5f\xfb\x1d\x40\x2e\xb2\x18\xb9\x50\
+\x9e\x02\x6e\x84\x6a\x35\x4c\x70\x04\xa8\x43\x26\xbf\x7e\xa2\xa8\
+\xf0\x0a\x49\xae\x57\x34\x7c\x00\xb9\xa6\xe8\x09\x2b\xb8\xad\x88\
+\x5c\xa8\xc5\x02\x2f\xb9\xe4\x2d\xae\x20\x1b\xa2\x43\x10\x44\xed\
+\x1d\x84\x23\x06\xb8\x94\x9b\x21\x81\x61\x1f\xaa\xc7\x47\x8b\x08\
+\x16\xb7\xc1\xc6\x27\x49\xb1\x09\x29\x48\x36\xb1\x88\x24\x76\x1d\
+\xc5\x17\x02\x3c\x97\x44\xc4\x89\x29\xc8\x34\x21\x49\x3a\x8d\x0f\
+\x6c\xa8\xc7\x83\xb8\x54\xa8\xf6\xbc\x81\x6e\xa5\x54\x8e\xbd\x37\
+\x6f\xa4\x73\xcf\x96\x54\x6a\x03\xda\x82\x36\xb1\x6d\xa3\x36\x2a\
+\xb0\xa4\x4a\x37\x6b\xe4\x65\xaf\xaf\xa9\x0c\x5e\x39\x47\x77\xc8\
+\x8e\x3d\x35\x33\xdb\x9c\xa4\x12\x9f\x07\xaa\x9e\x2c\xe1\x07\x72\
+\xea\x20\x0e\x73\x07\xa9\x06\xc8\x98\x22\x01\x6d\xbc\x40\x12\x37\
+\xa9\x86\x8c\x84\x0a\xfa\x74\x95\x70\xe6\xa6\xf0\xc3\x12\x4b\x07\
+\xdb\x24\xe8\x00\x55\x74\xe2\x9f\xfd\xaa\x60\x5c\x0c\xde\x03\x69\
+\xad\x18\xf2\x5c\x34\x2e\x2c\x55\x01\x29\x22\x01\x20\x1a\xdb\xcf\
+\x15\x63\x29\xd4\xec\xdd\x97\x9b\x34\xb5\x61\x88\x67\x45\xae\xa5\
+\x48\xae\x3b\x03\xb5\xe8\x42\x11\x36\xce\xd5\x3a\x88\xd0\x87\x1e\
+\xa3\x57\x8d\xa1\xed\x35\x3c\xf0\x1c\xc7\xe4\x26\xaa\xc8\x50\x8a\
+\x85\x24\xf3\x44\x4b\x26\x9b\xea\xd0\xaf\x7c\x2c\xdf\x3c\x71\x79\
+\x25\xd3\xfd\xf0\xed\x4c\xbf\xe3\xb7\x35\xf3\xbb\x6b\x21\x82\x29\
+\x79\x89\xd2\x68\x42\xf0\xfb\xa7\x12\xc9\xb4\x81\x74\xf2\x86\xf9\
+\x98\xc0\xe3\x1b\xee\x6c\x29\xef\xf5\x79\xd5\xd7\x37\x52\x7d\xd6\
+\x9e\x82\x54\x18\xe9\x6a\x0b\xda\x84\xb6\x29\x50\x1b\x2d\x57\x5b\
+\x19\x79\xd7\x9f\xcb\x66\x6d\x78\x78\xa4\x02\xcb\x6a\x5f\xe1\xf9\
+\x11\x32\x22\x6b\x05\x09\xe5\xfb\xd2\xf5\x67\x1b\x48\xb2\x29\xdc\
+\xb0\xc4\xc2\x85\xdc\x54\xe8\x02\x6e\xf5\xeb\x18\x02\xc8\xc5\x16\
+\xb7\x16\x2f\x03\x72\x61\x6b\xb1\x5a\x2b\x93\x2a\x01\xf1\x15\xfe\
+\xff\x6a\xe1\x42\xb3\x72\xdd\x3a\x49\xae\x56\x56\x72\x3d\x25\x09\
+\xfa\xab\x1a\x67\x85\x27\x97\x0b\x45\xc4\x83\xa8\x3e\x49\xb0\x93\
+\xcb\x8a\x48\x96\x5e\xc5\x11\x6c\x94\x9b\x71\x1d\x12\x2c\x24\x59\
+\x48\xb4\x24\xb2\xb1\x6a\xc6\xbc\xce\x15\xed\x82\x52\x5d\x62\x39\
+\xa6\x71\x6c\x1b\x33\x4a\xab\x6a\x90\xbd\x19\x27\x50\x28\x91\x42\
+\x22\x85\x64\x0a\x6d\xa8\x31\x39\x45\x12\x6a\x78\xa2\x71\xce\x3d\
+\x6c\x20\xa5\xba\x3a\x7b\x0a\x23\xbd\x55\x44\x2a\xda\x84\xb6\x59\
+\x29\x52\xad\x51\xb0\x79\xa0\x86\xcc\xf6\xa8\x10\xdb\x68\xc3\x3d\
+\x2c\x2e\x02\xc9\xf0\x10\x5d\x5f\x4e\x95\xed\x9c\xbd\xa9\xbc\x28\
+\x0f\x62\xc5\x74\x11\xfd\x29\xdf\xcd\x05\xb1\x85\xf9\x0c\x5c\x12\
+\x64\xc3\x90\x87\x5c\xa8\x41\xea\x82\x43\x28\x4f\x32\x8c\xf9\x6f\
+\xbe\xfb\xce\xac\x12\xb9\x5e\x23\x43\x42\xeb\x1f\x8e\xd1\xcc\xa1\
+\x1f\xaf\x0d\x42\x11\xf7\x04\x11\xfa\xa6\xe9\x45\x46\x7d\x87\x40\
+\x7a\xf5\x72\xb6\xd7\x03\x49\xea\x91\xb8\xd7\x50\x37\xe3\xfa\x11\
+\x17\xb9\x1f\x1d\x49\x8a\x94\x92\x2c\x20\x9a\x2d\x15\xfe\x44\xb4\
+\x72\xc6\x9c\xf6\x15\x4d\xb3\x8a\x95\xcd\x30\xd6\xcd\xd1\x5a\x40\
+\x8f\x68\xa9\x16\xbb\xa2\xc6\x13\x0e\x8f\xe7\x25\x12\x29\x85\x64\
+\xb2\x18\xed\x22\xe7\x8f\xb8\x6b\xf2\x12\x2a\x54\x7b\x0f\x38\x5b\
+\xaa\x57\x20\xa5\x3a\x04\x46\x7a\xd3\xe8\x41\xa3\x4d\x20\xd5\x0a\
+\xb5\x51\xdd\x7f\xa6\xf6\x04\xc9\x56\x38\xef\xbc\xf3\xec\xf4\x2d\
+\x1c\x2b\xf2\xd8\xd1\x26\xe4\x5c\xf9\x35\x93\x42\x90\xda\x8c\x41\
+\xaf\x3e\x9c\x23\x82\xed\x43\xd8\x60\xb3\x88\xa5\x83\x6c\x32\xe4\
+\x9a\xc6\x14\x6c\xab\xa8\x8b\x99\x0e\xa9\x80\x5d\x16\x45\xe4\xe2\
+\x06\x18\x7f\xc2\xde\x02\xa8\x40\x96\x22\x83\x60\x80\x72\xce\x2c\
+\xa6\xf8\xad\xc8\xb5\x5a\x06\xfd\x07\x24\x0f\xe6\x56\x34\x83\xf4\
+\xe4\x7d\x75\x49\x14\x44\xb5\x55\x02\x6b\xbb\xb1\x45\x67\x77\x79\
+\xd5\x68\x93\x05\xbd\xf4\xea\xe1\xc2\x12\x9e\x60\xfd\x1d\xc1\xbc\
+\x04\x1b\x12\x75\x64\x5c\x8a\x79\x5b\x6c\x74\x12\xc9\x1e\x4d\x94\
+\x6a\x66\x42\xae\x99\xd3\xae\x82\x69\xa6\x65\xf0\x86\xc4\xd2\x4c\
+\x47\x88\x75\x7e\x65\xbb\x64\x4b\x5c\x12\x8d\x4b\xfa\xed\xd8\x80\
+\x48\xa3\x82\xf3\x79\x32\x0d\x49\x92\x50\xfd\x8b\x08\x65\xc3\x08\
+\x3d\x02\x29\x15\xaa\x3e\x6f\xa4\xab\x2d\x0a\xf5\xc0\xad\x92\x54\
+\xaf\x76\xe8\x86\x84\x62\x19\x40\x12\x31\x59\x16\x2e\x55\xb1\x10\
+\x6a\x8c\xce\x99\x33\xc7\xa6\x38\x85\xb5\x46\x83\xf5\x10\x87\x8b\
+\x78\xac\xb1\x14\x53\xaa\xf2\x26\xc1\x12\x4b\xc4\xd8\x6c\x88\x50\
+\xfb\x8a\x50\x0b\xfc\x7a\x3b\x9e\x5c\xd4\xac\x24\x19\x90\x14\x57\
+\xa4\x17\x92\x8a\x7a\x4e\xd4\xce\x04\x48\x2d\xfe\x5f\xa8\xc1\xea\
+\x35\xba\x9a\xb9\x5a\x46\xe3\x81\x7f\xec\x61\x8b\x8c\x7c\x78\xaa\
+\x5b\x0b\x51\x63\x8b\xcb\xbc\x6a\xac\xe7\x4a\x24\x35\x75\x5e\x63\
+\x9b\xcc\xb8\x7a\x5c\xe5\x08\x96\x60\x7f\x3d\x18\xd8\x60\xde\x8b\
+\x1c\xe2\x54\xa5\xf7\x26\x9d\x34\xb3\x24\x70\x9e\xa5\xb7\xcf\x48\
+\xff\x9d\xd3\x26\x89\x58\xe7\x55\xb6\xeb\x01\x79\x49\xe4\xf7\x8f\
+\xff\xde\x49\xa5\x78\x60\xd3\x93\xc9\x79\x79\x71\x1b\xea\xc1\x24\
+\x3b\x8a\x6b\x0f\xd5\x5e\x9b\xc8\xeb\xe3\x5e\xb9\x67\xee\x9d\x36\
+\x20\xaf\x6d\xbd\xd0\xe2\x98\x0d\x25\x0e\xa3\x20\x1b\xb3\xc6\xce\
+\x37\x6a\x77\xa6\xcf\x27\x91\x6b\xbd\xd0\x3c\xb6\x19\x2f\x4b\x2c\
+\x91\x62\xb3\x41\x50\x4c\xaa\xf0\x14\x61\x31\xa4\x22\xc8\x06\x20\
+\x17\xcb\x00\x33\x64\xc0\x6a\x06\x48\x2e\xc6\xa9\x5e\xd5\x30\x0f\
+\x84\x02\x48\x2d\x16\xae\x66\x6d\x18\xc8\xf5\xad\xd6\xc9\x1b\x79\
+\xea\xe9\x36\x03\xf5\x85\x43\x64\x77\xdd\x88\x6a\x74\x29\x37\x77\
+\x3b\xe9\x55\xdf\xa5\x39\x37\x73\x13\x34\xda\x84\x12\x2c\x9a\x05\
+\xc4\x74\x7e\x5b\x2b\xa2\x38\x92\x0d\x4a\x24\x9a\x0d\x5b\x0c\x77\
+\x70\x84\x63\xa9\xe1\x39\x2d\xf2\x13\x89\xa5\x95\xcc\xcc\x63\xd9\
+\x45\x92\x68\x44\xd1\xef\xd6\x24\x13\x69\x50\x76\xf1\x64\xea\xed\
+\xae\xb1\x47\x14\x42\x88\x4b\xa8\x36\xd1\x3d\x71\x6f\xdc\x23\xf7\
+\xca\x3d\x73\xef\x2b\xf4\x80\xad\x55\x5b\x8c\x3d\x45\x76\x51\x90\
+\x26\x43\x2d\x76\xbc\xf0\x4d\x7d\x11\x26\x0a\x27\xac\x0a\xbf\x09\
+\x17\x6d\x16\xb1\x94\x4c\xbf\xd9\x50\x56\xa9\xdf\x5e\x2b\x42\xad\
+\xf5\xc4\x02\x18\xf4\x64\x2a\x32\x4d\x9f\x74\x1a\x6c\x2e\xd4\xe0\
+\x1b\x6f\xbc\x61\xa6\x4f\x9f\x6e\x01\xb1\x20\xdb\xfc\xf9\xf3\xad\
+\xcd\xb5\x6c\xf1\x62\xf3\x5c\xed\xbb\x2d\xb9\x86\xab\x40\xdc\x82\
+\x8b\x23\xd5\xb8\x1c\xdb\xab\x56\x92\xf4\xf2\xea\xb1\x95\xb3\xbf\
+\xda\x07\x12\xcc\x7b\x90\xbd\x03\x35\xd9\x2f\x30\xf6\x07\x84\x44\
+\x73\x2a\x6a\x70\x4e\x9c\x70\xac\x63\x3d\xa7\x69\x0a\x62\x8d\x8e\
+\x16\x9d\x8a\x13\x68\x70\x4e\xd1\xef\x1d\x91\xec\xb1\x43\x32\x79\
+\x75\xd7\xbb\xc8\xd3\xf3\x12\xca\x7a\x7b\x6d\x8b\x8c\x73\xab\xf6\
+\x92\xa4\xd4\x4a\x81\x98\xdf\x94\x23\x34\x87\x30\x49\x7d\xf1\xd0\
+\x6e\xee\x0b\xad\x92\x24\xb9\x3e\x65\x4e\xcd\x26\x13\xeb\xba\xeb\
+\xae\x2b\x17\x28\xc0\x16\x53\x71\xae\x98\x52\x30\x6a\x49\x3d\xae\
+\x67\xa0\xd3\x03\xf7\x15\x72\xa1\x16\x49\xb5\xe1\x26\x90\x5c\xcc\
+\x69\x83\x60\x80\x49\x00\xaf\xbd\xf6\x9a\x99\xfb\xe9\xa7\x66\xb9\
+\x66\x9a\x50\x1a\xff\xe3\x51\xa3\xcd\x03\xbb\xed\x61\x57\xe3\x7a\
+\x55\x2b\x8d\x2d\xb9\x39\x92\x5e\xb6\x54\x52\x6d\x17\x96\x48\x26\
+\x98\x53\x91\x96\x60\x1d\x5d\x09\xa5\x6e\x49\x24\xf3\xde\x64\x40\
+\x34\x6b\xeb\x3c\x14\x10\x4e\x60\x39\xbc\xd9\x0d\xf3\x12\x88\x35\
+\x42\xeb\x2f\xf2\x79\x9c\x3c\x9e\x40\xde\x5e\x0a\x89\xe4\xbc\xbb\
+\x04\x32\x11\x35\xef\xec\xae\xad\x7d\xa4\xf2\xac\x61\x9e\x4c\xa8\
+\xba\xd1\x3d\x22\xa5\xb8\xe7\x79\x17\x4a\x52\x69\x32\xf0\x45\x49\
+\xa4\xc2\x20\x2f\xaf\x17\x69\xc8\x49\xe4\x6a\xb4\xc9\xc4\x52\xb0\
+\xac\x5c\xa0\x42\x6c\x31\xd5\x28\x8d\xa9\xf2\x49\x4c\x0b\xf9\xd4\
+\xf3\xcb\x90\x79\xe0\x35\x92\x80\x46\x39\x1c\x9f\x57\x4d\xbd\xf0\
+\x77\xdf\x7d\xd7\x12\x6c\xc6\x8c\x19\x16\x90\x8c\x65\x63\x17\x6b\
+\x6c\x71\xad\xae\xf0\x47\x0d\x60\x3f\x7d\xdd\x0d\x56\x7a\x0d\xd3\
+\xcc\x9f\x39\x0a\x00\x2e\xaf\x16\x85\x25\xe2\xea\x31\x20\xd8\xf2\
+\x26\xd1\x34\x33\x6b\x83\xb5\x76\xd1\xfb\x0e\x45\x24\x5b\xe9\x49\
+\xe6\xec\x31\xeb\x81\xf5\x76\x9e\xa5\x27\x9b\x23\x9c\x19\x98\x65\
+\x66\xd5\x0b\x88\xa5\x55\x63\x47\x9c\x51\xc9\xae\xc1\x68\xc9\xd3\
+\x2f\xd8\xbf\xaf\xf3\xe8\x7a\xbb\x63\x7a\xbb\xa9\xbb\x1b\x82\x71\
+\x64\x2a\xf4\x5e\x5e\xeb\xe8\x1a\xad\xca\x6b\x92\x9e\x48\x28\x52\
+\xb8\x45\xa8\x95\x0a\x78\x2e\x92\x97\x3c\xf9\xa0\x68\x8d\xc4\xbe\
+\xfb\x1c\x60\xf6\xdb\x61\xc7\x84\xd9\xcf\x18\xe9\xe5\xf5\xfa\x41\
+\x59\xbf\x04\xba\x03\x62\x7d\x2e\x54\xd8\x2a\x88\xa5\xc8\x6e\x4c\
+\x8b\x3a\xc5\xe4\x9d\xb4\xc6\x43\x09\x81\xd7\xe8\x67\xda\x22\xbe\
+\xc9\x8f\x67\x68\x81\x25\xd2\x58\x99\x9d\x2d\x78\x47\x43\x10\xfc\
+\x4f\xa5\x3a\x3c\xc6\xd5\x52\x8f\x9f\x68\x88\x68\x80\xa6\x95\x91\
+\xea\xfc\x94\xc6\x19\x17\xfe\x37\x52\x8f\xb8\xdc\x36\xee\xe5\xed\
+\x2f\x2f\xc1\x1a\x47\x86\x6f\x5c\x4d\xb6\xce\x2c\x0a\x55\x74\x74\
+\xa5\xc2\xbb\x44\x12\x64\x55\x12\xd9\xec\xaa\x65\xe4\x39\x3d\x28\
+\x62\xd5\x4d\x22\x96\x56\x8e\x65\x81\x4f\xeb\x85\x82\x9e\xd9\x09\
+\x24\x8a\x1f\xaf\x4b\x94\x79\x60\xcf\x15\x27\x53\xa0\xee\xb8\xb6\
+\xc6\x81\x84\xba\x37\x92\x50\xcb\x9d\xda\x5b\x22\xb5\xf7\x96\x56\
+\xb0\xed\x2b\x5b\xaa\x6b\x66\xae\x79\x4d\x69\xde\x53\xe4\x59\xa7\
+\x07\xc5\xfd\x99\x72\x57\xde\x2f\x66\x3c\x07\xc4\x5a\x27\x9c\xbf\
+\x55\x11\x4b\x52\x2b\xa6\x78\x56\x33\x96\x21\x0b\xe1\x57\x92\xa2\
+\xee\x03\x36\x17\xea\x11\xf7\x18\x69\xc5\x24\x80\x8f\x3e\xfa\xc8\
+\x92\xea\x83\x0f\x3e\xb0\xdb\x2f\xbe\xf8\xc2\x14\xa8\xbe\x00\xd2\
+\x6b\x89\x88\xf6\x8a\x1a\xb3\x67\xa5\x1d\x6c\x3d\xae\xe7\xf4\x24\
+\x7f\x73\x45\x94\x4f\x5f\x08\xc1\x42\x15\x79\x5f\x7a\x51\x80\xb5\
+\x49\x7a\x11\xc9\x5a\x3a\xa2\x79\x8f\xb2\xbd\x23\x5b\x27\x87\xce\
+\x51\x8c\x0c\x97\x7f\x7d\xaf\x4c\x33\xab\x56\x44\x2c\x16\x49\x67\
+\x9d\x6b\x96\x25\x66\xf5\x58\x6b\x1f\x75\x75\xb1\xa6\xce\xc1\xef\
+\x3b\x38\x15\xe7\x3c\x3b\x7b\xae\x96\x01\x99\x9a\xa4\xc7\x03\x9c\
+\x5c\xa3\x57\x79\x10\x6a\x15\x84\x52\xf1\xba\xf7\xb4\x88\xfb\xa0\
+\xbc\x68\x59\xe1\x47\xcf\xbf\xd0\x7c\x39\xfd\x35\xdb\xe9\x1d\x64\
+\xa7\xfa\x4e\x67\x69\x1a\x56\x91\x28\xef\xd7\x67\xd2\x10\x1c\x3b\
+\x20\x57\xeb\xad\x8e\x58\xbc\x97\xcd\x75\x8f\xde\xaf\xf7\x0b\x69\
+\x02\xec\x2e\x86\x11\x98\x59\x8d\x4a\x24\xa6\x82\xc7\x88\x87\x38\
+\x7b\xf6\x6c\x4b\x30\xd4\xe1\xc7\x1f\x7f\x6c\x1b\x8f\xb8\xcb\x8f\
+\x3f\xfe\x68\x56\xaf\x5d\x6b\xd6\xe9\xaa\x17\x69\xa2\xe5\xe4\x1a\
+\x77\x99\x1e\x8a\x7b\x11\x9a\x98\xac\xe1\xa9\xaf\x2e\x8d\x54\xe4\
+\xca\xbb\x9c\x0d\x56\xcb\x05\x58\x43\x92\x35\x8c\xca\x2a\xad\xf0\
+\xea\xb2\xb9\x53\x99\xad\x9c\xda\x6c\x13\x2d\x89\x67\xa5\x8b\x88\
+\xb1\xb6\x4b\x86\xf9\xa4\x7a\xae\x69\x5c\x71\x5b\xd3\x2f\x3d\xd3\
+\xb4\xce\xa8\x68\x86\x9e\x50\xc9\xac\xeb\x1a\x79\xa1\x96\x3c\xec\
+\xdb\xd6\xfd\xb6\x75\x74\xac\x15\x9e\x48\xcd\x1c\x91\x1a\xbb\x73\
+\x87\x64\xba\x27\xba\x46\x16\x6a\x47\xe5\xfd\x74\x43\xcc\xbc\xa3\
+\x4a\x3d\x83\x1d\xa1\x46\x9c\x70\xb2\x99\x35\xee\x31\xb3\x5a\xb1\
+\x28\xee\x79\xad\xa4\x36\xb5\xa9\xc2\x62\x6b\x6b\xd7\xae\xdd\x22\
+\x8b\x34\x91\xf5\x10\x10\xeb\xc9\xad\x92\x58\x92\x5a\xd8\x5d\xb7\
+\x8a\x50\xcb\x43\x72\xa1\x1a\xb1\xbd\x7c\x35\x5e\x42\x12\xa8\x46\
+\x06\xac\x21\xd6\x3c\xcd\xa8\x86\x64\x80\x19\xbb\xe0\xcb\x2f\xbf\
+\xb4\x71\x1a\x9a\x93\xc6\xfe\x56\x52\x6d\x72\xb5\xea\x36\xc7\x8b\
+\xec\xd4\x27\x54\xe8\x64\xe6\xd9\x45\x46\x7e\x61\xcd\x68\x78\xc8\
+\x7a\x92\x77\xbb\x52\xe1\xf7\x45\x8b\x1c\xd8\x2c\x0a\xaf\x32\xe3\
+\x64\x0b\x08\xa7\x41\xde\x55\x22\xc9\x0f\xf5\x32\x4d\xdf\x43\x2a\
+\x99\x26\x3b\x6c\x67\x5a\xee\xb2\xad\x79\xeb\xea\x5c\xb3\xa6\x4d\
+\xf4\xbd\x85\x27\x50\x33\x77\x8c\xc6\xe9\x45\x2a\x8e\x73\xd4\x8b\
+\x6a\xdf\x73\x6e\x7b\x0d\x9e\x4c\xb5\xa2\xc9\x25\x5f\x5f\x1e\x33\
+\x2f\x69\x8c\xef\xc1\xb4\x68\x05\xd7\x11\x27\x9f\x6e\x66\x6a\x18\
+\x6c\x65\x61\xa1\x61\x39\xf1\x95\x9a\x23\x88\xed\xf3\xa9\x9c\x1a\
+\x3c\x6c\xdf\xe1\xb4\xdb\x96\x7a\xf9\x75\x08\x1d\xde\x61\xb2\xd6\
+\x56\x49\x2c\x3d\x01\x6c\xff\x2d\x2c\x62\xb8\xc7\xc3\x2e\x05\x2c\
+\x72\x31\xf4\x40\x70\x0f\xe9\x85\x8e\x27\x69\x10\x23\x9e\xf0\x03\
+\xa2\x79\xee\xdc\xb9\xb6\x61\x91\x5c\x10\x0e\xdb\x6b\xb9\x53\x8f\
+\xe0\x07\x91\x6f\x9a\x86\x2d\xfa\xef\xbd\xbf\xed\x9c\x41\x1a\xdd\
+\x7f\xe5\x88\x28\x4c\x41\xe6\x04\x52\x0c\x55\x89\x41\x6c\x25\x59\
+\xed\x40\x9a\xdd\x1b\x49\x11\x6b\x9b\xd5\x4f\x8f\x13\xce\x42\x2a\
+\x6b\xa5\xc8\xf2\x6b\xbd\x0c\xf3\x7d\xed\x4c\xf3\x63\x1d\x49\xa4\
+\xc6\x11\x79\xac\x3a\xf3\xfb\x35\x70\xbf\xad\xe7\x8e\x75\x6f\x20\
+\x95\x50\x73\xb5\xbd\x64\x92\x87\xa7\x6b\x59\x74\x5d\xcc\x7c\xa0\
+\x58\xd4\xb8\x9d\x23\xa3\xbc\x7b\x56\xbe\x2d\x5c\x37\x57\x01\xe4\
+\x42\x45\xc6\x21\xd4\x2a\xad\x12\xc1\xa4\x07\x26\x45\xd0\x06\x38\
+\x38\x61\xa4\x9c\x54\xe3\x2d\xf5\xe2\xd8\x49\x61\x87\xed\xb7\x4a\
+\x62\x11\xe7\x62\x2b\x1c\xa1\xef\xde\x67\xc5\xd6\x10\x48\x2f\xf2\
+\xb9\x50\x8d\x48\x2d\x86\x82\x90\x62\x64\x43\x40\x26\x24\x15\x24\
+\x63\x19\x35\x1a\x19\x72\xd1\xe0\xa8\xc7\x42\x3d\xd9\x90\x8b\xce\
+\x58\xaa\xfa\x50\x9f\x28\x44\xf1\xe8\x39\xe7\x99\xfb\x2b\x6c\x6b\
+\xa5\xd8\x30\x0d\x11\x4d\x93\x44\x98\x7b\x81\xd4\xcd\xf5\xd1\x0a\
+\x1a\xab\x6a\xc6\x6c\x07\x2f\xf7\x86\xbf\x27\xdb\xdd\x8e\x0c\x75\
+\x1c\xea\x46\x58\x21\x89\x53\x58\x2f\xc2\xf2\x7b\x8b\x3e\x8f\xef\
+\x77\x8f\xfb\xed\xdd\x91\xad\x04\x08\x13\x20\x95\x56\xe9\x3c\x18\
+\xe2\x5f\x5f\x11\xa9\x3a\xa4\x6a\x1f\xbb\xce\x74\x9a\xad\x71\xf1\
+\x6a\x8b\x56\xe6\xfb\x0f\x3f\x8a\xdf\x03\xaa\x8f\x65\x4c\x98\x50\
+\xca\xbd\x02\xee\x1f\x62\x31\x15\xcb\x77\x38\xf5\xab\xb6\xd4\x2b\
+\x2c\xc4\xe6\x88\xb5\xdd\xd6\x4e\xac\x98\x6c\xab\x9d\x84\x47\xb1\
+\xb1\x42\x78\x15\x49\x4e\x17\xb9\x42\xa8\x46\x6a\x41\x30\x14\x84\
+\xed\x85\x11\xcf\x10\x04\x8d\xcc\x7b\x88\x05\x88\xd8\xff\xf4\xd3\
+\x4f\x76\x0c\xcc\x5b\x1c\x6c\x17\xc9\x3e\x7b\xbb\x67\x2f\x33\xfa\
+\x8c\x73\xcc\xfd\x39\x15\x6c\xb5\xc1\xfe\xa8\x4b\x15\x9c\x7b\xed\
+\x28\x2d\xd3\x22\xa2\xfd\x78\x5d\xd4\xe9\x05\xd5\x22\xd5\xb9\x4a\
+\x44\xc0\xde\x41\x85\xae\x70\xe4\x58\x5e\x0c\xf8\xbe\xd0\xa2\xe8\
+\x77\x90\x76\xa9\x24\xe4\x2f\xca\x8c\xfd\x4a\x2a\xee\x3d\x49\xa5\
+\x49\x4a\x01\x1f\x96\x1f\x49\xa6\x6e\x69\x59\xb6\xcc\xd3\x0b\x75\
+\xee\x35\x9f\x6b\xbc\xb4\x70\xd9\x32\x7b\xbd\xeb\x9c\x84\x5a\xa2\
+\x10\x0b\xe1\x03\x7f\x6f\xdc\x27\xe0\x33\x4c\x02\xd2\x92\x7c\x87\
+\x53\x5d\x6f\x4b\xbd\x48\x35\x0f\x88\x35\x63\xab\x55\x85\x49\xc4\
+\xb2\xd0\x3e\x4d\xb5\x2d\x4c\x26\x18\x9e\x23\x5e\x23\x49\xfd\xbe\
+\xe0\x08\x04\xc3\x73\xe4\xa9\xa5\x91\x51\x85\x90\x6c\xa1\x52\x6f\
+\x78\xb2\x21\x17\xff\x23\xc1\x0a\x34\xdd\x8c\x4e\x5a\xef\x4b\xf5\
+\xe8\xfd\xcf\x7a\xea\xff\xa7\xdc\xfb\x49\x37\xdd\x6a\x86\x1c\x71\
+\x8c\xb9\x3f\x23\xc7\xae\x4a\xdf\x4b\x18\x22\xb5\x39\x5e\x64\x9b\
+\xaa\xe1\xa3\xb7\x8f\x8b\x99\x8f\x95\x82\x32\xef\xdf\xaa\x00\x7d\
+\x59\xcc\x7c\xab\xba\x13\x3f\x5c\x1b\x49\xba\x5f\xaa\x46\x06\x36\
+\x64\x5c\x74\x4d\x24\x81\x3e\xff\x4f\xcc\xcc\x56\x5c\xed\x7d\x79\
+\x71\xd3\x15\xc0\x7d\x56\x59\xb9\xa3\x14\xc4\xec\x87\x7a\x83\x48\
+\xc2\x83\xff\xd8\xdb\x8c\xbb\xf0\x62\xf3\x46\xdb\x76\x66\xa1\x4a\
+\x0f\x14\xca\x46\xf4\xd7\xf6\x9b\x8c\x6f\x24\xee\x62\x8d\x34\x7c\
+\xa7\xc1\x78\x7f\x2f\xdc\x17\xe0\x33\xaa\xf4\x61\x06\xb0\x28\x29\
+\xa9\x2d\xbe\xc3\x59\x91\x6b\x4b\xbc\xd6\xc9\x49\x40\x7b\x04\xc4\
+\x7a\x7c\xab\x35\xde\x53\x11\x8b\xef\x64\x7b\x9d\xa5\xed\x2c\xb2\
+\x1a\x43\xa0\x1e\xb1\xc1\x88\xd6\xa3\xef\x21\x17\x81\x55\xec\x2f\
+\x26\x67\xbc\xff\xfe\xfb\xb6\xc1\x91\x54\x6c\xe9\x00\x00\xe9\xd8\
+\x42\x30\xd4\x09\x9d\xb6\xc6\x79\x55\xf1\xd1\x7d\x75\xe2\x0f\x72\
+\x04\x66\x3f\xf6\xb8\x79\xb5\x69\x33\xf3\xc4\xc5\x97\x99\xc1\x9a\
+\xad\xfd\xc0\xae\x7b\x99\xfb\xb5\xb0\xa7\x27\x04\x12\x86\x05\xa8\
+\x58\x45\xf6\x01\x08\x12\x8b\x08\xc3\x7b\x54\x59\x4f\xb7\x1f\xfb\
+\xf7\x48\xcf\x36\xbd\xb7\xdd\xc5\x0c\xd8\xef\x60\x33\xea\x94\xd3\
+\xcd\x73\xf2\x58\xdf\xef\xf7\xa0\x59\xf8\xc6\x9b\x66\xa9\xae\x69\
+\xed\xfa\x88\x4a\xfc\xe5\x3d\x45\x3b\x78\x00\x7e\xfe\xf9\x67\xfb\
+\x90\x70\xdd\xfe\xda\xf9\x1f\x63\x9d\xef\x50\xfd\x0c\xdc\x13\x50\
+\xc6\x34\xe0\xc1\xf3\x1d\x4e\xb6\x67\x79\xae\xfc\xe5\x5f\x48\xc6\
+\x70\x05\x7b\xa1\xd5\x1f\x8e\x58\x8c\x2f\x6a\xbb\x93\x30\x58\x9e\
+\xe3\x7a\x5c\xe8\x10\x90\x0b\xa2\xb1\xda\x18\x36\x05\x12\x8c\x99\
+\x24\x90\x0c\x82\x11\xfb\xa2\x23\x28\x27\x4d\x47\x40\x28\x3a\x25\
+\x04\x06\x30\x9e\xa4\x25\x99\x52\xa2\xd7\x16\x53\x80\x6c\x89\xa4\
+\xc5\x77\x22\xec\xfc\xe7\x9e\x37\x73\x44\xba\x0f\x1e\x1e\x64\xde\
+\xea\xda\xdd\xbc\xd6\xaa\xad\x79\xa5\x69\x0b\xf3\x52\xbd\x86\xe6\
+\xe5\x46\x4d\xcd\xf4\x16\xad\xcd\x1b\x1d\x3a\x99\xf7\xfa\xf4\x33\
+\x33\x47\x8e\x36\xf3\x9e\x99\x68\x16\x6a\x38\xea\x67\x39\x18\x85\
+\xba\x8e\x0d\xdc\x77\x27\x99\x28\x88\x06\x99\xb8\x56\x1e\x88\xf0\
+\x1a\xb9\x6e\xe0\xef\x03\x09\x45\x9a\x11\x84\x22\x08\xca\x7d\x73\
+\xcf\xe1\x92\x6f\x0c\x1a\x93\x9d\x5b\xde\x2f\xcc\x90\x80\x54\x6b\
+\x85\xb3\xfe\xa8\xc4\xb2\x50\xfe\xd6\x25\x2a\x36\xf2\x09\x05\x47\
+\x10\xc5\x1e\x10\x0c\x09\x46\x7e\x17\xab\x8f\x91\xb4\xe6\x09\x06\
+\x98\x19\x84\x0d\x86\x0a\x81\x40\x74\x1e\x4f\x32\x84\x02\x74\x14\
+\xe0\x3d\x2a\x87\x7d\xf0\x28\xe9\x68\xd6\xe7\xb3\x64\x93\x14\x59\
+\x5f\x1e\x6a\x04\x12\x49\x42\x72\x5c\xa4\x12\x64\x0e\xaf\x27\xbc\
+\x16\x7f\x3d\xcb\x64\x67\xb1\x0f\xc4\x42\x12\x33\x09\x05\xe9\x44\
+\x49\x21\x24\x34\x5b\x0a\x9f\x91\x25\x82\x07\x4d\x71\x35\xdf\xf1\
+\x2c\x52\xb9\x7e\xfd\xfa\x72\x23\x15\xf6\x2b\x35\xd0\x02\x62\xcd\
+\x14\x72\xfe\xd0\xc4\x22\x24\x21\xf2\x6c\xaf\xf7\x3d\x44\xa8\x15\
+\x21\xb9\x3c\xc1\x50\x05\xd8\x5f\xac\x9b\xc8\x04\x00\x1a\x1e\xfb\
+\x8b\x21\x22\x42\x15\xcc\x0a\x22\x5a\x8f\x14\x80\x38\x18\xf4\x90\
+\x88\xce\xa3\x73\x3d\x90\x0c\x6c\x21\x99\xef\x58\xf6\xe5\x37\x10\
+\xc2\x13\xce\x42\x44\xf9\x2d\x00\xff\xfb\xef\x3c\x81\xf8\x1d\x24\
+\xf2\xe7\xe2\xb8\xc9\xe7\xf2\xe7\x0b\xcf\xc5\xff\x78\x7d\x24\x40\
+\x32\x4f\xc0\x57\x46\xe4\xc1\xa1\xbe\x2b\x03\xcc\x2c\xee\xee\x4d\
+\x04\x1e\x2e\x32\x43\xc3\x81\x62\x66\xdd\x94\x57\x60\x94\x45\xb8\
+\x92\x06\xa1\x6b\x6f\x35\x83\xd0\x9b\x43\x2c\xf6\x23\x71\x50\x24\
+\x3a\x46\x92\xeb\x71\xa4\x17\x8d\xc9\xd6\x03\x92\x41\x30\x3e\xc7\
+\x80\x25\xf5\x96\x4e\x40\x55\x40\x30\x40\x14\x9f\x40\x2b\xc3\x42\
+\x3e\xe6\xe5\x25\x09\x04\xf0\x84\x03\x74\x74\x2a\xf8\xef\x4b\x42\
+\x59\x7e\xeb\x49\xe4\xcf\x8f\xa4\x82\x4c\xcf\x6b\xd1\x05\x66\xc8\
+\xf0\x70\x70\xfd\x90\x0a\xe9\xc4\x03\xc3\xca\x6a\x8c\xa9\x72\x9f\
+\x3c\x50\xfe\xde\x79\xa8\x90\x5a\xe4\x5f\xf9\xce\x67\xbd\xa3\xf2\
+\x58\x0c\x93\x38\x62\x12\xa9\xde\x13\xf2\xfe\x34\xc4\x52\xc3\xd9\
+\xff\xd5\x90\x48\xb0\xcb\x44\xa4\xd7\x68\x50\x00\x99\x3c\x68\x68\
+\x6f\x8b\x91\x35\xc1\xbc\x38\x5f\x97\x1c\x40\x30\x0a\xb4\x12\xb6\
+\x60\xfe\x1c\x19\xab\x44\xf4\xe9\x58\x3a\x1a\x89\xc3\x53\x0a\x18\
+\x1a\xe1\x7f\x2f\x7d\x36\x16\x5e\xca\x41\x1e\xbc\x2a\x7f\x4c\x6f\
+\xa4\x13\x32\x78\x53\x1e\x21\x71\x39\xa6\xc7\x21\x95\xb8\x36\xb6\
+\xbe\x42\x31\x65\xa1\x78\x50\xb8\x37\xa4\x13\x0f\x50\x78\xbf\x80\
+\xa1\x16\xe1\x2b\x55\x8a\x99\x1c\x92\x80\xfa\xb0\x9b\xba\xa2\x3d\
+\xd7\x0d\x99\x93\x48\x55\x20\x9c\xbc\x59\x89\x7e\x5b\x33\xb1\xf8\
+\x5e\xd1\xe6\x6c\x65\x45\x54\x15\x81\xde\xa1\xc2\xaf\x27\x59\x08\
+\x4f\x32\x1a\x9e\x55\xab\xa8\xfd\xc4\x4c\x21\xc8\x85\x24\xa0\x13\
+\x01\xef\x49\x38\x24\xdb\x92\xd9\x42\x44\xf7\xf1\x82\x70\xf3\x21\
+\x1c\x8d\xbc\xa9\x2e\x3a\x04\xc2\xab\x43\x1a\x61\x2b\x91\x76\xcd\
+\xc4\x50\x12\x1b\x51\x6d\x90\x08\xc9\x04\x91\xb8\x0e\x6c\x45\xf2\
+\x9f\x28\x73\x8d\x51\xee\x55\x3e\x04\x4a\x75\x8f\xba\xf7\x9f\x14\
+\xe7\xeb\xaa\xb4\x96\xdd\x95\x2e\x93\xae\xfe\x1b\x1f\x92\x01\x4f\
+\x0e\x63\x7f\x63\xaa\x26\x33\x4c\xc6\x5a\xdf\x29\x26\x63\xdc\xb5\
+\xd9\xa9\xc9\x7f\x00\x62\xd9\xd5\x0e\xb4\xcd\x57\x46\xea\xd5\xa7\
+\x9c\x72\xca\x4b\xda\x77\xad\x7b\x7a\x13\xe0\xa5\x1a\x24\xd3\x7e\
+\x76\xd0\x96\x31\x35\xbb\x98\x94\xb2\x29\xe8\x58\x0c\x7e\xdf\xc9\
+\x8d\x1a\x35\xb2\xe4\x23\x9c\x41\x1a\x8f\x2f\x3a\x46\xb5\x3b\xa4\
+\x0b\x33\x8a\x98\xf8\x81\x34\xf0\x69\xd4\xa8\x30\x32\x60\x89\x2b\
+\x91\xf2\xe3\x4b\x66\xa2\x7e\x91\x8e\x1c\x1f\x09\x04\x7c\x69\x6b\
+\x5f\x90\x9f\xd0\x09\xa4\x67\xe5\x52\x54\x9d\x57\xed\x9e\x4c\xc9\
+\xf7\xe2\xde\x7f\xa1\x7d\xda\x28\x38\xba\xaf\x2b\x38\x6c\x97\x15\
+\x71\xd9\x9d\x53\x93\x49\xc1\x31\x99\xcf\xc9\x48\x45\xaa\xc9\x14\
+\xd8\x7c\xa4\x25\xd1\x1e\x4c\x62\x4d\xfa\x3d\x69\x32\xf5\x62\x9b\
+\xf9\xfa\xc3\x10\x8b\xc6\x84\x5c\x4c\xa0\x14\x61\xd8\xf7\x0c\x6d\
+\x87\x0a\x8b\x91\x62\xa9\x40\xa7\x84\x6a\xf3\xd4\x53\x4f\xb5\xd2\
+\x0c\xcf\x92\x09\x04\xac\x01\x84\xd4\xc0\xbd\x06\x10\xcd\x4b\x36\
+\xde\xfb\xc5\x11\x8a\x83\xdf\x27\xfc\x8d\xff\x0e\x52\x21\x31\x51\
+\x6f\x18\xc4\x8c\x26\xa0\xae\xfd\xb5\x84\x44\x2a\xe6\xfa\xd7\xe8\
+\xde\xde\xd0\xf6\x4e\xed\xb7\x03\xc9\x93\xee\xe1\xb2\xf5\xa7\x1c\
+\xb1\x78\x6d\x2b\x8c\x49\x35\xfd\x8b\xf5\x04\xb9\x67\x65\xf7\xda\
+\xa1\x32\x3c\x4b\xa4\x63\x58\xc2\x28\x09\xbf\x0a\x37\xc5\xca\xe1\
+\xf5\x87\x22\x16\x4f\x2b\x93\x36\x20\x16\xfb\xa9\xa3\x62\x0a\xa0\
+\xee\xad\xf7\xf5\xb5\xdf\xeb\xc5\x11\x2c\x99\x68\xde\x08\xe6\x33\
+\x54\x10\x52\x8d\x8e\x87\x70\x0c\x65\xe0\x89\xd1\x11\x48\x39\xbf\
+\xca\x99\x5f\x94\x0a\x89\xe3\x57\x3d\x63\x59\x3d\xbf\x30\x28\x52\
+\x08\x83\x9b\x78\x1b\xe4\x65\x76\x52\x32\xb9\x4b\x20\x51\x88\x85\
+\xc2\xc3\xfa\xfd\x99\x5a\xeb\x39\xc3\xb7\x09\xc4\xe2\xfe\x53\x10\
+\xcb\xbf\xee\x16\xbe\xdf\xd4\x09\xab\x4e\xf2\x1d\x1f\x2b\xa7\xd7\
+\x9f\x81\x58\xf6\xbd\x3e\xcb\xd5\xef\x8f\xd5\xfe\x1d\xf5\xfe\x3d\
+\x61\x35\xaa\xb0\x24\x84\x84\x0b\x9d\x83\x90\x78\x3c\xf1\x00\x02\
+\xa2\xba\xd8\x02\x3e\xe3\x18\x21\x59\x53\x49\xa1\xe2\xce\xed\x7f\
+\xef\xb0\x40\x18\xa3\xdf\xfd\x57\xbf\xd9\x85\x7b\x16\xb1\x62\x22\
+\x56\x6c\x23\x88\xc5\x6b\x2f\x41\xc9\x1d\xb1\xf9\x65\x24\x13\xc1\
+\xcf\x97\x85\x2b\x85\xcc\x58\x39\xbe\xfe\x4c\xc4\xb2\xbf\x67\x7f\
+\xbd\xcf\x10\x8e\x13\x6a\xe9\xbb\x49\xfa\xfc\x7b\xbd\xff\xad\x34\
+\xa2\x15\x47\xbc\x92\xb0\xb1\xc7\x74\xf8\x55\x24\xfc\x48\xdb\xce\
+\x3a\xc6\x05\xda\xee\x12\x5e\xff\x66\x10\xcb\xbf\x76\x16\xae\x10\
+\x34\xea\x14\x7b\x41\x98\x23\x7c\x29\x7c\x21\x7c\xe8\xc6\xfe\xea\
+\x0b\x27\xc4\xb6\xd0\xeb\xcf\x4a\x2c\x0b\x49\x16\xbb\xbf\x52\xa1\
+\x77\x54\x47\x5d\xa0\xef\x1a\x09\x63\xf5\xdd\x6c\x61\x55\x09\x12\
+\xa4\x3c\xb1\x5e\xe7\xfc\x4e\xdb\xa9\xda\xf6\xd0\xb6\xaa\x70\x08\
+\xf6\x92\xce\x99\x70\xbd\xe5\x48\xac\x54\x2f\x24\x52\x7a\xec\x77\
+\x7a\xfd\x55\x88\x65\x3b\x2a\x98\x07\x59\x49\xdf\xed\xa5\x7d\xce\
+\x13\xea\x09\x0f\xe8\x18\xe3\xb5\x7d\x5d\x98\x23\x7c\x23\x2c\x81\
+\x14\x65\x20\x4e\xa1\xf0\x83\x30\x5f\x78\x5f\xd7\x30\x45\xc0\xa9\
+\x68\x23\x5c\x2f\xf2\x1c\xae\xed\x0e\x42\x56\x78\x6d\xff\x0f\xc4\
+\xfa\x5d\x5f\x7f\x55\x62\xd9\xef\xfc\x7e\xfc\xcf\x75\xa9\xa3\x33\
+\x1c\x09\xf6\xd4\xfb\x83\xb5\x3d\x5a\x38\xf1\x94\xe8\x75\xba\x3e\
+\x3b\x5f\xdb\xb3\x85\x53\x85\x93\x9c\xba\xad\x22\xec\x8b\x6d\xa4\
+\xef\x2b\x70\xcd\x90\x00\xd2\x70\x6c\xbf\xf5\xe7\xf9\x4b\x11\x8b\
+\x3f\x7f\xe3\x6f\x94\x37\xfe\x0f\xef\xc3\x4f\x4a\x8c\xfb\x94\xf8\
+\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x04\xf3\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\
+\x01\x1e\x75\x38\x35\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x04\x70\x49\x44\
+\x41\x54\x48\x89\xd5\x55\x6d\x68\xd5\x65\x14\xff\x9d\xe7\xff\xe6\
+\xdd\xee\xcb\xff\xde\x6d\x72\x27\x6e\xf7\x3a\x37\x73\xb9\x69\xd2\
+\x9c\xc3\x14\x34\x23\xd7\xc4\xd8\x34\x4d\x7a\x99\xa1\x56\x48\x0b\
+\x67\xa0\x16\xb9\x84\xde\xa8\xa4\x0f\x52\x96\xa0\x50\x22\x95\x1f\
+\x86\xb2\xf5\x62\x61\xd6\xd2\x32\x43\x54\xda\xc2\x35\x9c\xc2\x66\
+\xf3\x8e\x69\x13\xb7\x7b\xaf\xff\xf7\xd3\x07\xb7\x31\x5f\x06\x81\
+\xf9\xa1\x03\x87\xf3\xc0\x79\x9e\xdf\xef\x3c\xe7\x9c\xe7\x39\xc4\
+\xcc\xb8\x9b\x22\xee\x2a\xfa\xff\x8a\x20\x30\x49\xda\x31\x2e\x87\
+\xca\x6f\x71\x30\xf3\x1d\x6b\x64\xaa\xf6\xe1\xbb\x8d\x2f\xbb\x4f\
+\x6e\xa9\xba\xe8\x9b\x80\xea\xd1\x3e\xf9\x4e\x23\x0f\x15\x49\x5b\
+\x37\xbe\xfe\xe2\x73\xd3\x0a\x4a\x45\xc9\xe4\xd2\xdc\xf1\xd1\xf0\
+\xa7\xc1\x42\x6a\x18\xe8\xe4\x0f\x00\xdc\x19\x41\xa8\x90\xd6\xd5\
+\x6d\x59\xbd\x79\xe6\xbd\xf7\x2b\xcc\x36\x98\x18\x0f\xce\x5d\x18\
+\x0a\x67\x65\x6e\x8b\xce\xd0\xf2\x7a\x7f\x37\x37\xdd\x50\x03\x22\
+\x52\x7d\xb9\xb4\x93\x88\xfc\xff\x02\x7c\xf9\xaa\xfa\x9a\x77\x2a\
+\x66\xce\xf1\xb9\x30\xe0\x91\x09\x17\x06\x5c\x5c\xc3\xd4\xa2\x29\
+\xda\xf4\xc5\x59\x75\xfe\x5c\x7a\x6c\x84\x40\xd2\x28\x52\xf2\xf0\
+\xc4\xef\xdf\xda\xb5\xf1\xf9\x78\x45\xf8\x0b\x22\xa2\xb1\xc0\xf5\
+\x02\x5a\xb0\x6c\xcd\x82\x1d\x0b\xe7\x3d\x14\xf4\x60\x60\x58\x99\
+\x4c\xd8\x9c\x44\xf3\xc9\x8f\xdd\xd6\x83\x89\x3d\xc9\x04\x37\x0a\
+\x00\xf0\x8d\xa7\xa2\xb9\x2b\x8a\x5b\x1a\xde\xd8\x34\x6f\x4a\x5e\
+\x31\xea\x1b\x56\x57\x65\x15\x8b\xb7\x6f\x1b\x79\x8c\xa6\x2f\x7a\
+\x62\xd6\x9e\x25\x55\x4b\x72\x98\x4c\x78\x64\xc0\x13\x06\x58\x18\
+\x70\x90\xc2\xe1\xf6\x4f\xf8\xc7\xdd\xe7\xf6\xf5\x9e\xe6\x75\x00\
+\x20\x42\x71\x9a\xff\x48\x6d\xf9\x77\xf5\x2f\xbd\x50\xea\x1b\xa7\
+\xc2\x83\x81\x49\x13\x0b\xc4\x9a\x8d\x35\x75\xe1\x42\x5a\x39\x1a\
+\x3c\x90\x4b\xf9\xf3\xaa\x8b\x1b\x97\xaf\xa8\xce\x63\x61\x8e\x00\
+\x63\x68\x7d\xbc\x6b\x1f\x0e\xee\xf8\xb3\xa9\xe7\x04\x6a\x87\xcf\
+\xc8\x5a\x10\x1b\x4a\xe7\xe7\x4e\x12\xb2\x07\x17\x06\x00\x0f\x20\
+\x46\x45\x59\x85\xbf\xa7\xb6\xfb\xbd\x40\x94\x5a\x07\x7b\xf9\x8c\
+\x9a\x49\xfa\xdc\xc7\x63\x5f\x3e\xbd\x66\x69\x91\x90\x1c\x30\x3c\
+\x10\x31\x78\x68\x7f\xeb\xc5\x83\x68\xde\xde\x76\xa8\xeb\x17\xac\
+\x60\x66\x6f\x98\x40\xf4\x77\xe0\xd9\x03\x7b\xbf\x6e\xbf\x94\xee\
+\x04\x93\x01\x16\xe6\x90\x35\xf0\x68\xcd\xa2\xbc\xb2\xca\xbc\xbd\
+\x44\x94\x5d\xb6\x64\xfc\x37\x6b\xeb\x97\x4e\x97\x15\x06\x4b\x06\
+\x20\x99\x43\xd6\x40\xe7\xc0\x61\xec\xdf\x7e\xea\xe7\xb3\x87\x51\
+\xcd\xcc\xf6\xe8\x5b\x4b\x9e\xc3\xa9\xd7\x1a\xb6\x5e\x72\x73\xce\
+\x55\x4e\x2d\x2c\xd1\x84\x60\x40\xd8\x00\x39\x10\x92\x87\x69\xb3\
+\xf2\x72\x2d\x5c\x5d\xf9\xcc\x86\xca\x12\x9f\xcf\x07\x92\x1c\x90\
+\xb0\x01\x61\x83\x84\x83\x9e\x6b\x27\xb0\xef\xfd\x63\x27\xce\x7c\
+\x8b\xc5\x8e\xc5\x83\x37\xd7\x8c\x86\x7f\xd3\xd8\x6c\xda\x55\xfb\
+\xe6\x8c\xb5\xf7\xe5\x57\x42\x10\x03\x04\x80\x3c\x10\xae\x5b\x10\
+\x83\xe1\x02\x60\x00\x2e\x98\x3c\x5c\x36\xdb\xf1\xd9\xf6\x23\xad\
+\x6d\x5f\x71\x55\xff\x05\xee\xb9\x5d\x53\x8c\x10\x10\x91\x36\x63\
+\x29\x1d\x59\xb0\x5e\x2a\xd7\xe4\x71\x50\x65\x1f\x14\x49\x85\x20\
+\x02\x09\x40\x08\x80\xc9\x85\x07\x13\x2e\xa7\xe1\x91\x85\xdf\x3e\
+\xf7\xda\x4f\xef\x47\x4d\x6f\x07\x77\x8c\xd5\xd2\x23\x2f\x99\x99\
+\xcd\x58\x69\x66\xdd\xc5\xe3\xd4\x5c\x56\x95\x13\x0d\x6b\x31\xf8\
+\x95\x2c\xc8\x42\x05\x09\x02\xe0\xc1\x41\x0a\x29\x2f\x81\xa4\x73\
+\x01\xbf\x1e\xb8\x72\xbe\xe3\x10\xaf\xea\xed\xb0\xc6\x04\xbf\x81\
+\x00\x00\xba\xda\x52\x27\x1f\xa8\x9e\xb8\x7b\xf6\x7c\xfd\x95\x68\
+\xf6\x64\x29\xa2\x4e\x86\x26\x05\x40\x44\x60\xd8\x48\x7b\x7d\xf8\
+\xdb\x01\x3a\x7e\xb8\x92\xe8\x3d\x96\xb9\xfe\x68\xd3\x91\xd3\xb8\
+\x9e\xcc\x31\xa7\x16\x8d\x9a\x68\x94\x48\x24\xfc\xe9\x74\x5a\xdf\
+\xfa\xd1\xca\xc6\x65\x9b\x27\x94\x47\xb4\x02\x68\x22\x08\x22\x82\
+\x07\x0b\xd7\xd0\x8b\x3f\x4e\x9e\xbf\xda\xd6\xe4\xdf\xb6\xb0\xfc\
+\xa9\x26\x00\x66\x20\x10\xb0\x02\x81\x80\x9d\x91\x91\x61\xeb\xba\
+\x6e\xa9\xaa\x6a\xe4\xe7\xe7\x5b\xc3\xa4\xc4\xcc\xe8\xeb\xeb\xcb\
+\xe8\xea\xea\xf2\x77\x77\x77\x07\x13\x89\x44\xe0\x6c\x67\xfb\x3d\
+\x97\xe4\xa3\xaf\x46\x27\xe8\xaa\x24\x69\x42\x10\x91\xcb\x8e\x67\
+\x98\x83\x6e\xa2\x5d\xf9\x29\x9a\x31\xb3\x19\x40\x3a\x14\x0a\x19\
+\xba\xae\x9b\xba\xae\x9b\xe1\x70\xd8\xd4\x75\xdd\x8c\x44\x22\x66\
+\x76\x76\xb6\x19\x8b\xc5\xd2\x00\x1c\x19\x00\x2c\xcb\xb2\x98\xd9\
+\x02\xe0\xb9\xae\xcb\x01\x7f\xf8\x2f\xf3\xf2\x9c\x4d\x7d\xa7\x92\
+\xfe\x54\x2a\xa5\x39\x8e\x23\x24\x29\xc3\x95\xe5\x88\xa5\xc8\xb2\
+\x31\xe8\x0e\xda\xa1\x50\xc8\x13\x42\xb0\x24\x49\x2c\x84\x00\x11\
+\xb1\x24\x49\x50\x14\xc5\x1e\x18\x18\x30\x01\x38\x37\xa7\x08\x00\
+\xd0\xd2\xd2\x22\x01\x90\x93\xc9\xa4\xda\xdf\xdf\xaf\xa4\xd3\x69\
+\xd5\xb2\x2c\x85\x99\x15\x22\x92\x24\x49\xb2\x55\x55\xb5\x64\x59\
+\xb6\x83\xc1\xa0\xad\x28\x8a\x1d\x0c\x06\x9d\x78\x3c\xee\xc4\xe3\
+\x71\xef\xe6\x7a\xdc\x42\xf0\x5f\xcb\x5d\x1f\xfa\xff\x00\xa6\x30\
+\x1f\x89\xba\x23\xab\xc4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
+\x60\x82\
+\x00\x00\x95\x84\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x02\x00\x00\x00\x02\x00\x08\x06\x00\x00\x00\xf4\x78\xd4\xfa\
+\x00\x00\x95\x4b\x49\x44\x41\x54\x78\xda\xed\x9d\x05\x98\x1b\x55\
+\xd7\xc7\x53\x56\xa2\x9b\xec\x26\x1b\x77\x77\xd9\xac\xbb\x75\xeb\
+\xa5\xb8\x16\xb7\xe2\xae\xc5\xdd\xad\xb4\xb8\x15\x2b\xd0\x16\xea\
+\xee\xee\xee\x8e\xbe\xf0\xc2\x87\x53\xaa\xfb\xcd\x2d\x33\x65\xde\
+\x65\x37\xb6\xc9\x58\xce\x3c\xcf\xff\x81\xa4\x99\xdf\xce\x9c\x7b\
+\xef\xf9\x9f\xb1\x3b\x3c\x1e\x2c\xb0\xc0\x02\x0b\x2c\xb0\xc0\x02\
+\x4b\xb2\x4b\xcf\x9e\x4d\x3d\x30\x9d\x40\x52\x0f\xe0\x01\x0f\x78\
+\xc0\x03\x1e\xf0\x80\xc7\x2e\x5e\xb2\x7f\x3c\xa7\xa3\x80\x07\x3c\
+\xe0\x01\x8f\x0a\x9e\x82\xc7\x2b\xf8\xa0\xa2\x62\x72\xc1\xb1\xff\
+\x85\xf8\x01\x0f\x78\x54\x1e\xf5\xe7\x62\xca\x23\x29\x37\xd5\xea\
+\x03\x78\xc0\x03\x1e\xf0\x92\xe4\xe5\x8e\xaa\xaa\x9a\xf6\x9f\x81\
+\x03\xdb\xdf\x2e\x2f\xff\x1c\xe2\x07\x3c\xe0\x51\x70\xf4\x8f\xff\
+\x71\xf4\x07\xf3\x49\xca\xeb\xe6\xce\x00\x0f\x78\xc0\x03\x5e\xc2\
+\xbc\x67\xc3\xe1\x37\x90\xf9\x13\xba\xc0\x6c\x1e\x02\xf1\x03\x1e\
+\xf0\x32\x6f\xfe\x7c\x4c\x02\x92\xf8\xdd\xdc\x19\xe0\x01\x0f\x78\
+\x0c\xe1\xe9\x85\x42\x03\xd3\xf7\xf7\x7a\xa7\x73\xe8\xb7\x98\xe9\
+\x93\xb5\xbb\x6f\xdf\x3f\x9d\x12\x89\x17\xda\x17\x78\xc0\xcb\x9c\
+\xf9\xa3\x3f\x28\x24\x49\xd0\xcd\x9d\x01\x1e\xf0\x80\xc7\x10\x1e\
+\xba\xa6\xbe\xa9\x57\xaf\x1f\x30\x83\xbd\x9b\xa9\xfb\x7b\xb2\xc1\
+\x70\xee\xb7\x03\x06\xb4\x77\xa6\x19\x0d\x0d\x6b\xb1\x9f\xf0\xa1\
+\x7d\x81\x07\xbc\xc4\x98\x89\xfe\x10\xdd\x5d\x28\xc2\x24\x26\x09\
+\x7d\x3e\x21\xc5\x3f\x0c\x3c\xe0\x01\x8f\x61\xbc\xeb\xec\xf6\xbb\
+\x08\x33\x7d\xa5\xa4\xe4\x63\xec\x2b\x21\x93\xb6\xaf\x56\xa1\x68\
+\xfa\xa2\x6f\xdf\x03\x5d\x15\x00\x48\x0f\xf8\x7c\xcf\x43\xfb\x02\
+\x0f\x78\x71\x8b\x89\x9c\x84\xd6\xc7\xff\x38\xfa\x83\x12\x92\xc4\
+\xdd\xdc\x19\xe0\x01\x0f\x78\x0c\xe2\x29\x79\x3c\x09\x3a\xfa\x27\
+\x9b\xe9\xf4\xba\xba\x55\x1d\x2f\x09\xd0\xb5\x7d\xe8\xf4\xfe\xd6\
+\xde\xbd\x7f\x8a\x65\xfe\x48\x5f\x0d\x18\x70\xb4\x49\xaf\x3f\x09\
+\xda\x17\x78\xc0\xeb\xd2\xfc\x73\x13\x2a\x00\x48\x7f\x5c\x4a\x92\
+\xa4\x9b\x3b\x23\x01\x1e\xf0\x80\xc7\x2c\xde\x55\x36\xdb\xad\x9d\
+\x19\xea\xba\xb6\xb6\xff\x94\x17\x15\x55\xd3\xb9\x7d\x2a\xb1\x58\
+\xbd\xbc\xa5\x65\x4f\x3c\xf3\xff\x06\xd3\x9e\x01\xfd\xdb\x97\xb5\
+\xb5\x7d\xe7\xb5\x59\xec\x4c\x6c\x8f\xca\xca\x32\x19\xf4\x3f\xe0\
+\xd1\xc4\xeb\x41\x7a\x6a\x20\x76\x01\x80\xff\x58\x44\xda\x00\xa2\
+\xe3\x76\x67\x67\x08\x8e\x0c\x78\xc0\x03\x1e\x63\x78\xc2\x0d\x3d\
+\x7b\x7e\xd7\x95\xb1\xee\xeb\xd7\xef\xaf\x33\x4d\xa6\x8b\x68\xda\
+\x3e\xd1\x94\xfa\xfa\x15\x89\x98\xff\x6e\xcc\xfc\x77\xe1\x7a\xb3\
+\xb2\x72\x7a\x53\x53\x9d\x8c\x29\xed\xa1\x15\x0a\x0b\x1f\x2b\x89\
+\x8c\xbc\xd6\xeb\x7d\x0c\xfa\x1f\xf0\x68\xe0\x11\x37\x10\xe6\x93\
+\x0a\x80\x1e\xb1\x7e\x2c\x20\x9d\x7a\x90\x42\xb0\x81\x07\x3c\x6e\
+\xf2\x2e\xb3\x5a\x6f\x4c\xc4\x5c\x87\x06\xfc\xc3\xab\xab\x2b\xe4\
+\x14\x6e\xdf\x09\x6f\x97\x97\x8f\x4b\xd6\xfc\x91\xd0\xe7\x8b\xad\
+\xd6\xeb\x99\xd0\x1e\x0d\x6a\x75\xef\x45\x2d\x2d\x5f\xa1\xed\x9a\
+\xdb\xdc\xbc\x17\x2b\x4c\x0a\xa1\xff\x01\x8f\x62\x1e\xf1\xd4\xc0\
+\xf1\x02\x20\x5e\xa5\x20\xec\x70\xed\x01\x82\x0d\x3c\xe0\x71\x8f\
+\x27\x58\xd3\xb3\xe7\x37\xdf\xe0\x46\xda\x51\x5f\x63\x42\xc6\xb5\
+\x13\xd7\x5b\x55\x55\x33\x3d\x16\x93\x91\x8a\xed\x7b\xc4\xef\x1f\
+\xd6\xd5\x76\x75\xb5\x7d\x3b\xf1\x02\x00\x7d\xbf\xb7\x5f\xbf\xbf\
+\xdc\x62\x71\x90\xae\xf6\x40\x4f\x55\x3c\x19\x0a\xbd\xde\x71\xfb\
+\xea\x0d\xfa\x7e\xd0\xff\x80\x47\x21\x4f\x44\x7a\x6a\x00\x15\x00\
+\xb9\xf1\xae\x11\x08\x48\x05\x80\x18\x82\x0d\x3c\xe0\x71\x93\x77\
+\xa1\xd5\x7a\x4d\xb2\xe6\xba\xa0\xa9\x69\x9b\xb5\xa0\xc0\x95\xc9\
+\xed\xbb\xdc\x6a\xbd\xa9\x3b\xe6\x4f\xfc\x66\x4e\x43\xc3\x46\x5e\
+\x17\x4f\x33\x64\xb2\x3d\xea\x15\x8a\x96\xa5\xad\xad\x7b\x3b\xdb\
+\xbe\xe7\xc3\xe1\x91\xd0\xff\x80\x47\x11\x8f\xf0\x70\xa2\x00\xc8\
+\x8b\x75\xea\x3f\x17\xaf\x10\x88\x02\x40\x04\xc1\x06\x1e\xf0\x38\
+\xcb\xcb\x5f\xd9\xb3\xe7\x97\xa9\x98\xeb\x96\xde\xbd\x7f\xaa\x57\
+\x2a\x7b\x65\x62\xfb\xfa\x6a\x34\xa7\xa2\xbb\xf9\xbb\x6b\xfe\x84\
+\x1e\x0d\x04\x46\x50\xd8\x1e\x62\xf4\xf7\xbe\xc4\xb6\xbf\xab\xed\
+\xdb\xd9\xb7\xef\xef\xe8\xa9\x0b\xe8\x7f\xc0\xcb\x30\x8f\xfc\xd4\
+\x80\x30\xe6\xa4\x41\xf8\x4d\x01\x79\xa4\x02\x40\x00\xc1\x06\x1e\
+\xf0\xb8\xcb\x3b\xcf\x6c\x1e\xf2\x4d\xff\xfe\xed\x1d\xf5\x35\xa6\
+\x5d\xfd\xfb\xb5\xef\x24\x09\x7d\xfe\xba\xc3\xef\xbe\xe8\xdb\xf7\
+\x30\x3a\x52\x4f\xe7\xf6\x95\x14\x15\x55\xef\xee\xdb\x77\x7f\x67\
+\xdb\x95\xec\xf6\x91\xd5\x53\xa5\x3a\x31\xd3\xed\x51\x55\x5c\xdc\
+\xb8\xb8\xa9\x69\x57\x22\xdb\x77\x96\xc1\x70\x31\xf4\x67\xe0\x65\
+\x98\x27\x25\x15\x00\x82\x78\x37\xfd\x91\x0b\x80\xee\x4c\x57\x08\
+\x8d\x07\x3c\xe0\x31\x9f\x97\xb7\xac\xa5\x65\x6f\x3a\xcc\xf5\xb9\
+\x50\xe8\x2d\x74\x36\xa1\xbb\xdb\x67\xe4\xf3\xed\x1b\xda\xda\xfe\
+\x9b\x6e\xf3\x47\x42\xdc\x62\x91\x48\x9b\xa1\xf6\x10\x3d\xec\xf7\
+\xbf\xf8\x55\xff\xfe\x47\x13\xdd\xbe\xb1\x55\x55\xf3\xa1\x3f\x03\
+\x2f\xc3\x3c\xa2\x00\x10\xc5\xf4\x73\x7c\xa5\x1c\xd2\x33\x82\x60\
+\xfe\xc0\x03\x1e\x87\x79\xe7\x18\x0c\x97\xa4\xcb\x5c\x91\xc6\xd7\
+\xd4\x2c\x42\xcf\xeb\xa7\xba\x7d\xe8\x95\xbe\x0b\x9b\x9a\xb6\x67\
+\xc2\xfc\x09\x7d\x5c\x51\x31\x03\xfb\x53\x3d\xd2\xd9\x1e\x95\x45\
+\x45\x75\x8b\x9a\x9a\x76\xa4\xb2\x7d\xa8\xe0\x81\xfe\x0c\xbc\x0c\
+\xf2\xa4\x09\xdd\xc3\x47\x2a\x00\x72\xc1\xfc\x81\x07\x3c\xce\xf3\
+\x72\xd1\xa9\xea\x74\x9a\x2b\xd2\xca\xe6\xe6\x2f\xbc\x32\x59\x49\
+\x0a\xdb\x27\xf8\xbc\xa6\x66\x61\x26\xcd\x9f\x10\x9a\xf0\x28\x4d\
+\xed\x21\xbc\xcf\xeb\x7d\xf6\xcb\xbe\x7d\x8f\xa4\xba\x7d\xb7\xb9\
+\x5c\x0f\x41\x7f\x06\x5e\x06\x79\x89\x3d\xbd\x47\x2a\x00\xc0\xfc\
+\x81\x07\x3c\x8e\xf3\x4e\xd3\xeb\xcf\xcf\x84\xb9\x22\xed\xec\xdd\
+\xfb\x8f\x81\x1a\xcd\x99\x49\x6c\x5f\x0f\xf4\xde\x01\x2a\xcc\x1f\
+\x69\x5f\xbf\x7e\x07\x43\x45\x45\x65\xdd\x89\x5f\x54\x2e\xaf\x9a\
+\xdf\xd8\xb8\xb5\xbb\xdb\x87\x0a\x26\x0c\x77\x02\xf4\x67\xe0\xd1\
+\xca\xeb\xe6\x1b\x85\x20\xd8\xc0\x03\x5e\x1c\x9e\xc7\x68\x08\xf8\
+\x6c\x16\x13\x03\xb6\xef\x84\x05\x8d\x8d\xdb\xbe\xc6\x8d\xeb\x2b\
+\x64\xda\x98\x59\xed\x20\x09\x7d\xfe\x0a\xff\xf7\x64\x45\xf0\x6e\
+\xf0\x7a\x9f\xc4\x27\xbc\x89\xb9\x7d\x43\x3d\x9e\x27\x13\xe1\xa5\
+\x73\xfb\x66\x34\x36\xee\xf0\x7a\x5d\xba\x14\xe2\xc7\x47\xdb\xfb\
+\x05\x76\xd4\x9f\xae\xed\xab\x93\xcb\x7b\xc2\xf8\x00\x1e\x53\x78\
+\x10\x1c\xe0\x01\x2f\x03\xbc\xeb\x3d\x9e\x27\xd6\xf5\xee\xfd\xfb\
+\xd0\x40\x60\x84\x5e\x22\xb1\xd2\xb5\x7d\xa7\x68\xb5\xe7\x64\xda\
+\xfc\x09\xd6\x4b\x65\x65\x13\xdc\x6e\x87\xa6\xab\xed\x3b\xdf\x6c\
+\xbe\x92\x6a\xf3\x27\x78\x8f\x84\xc3\x23\x93\x89\x5f\x58\x2a\xad\
+\x98\xd3\xd8\xb8\x39\xdd\xdb\xf7\x52\x49\xc9\x07\x30\x3e\x80\x07\
+\xe6\x0f\x3c\xe0\x71\x98\xf7\x4a\x79\xf9\x54\xc2\x1c\xf6\xf6\xeb\
+\x77\x68\x78\x24\xf2\x51\x50\x26\x8b\x52\xbc\x7d\x3d\x30\x13\xdb\
+\x44\x85\xf9\x13\xbc\xe9\xf5\xf5\xeb\xcc\x7c\xbe\xa5\xe3\x86\xb4\
+\xaa\xd5\x03\xf6\xf5\xed\x7b\x98\x0e\xf3\x27\xd4\xc7\x6c\x1a\x9c\
+\x40\xfc\xf2\xef\x70\xbb\x1f\xeb\xb8\xad\xe9\xda\xbe\x9d\xbd\x7b\
+\xff\x29\xe7\xf1\xa4\x30\xde\x80\x07\xe6\x0f\x3c\xe0\x71\x94\xb7\
+\xa4\xb5\xf5\xdb\xce\xcc\x61\x74\x55\xd5\x9c\xe6\xe2\xe2\x7e\x3c\
+\xd2\xdd\xe9\x99\xda\xbe\xfe\x1a\xcd\xe9\x54\x9a\x3f\xc1\x5b\xdf\
+\xd6\xf6\xdf\xea\xc2\xc2\x06\x62\x3b\x50\xe1\xb3\xa3\x4f\x9f\xdf\
+\xe9\x34\x7f\xf4\x79\x53\x5b\xdb\xff\x75\x7c\xdd\x31\x79\xc1\xb6\
+\xb3\x74\x66\x7d\xfd\x86\x4c\x6f\xdf\x39\x66\xf3\xe5\x30\xde\x80\
+\x07\xe6\x0f\x3c\xe0\x71\x90\xe7\xb6\x98\x1c\x5f\xf5\xeb\xd7\xfe\
+\x75\x0c\xcd\x6e\x68\xd8\x88\x4f\x0e\x93\x9f\xa1\xed\xeb\x31\x03\
+\x3b\x1a\x47\xdb\xb1\xb3\x5f\xdf\xf6\x1d\x24\xa1\xcf\xf1\xb6\xaf\
+\x2b\x25\xca\xc3\x8e\xa0\x0f\x0e\x36\x99\x2e\xd7\x0a\x04\xa6\x35\
+\x2d\x2d\xdf\x74\x97\x97\xae\xed\x1b\x5d\x59\x39\x97\xf7\xef\x1b\
+\xf1\xf2\xd0\x1d\xfa\x7b\xfb\xf6\x3d\x44\xc5\xf6\x7d\x52\x5d\xbd\
+\x0c\xc6\x1b\xf0\xc0\xfc\x81\x07\x3c\x0e\xf2\x9a\x95\xca\x01\x89\
+\x1a\x02\x32\xc7\x6b\xed\xf6\x3b\x65\x3c\x5e\x61\x3a\xb7\xaf\x8f\
+\x56\x7b\x32\x5d\xe6\x4f\xd6\xa6\x9e\x3d\x7f\x64\x8a\xf9\x13\xc2\
+\xe2\x3d\x94\x88\x93\x4f\x26\x8b\xcc\xa8\xab\x5b\x4b\xf5\xf6\xf9\
+\x8d\x86\x32\x18\x6f\xc0\x03\xf3\x07\x1e\xf0\x38\xc6\xbb\xde\xe9\
+\xbc\x3b\x59\x73\xd8\xd6\xbb\xf7\x6f\x77\x07\x02\x2f\xfb\x0d\xba\
+\x60\x3a\xb6\x6f\x4a\x4d\xcd\x2a\xba\xcd\x9f\xa9\x3c\x74\xa4\x8f\
+\x26\xf4\xb9\xc9\xe1\xb8\x0f\x9d\xa9\xa0\x63\xfb\x6e\xf2\xf9\x9e\
+\x85\xf1\x06\x3c\x2a\xcd\x3f\xe1\xa7\xff\x20\xd8\xc0\x03\x5e\xea\
+\xbc\xd7\x4b\x4a\xc6\x7e\x85\x27\xfd\x44\xf4\x25\x26\x64\x0a\xdb\
+\x31\x6d\xe9\xd3\xe7\xf0\x73\xd1\x92\x31\x51\xbd\xae\x21\xd5\xed\
+\x6b\xd5\x68\x4e\x24\x78\x84\xd0\xe7\x2f\x93\xd8\xa6\xae\xb6\x8f\
+\x2b\xbc\x7d\x7d\xfa\x1c\xa1\x73\xfb\x16\x34\x37\x7f\x6d\x34\xea\
+\xf3\x60\xbc\x01\x8f\x02\x1e\x31\xf5\x7f\xc2\x93\x04\x49\x20\xd8\
+\xc0\x03\x5e\x6a\xbc\xa5\x4d\x4d\xfb\xd2\x61\x5e\x9f\x54\x54\xcc\
+\x69\x2d\x2e\xee\xcf\x8b\x71\xc3\x60\x67\xdb\x37\xba\xb6\x66\x25\
+\x98\x3f\xf3\x79\x8d\x4a\x65\x1f\x9a\xfb\x73\x8e\x52\xc0\x57\xc0\
+\xf8\xe5\xbc\xf9\xe7\x26\x54\x00\x90\xde\x27\x2c\x85\x60\x03\x0f\
+\x78\xc9\xf3\xd0\x1c\xf7\xe9\x36\x9b\xd9\x0d\x0d\x9b\xd0\x5c\xfe\
+\x18\x9e\x1f\x6f\xfb\x9a\x8d\x86\x93\xc1\x5c\xd9\xc1\x1b\x11\x89\
+\x8c\xa2\xa1\x3f\x9f\x50\x55\x58\xd8\xf8\x98\xdf\xff\xf2\xda\xd6\
+\xd6\xef\x2f\x73\x39\xef\x81\xf1\xcb\x69\xf3\x27\xde\xf7\x13\xbb\
+\x00\xc0\x7f\x2c\xc2\x8f\xfe\xa5\x10\x6c\xe0\x01\x2f\x79\x5e\x83\
+\x5c\xde\x96\x29\xb3\x59\xd3\xda\xfa\xed\x35\x76\xfb\x5d\x32\x1e\
+\xaf\xa8\xab\xed\x1b\x55\x5d\xbd\x14\xcc\x95\x1d\xbc\xdd\xbd\x7b\
+\xff\x85\x6e\xfe\xa4\xa2\x3f\xa3\xe9\x8c\x1f\xf0\xf9\x9e\x5f\xd9\
+\xdc\xfc\x35\x79\xfb\xee\x0a\xf8\x47\xc0\xf8\xe5\xac\xf9\xf3\xf1\
+\xb7\xfd\xe6\xc5\x9c\xfa\x1f\xff\xb1\x00\x3f\xfa\x97\x90\xde\x2d\
+\x0c\xc1\x06\x1e\xf0\x92\xe0\x0d\xb1\xd9\x6e\xcf\xb4\xd9\x6c\xef\
+\xdd\xfb\xf7\x07\xbd\xde\x17\x4d\x7c\xbe\x95\xbc\x7d\x0d\x46\xc3\
+\x89\x60\xae\xec\xe2\x9d\x6f\x34\x5e\x99\xa9\xfe\x8c\x5e\xd4\x74\
+\xa7\xdb\xfd\xf8\xe2\xe6\xe6\x3d\x5d\x6d\x1f\xba\xdf\x04\xc6\x2f\
+\x27\x79\x02\x5c\xc7\x0b\x80\x78\x95\x82\x90\x54\x00\x48\x20\xd8\
+\xc0\x03\x5e\xf2\xbc\x97\x4b\x4a\x3e\xf9\xaa\x6f\xdf\xf6\xae\xf4\
+\x25\xa6\x1d\x98\xb6\xf7\xed\x73\x5c\x3b\xf0\xef\xbf\x4a\x52\x7b\
+\x7b\xf7\x3e\x3c\x3c\x12\xf9\xa4\x44\xa7\x6d\x44\xdb\xf5\x7e\x65\
+\xe5\xc2\xee\xf0\xd2\xbd\x7d\xc0\x8b\xcf\x9b\x58\x53\xb3\x2c\x9d\
+\xfd\xcf\x29\x91\x78\x6f\x72\x3a\x1f\x98\xdb\xd0\xb0\x35\x91\xed\
+\xfb\xa0\xaa\x6a\x11\x8c\x5f\xce\xf1\x44\xb8\x9f\x13\x05\x40\x6e\
+\xbc\x6b\x04\x02\x52\x01\x20\x86\x60\x03\x0f\x78\xa9\xf1\x16\x35\
+\x34\xec\xa4\xc3\x6c\xc6\xd6\xd4\xac\x06\x73\x65\x27\xcf\x21\x91\
+\xf8\xba\xd3\xff\x8c\x7c\xbe\x1d\x5d\x1a\x9a\x59\x5b\xbb\x3e\xd9\
+\xed\x9b\x51\x5f\xbf\x13\xc6\x2f\xa7\x78\x84\x87\x13\x05\x40\x5e\
+\xac\x53\xff\xb9\x78\x85\x40\x14\x00\x22\x08\x36\xf0\x80\x97\x1a\
+\xaf\x88\xc7\x93\xed\xeb\xdb\xf7\xe8\x97\x78\xb2\x25\xeb\x8b\x63\
+\x89\xb7\x6f\xfb\x36\x2c\xe9\x12\xda\x8e\x7f\xff\x65\x0a\x02\x1e\
+\x77\x78\xe8\x8d\x83\xc9\xf6\x3f\x34\xa5\xf1\x65\x56\xeb\xcd\x13\
+\xab\xab\x57\x74\x67\xfb\xb6\xf4\xea\xf5\x1b\x8c\x5f\xce\xf0\x88\
+\xb3\xf7\x44\x01\xc0\x8f\x65\xfe\x39\x78\x75\x90\x4f\xba\x5e\x00\
+\xc1\x06\x1e\xf0\x52\xe4\x55\x2a\x14\x4d\x60\x86\xc0\x4b\x96\xb7\
+\xba\xa5\xe5\x5b\xac\xfb\xe4\xc4\xeb\x7f\xba\xc2\x42\xed\x85\x26\
+\xd3\x35\x63\x2b\x2b\x17\x76\x55\x68\xa6\xb2\x7d\x8a\x63\x0f\xaf\
+\xc0\xf8\xe5\x00\x4f\x4a\x2a\x00\x04\xf1\x6e\xfa\x23\x17\x00\xfc\
+\x84\x67\x09\x82\x60\x03\x0f\x78\x9d\x2e\x97\x5a\xad\x37\x81\x19\
+\x02\x2f\x15\x1e\x3e\xdf\xc3\xbf\xfa\x9f\xc7\x62\xb2\x9c\x69\xb1\
+\x5c\xfb\x51\x79\xf9\xec\x3d\xbd\x7b\x1f\xce\xc4\xf6\x59\x0b\x0a\
+\x5c\x30\x7e\x39\xc1\x23\x0a\x00\x51\x4c\x3f\xc7\x57\xca\x21\x3d\
+\x23\x08\xe6\x0f\x3c\xe0\x75\x93\xf7\x62\x28\xf4\x01\x98\x21\xf0\
+\x52\xe1\xbd\x12\x89\x8c\x26\xfa\x5f\xd0\x69\x33\x9c\x62\xb5\x0c\
+\x79\xa5\xbc\x7c\xda\x96\x3e\x7d\x0e\x66\x7a\xfb\xd0\xbc\x00\x30\
+\x7e\x39\xc1\x93\x26\x74\x0f\x1f\xa9\x00\xc8\x05\xf3\x07\x1e\xf0\
+\xd2\xc3\x9b\x5d\x5f\xbf\x05\xcc\x10\x78\xa9\xf0\x76\xf5\xea\x75\
+\xe0\x54\x8b\x79\xc8\x4b\xa5\xd1\x09\xeb\x7b\xf7\xde\x4f\xe5\xf6\
+\x9d\xa4\xd5\x9e\x0d\xe3\x97\x13\xbc\xc4\x9e\xde\x23\x15\x00\x60\
+\xfe\xc0\x03\x5e\x7a\x78\xe2\xbd\xbd\x7b\x1f\x01\x33\x04\x1e\xdb\
+\x78\xe8\xd2\x15\x8c\xdf\x2c\xe2\xa5\x6a\xfc\x10\x6c\xe0\x01\xaf\
+\xf3\xa5\xb4\xa8\xa8\x16\xcc\x06\x78\x6c\xe4\xdd\xed\xf1\x3c\x05\
+\xf9\x20\x3b\x79\x10\x1c\xe0\x01\x2f\x0d\xbc\x8b\x4c\xa6\xeb\xf6\
+\xf5\xc1\x92\x2d\xa6\x6d\x24\xa1\xcf\xe8\xfb\x2f\x52\x10\xf0\x80\
+\x47\x05\xef\xf9\x50\xe8\x7d\xc8\x07\x60\xfe\x10\x1c\xe0\x01\x2f\
+\x45\xde\xd3\x81\xc0\xbb\x60\x36\xc0\x63\x23\x6f\x54\x59\xd9\x2c\
+\xc8\x07\x60\xfe\x10\x1c\xe0\x01\x2f\x45\xde\xb8\xba\xda\x8d\x60\
+\x36\xc0\x63\x23\x6f\x76\x5d\xdd\x16\xc8\x07\x60\xfe\x10\x1c\xe0\
+\x01\x2f\x05\x5e\x20\xe0\x55\x6e\xea\xdd\xfb\x10\x98\x0d\xf0\xd8\
+\xc8\xdb\xd8\xb3\xe7\xcf\x90\x0f\xc0\xfc\x21\xd8\xc0\x63\x33\x2f\
+\x9f\xae\xed\xab\xd0\x69\x5b\xc1\x6c\x80\xc7\x66\x1e\xd6\x9d\x85\
+\x90\x5f\xc0\xfc\x21\xd8\xc0\x63\x25\xef\xfd\xb2\xb2\x69\xcb\x9a\
+\x9a\xbe\x7c\xaf\xac\x6c\xea\x3d\x6e\xf7\xb3\x67\x1a\x0c\x97\x94\
+\x14\x15\x55\xa3\xf9\xf9\x33\xbd\x7d\x67\xdb\x6c\x37\x81\xd9\x00\
+\x8f\xcd\x3c\x8b\x58\xe4\x80\xfc\xc2\x6d\xf3\x4f\xf8\xe9\x3f\x08\
+\x36\xf0\xd8\xc6\x7b\x29\x1c\x1e\xd5\x55\x72\x5b\xd6\xd8\xf8\xd5\
+\x9b\x15\x15\xb3\xef\xf4\xfb\x5e\x3e\xd3\x6a\xb9\xae\xd2\xa0\xef\
+\xed\xb1\x98\x8c\xe9\xda\xbe\x07\x43\xa1\x77\xc1\x6c\x80\xc7\x66\
+\x5e\x85\x5e\xd7\x0b\xf2\x0b\x67\x79\xc4\xd4\xff\x09\x4f\x12\x24\
+\x81\x60\x03\x8f\x4d\xbc\x47\x7c\xbe\x97\xf7\xe1\xc9\x8c\xac\xbd\
+\x78\x82\xdc\x4a\xd2\x36\xfc\xfb\xe5\xcd\xcd\xdf\x7c\x50\x56\x36\
+\xf3\x7e\x8f\xe7\x85\x73\x8d\xc6\x2b\x4a\x8b\x8a\xea\x64\xc7\x5e\
+\xea\x97\xdc\xf6\x8d\xad\xad\x59\xbb\xb7\x93\xbf\x9d\x88\x62\x6d\
+\x1f\xf0\x80\x47\x15\xaf\x9f\xd9\x74\x01\xe4\x17\xce\x9a\x7f\x6e\
+\x42\x05\x00\xe9\x7d\xc2\x52\x08\x36\xf0\xd8\xc4\xbb\xd5\xe5\x7a\
+\x34\x5d\xc9\x72\x55\x73\xf3\x7f\x3e\x2a\x2b\x9b\xf5\xa0\xd7\x3b\
+\x6c\xb0\xd1\x78\x65\x79\x61\x61\x43\x01\x8f\xa7\xe8\x6c\xfb\x22\
+\x91\xa0\x62\x47\x5b\xdb\x01\x30\x9b\xee\xf3\x76\xf7\xea\x75\x64\
+\x27\x16\xcb\x3d\x7d\xfa\x1c\x85\xf8\x51\xcb\x3b\xcf\x6e\xbb\x1d\
+\xf2\x0b\x27\xcd\x9f\x78\xdf\x4f\xec\x02\x00\xff\xb1\x08\x3f\xfa\
+\x97\x42\xb0\x81\xc7\x26\xde\x65\x16\xcb\x2d\xfb\x7a\xf7\x6e\x27\
+\xb4\x17\xd3\x36\x4c\x5b\x49\xda\x86\x7f\xbf\x2f\x05\xa1\xf5\x16\
+\x35\x35\x7d\xff\x6e\x45\xc5\x82\x7b\x02\xfe\xd7\xcf\xb1\x5a\x6f\
+\xae\x36\xe8\xfb\x35\x14\x2b\x7a\xa7\xca\x4b\xf7\xf6\x31\x85\x87\
+\x15\x50\xdf\x8f\xab\xac\x5c\x36\x3c\x14\xfa\x18\xdd\x8f\x71\xad\
+\xd5\x3a\xf4\x5c\x93\x69\x48\x6f\x93\xe9\xbc\x1a\xa3\xa1\x7f\x58\
+\xa7\xad\xf1\x18\x0d\x01\x8f\xd5\x6c\x57\x0a\xf8\x0a\xac\xf9\x44\
+\x98\x72\x3b\x34\x29\xfa\x2c\x46\x67\x64\x94\x22\x91\x46\x2b\x10\
+\x98\xfc\x22\x51\xb8\x5a\xa1\x68\xe9\xaf\xd1\x9c\x3e\xd8\x64\xba\
+\x6a\x88\xcb\xf9\x10\xba\xac\xf3\x5c\x49\xe4\xf3\xd1\x35\x35\x6b\
+\x96\x34\x37\xff\xc8\x85\xf8\xd1\xc1\xbb\xd1\xe3\x79\x16\xf2\x0b\
+\xe7\xcc\x9f\x8f\xbf\xed\x37\x2f\xe6\xd4\xff\xf8\x8f\x05\xf8\xd1\
+\xbf\x84\xf4\x6e\x61\x08\x36\xf0\x58\xc1\x43\x37\xfd\x41\x32\xa7\
+\x8e\xb7\xa3\x67\xcf\xbf\x26\x55\x55\xad\x7e\x3a\x10\x78\xe7\x52\
+\xb3\xf9\xa6\xa6\xe2\xe2\xfe\x76\xb1\xd8\x8f\x4c\x9b\xce\xfe\x82\
+\xde\x6d\xef\x12\x8b\x43\x3d\x55\xaa\x13\xaf\xb0\x58\x6e\x43\xb3\
+\xdc\x4d\xad\xa9\x59\xbf\xab\x57\xaf\x83\xd0\xbe\x5d\xf3\x1e\x0d\
+\x85\x3e\x80\xfc\xc2\x29\x9e\x00\xd7\xf1\x02\x20\x5e\xa5\x20\x24\
+\x15\x00\x12\x08\x36\xf0\xd8\xc4\xeb\xad\x54\x9e\x02\xc9\x3c\x33\
+\xbc\x8d\x3d\x7b\x1e\x18\x5b\x59\xb9\xe4\x2e\x97\xeb\xa9\x13\x75\
+\xba\xb3\x1c\x12\x89\xaf\x93\x23\x76\xa6\xf7\x97\x3c\x54\x18\x9c\
+\xa4\xd1\x0c\x46\xf7\x7c\x4c\xac\xae\x5e\x89\x15\x05\x87\xa0\x7d\
+\xff\xfe\xfc\x6e\x59\xd9\x74\xc8\x2f\x9c\xe1\x89\x70\x3f\x27\x0a\
+\x80\xdc\x78\xd7\x08\x04\xa4\x02\x40\x0c\xc1\x06\x1e\xdb\x78\x95\
+\x0a\x45\x13\x24\xf3\xf4\xf0\x56\xb4\xb4\xfc\x3a\xa2\x34\x3a\xf5\
+\x32\x97\xeb\xfe\x6a\x9d\xb6\xb7\x4c\x26\x15\x71\xb1\xff\x89\xc5\
+\xe2\x82\x5a\x83\x7e\xc0\x95\x2e\xe7\x23\xaf\x95\x97\xcf\x5a\xd3\
+\xda\xfa\x67\xb6\xf6\x97\xe9\xb5\xb5\x1b\x20\xbf\x70\x82\x47\x78\
+\x38\x51\x00\xe4\xc5\x3a\xf5\x9f\x8b\x57\x08\x44\x01\x20\x82\x60\
+\x03\x8f\x8d\xbc\x40\x81\xa4\x04\xcc\x3f\x75\xde\xe4\xea\xea\xb5\
+\x37\x7b\x3c\xcf\xd6\xe8\x75\xfd\xcb\xca\x4a\x8a\xb3\xb1\xff\x85\
+\x42\xfe\xe2\xfa\x62\x45\x1f\x74\xdf\xc2\xac\xba\xba\x2d\xd9\xd4\
+\x5f\xd6\x34\x37\xff\x00\xf9\x85\xf5\x3c\xe2\xec\x3d\x51\x00\xf0\
+\x63\x99\x7f\x0e\x5e\x1d\xe4\x93\xae\x17\x40\xb0\x81\xc7\x4a\x9e\
+\xc7\xa0\xf7\x6e\xc1\x12\x19\x21\x94\xe0\xf6\xe0\x09\x2e\x59\xed\
+\xc1\xd7\xe7\x32\x6f\x67\xaf\x5e\x07\xdf\x8e\x46\x27\xa3\x7b\x27\
+\xd4\xd2\x02\x03\xf4\xbf\x7f\xf3\xcc\x7c\xbe\xe5\x3c\x83\xe1\xaa\
+\x0f\xcb\xca\x66\xef\x6a\x6b\x3b\xc2\xf5\xfe\xc2\x4b\x60\x36\x4d\
+\xc8\x57\x8c\xe6\x49\x49\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\
+\xf0\x2c\x41\x10\x6c\xe0\x31\x90\x17\x71\x3b\xf5\x60\xfe\x71\x4c\
+\xbf\xad\xed\xf0\x7b\x65\x65\xd3\x91\xe9\x4b\x79\x3c\x39\xf4\xbf\
+\xc4\x79\xe8\x49\x84\xf3\x4d\xa6\x6b\x3e\x2e\x2f\x9f\xbf\xbb\x77\
+\xef\xa3\x5c\xec\x2f\xe8\x49\x0b\xc8\x2f\xac\xe6\x11\x05\x80\x28\
+\xa6\x9f\xe3\x2b\xe5\x90\x9e\x11\x04\xf3\x07\x1e\xeb\x79\x1b\xb0\
+\xa3\x5a\x30\xff\x7f\x6b\x72\x75\xf5\x9a\xc1\x26\xd3\xd5\x12\xcc\
+\xc7\xa0\xbf\x74\x9f\x67\x10\x0a\xf5\xe8\xb1\xd3\x59\x75\x75\x5b\
+\xb9\xd4\x5f\xa2\x72\x79\x55\x06\xe3\x27\x50\x08\x85\x3a\x8f\x58\
+\x1c\xa8\x28\x2c\xac\x6f\x55\xa9\x06\x9d\xae\xd7\x5f\x74\xa1\xdd\
+\x7e\xd7\xb5\x6e\xd7\x93\x2e\x8b\xc9\x03\xfd\xaf\xdb\x3c\x69\x42\
+\xf7\xf0\x91\x0a\x80\x5c\x30\x7f\xe0\x71\x85\xb7\xb0\xa9\xe9\x7b\
+\x30\xff\xbf\xb5\xa9\x67\xcf\xdf\x1e\xf3\xfb\x5f\x8b\x48\xa5\xe5\
+\xd0\x5f\x32\xc7\xab\xd2\xeb\xfa\x3d\x16\x0e\x7f\xbc\xa6\xad\x6d\
+\x3f\xdb\x8b\xc5\x3e\x2a\xd5\xc9\xb1\xf6\xb7\xaa\xaa\xbc\xc8\x65\
+\x36\x59\x03\x5a\x4d\xb4\x4c\xaf\x6b\x69\x32\x1a\x4e\x19\xa8\xd3\
+\x9d\x83\x8a\xcb\xeb\x6c\xb6\x7b\xd0\xbd\x13\xe8\xb1\xd0\xd7\x23\
+\x91\x71\x9f\x96\x97\x2f\x98\x5e\x53\xb3\x71\x59\x53\xd3\x37\x5b\
+\xb1\xd8\xc4\xdb\xbe\x80\xdd\x6a\x84\xfe\xd7\x6d\x5e\x62\x4f\xef\
+\x91\x0a\x00\x30\x7f\xe0\x71\x86\x37\xab\xb6\x76\xeb\xde\x5e\xbd\
+\xda\x93\xd5\x1e\x4c\x5b\x31\x6d\x21\x69\x2b\xfe\x3d\xdb\x78\x53\
+\x6b\x6a\x36\x9c\xa3\xd7\x5f\x8e\x1d\xea\x4b\xa0\xbf\x50\xc7\x73\
+\x9b\x8d\xa6\x0b\xcd\xe6\xeb\xe7\xd6\xd5\xed\x60\x6b\xff\x7b\x25\
+\x12\x19\x7b\x9b\xd3\xf9\xd8\x23\x5e\xef\xab\x2f\x85\x42\x9f\x7c\
+\x50\x5a\x3a\x73\x62\x65\xe5\xaa\xf9\x75\x75\x7b\x56\x34\x37\xff\
+\xb2\xa9\x57\xaf\xa3\x99\xd8\xbe\x39\x0d\x0d\xdf\x40\xff\xa3\x90\
+\x97\xaa\xf1\x43\xb0\x81\xc7\x64\xde\x98\x8a\x8a\xc5\xd9\x6a\xfe\
+\xef\x45\xa3\x33\x1a\x95\xca\x3e\xd0\x5f\x68\xe7\x9d\x80\xe6\xa4\
+\x48\xb4\x2f\x72\xa9\xf8\x4c\x95\xf7\x46\x69\xe9\x6c\xe8\x7f\xf4\
+\xf0\x20\x38\xc0\xe3\x0c\xef\xed\x92\x92\x49\xd9\x64\xfe\x3b\xdb\
+\xda\x0e\x3e\x1b\x0c\xbe\x87\xa6\xc8\x85\xfe\xc2\x3c\x1e\x7a\x1d\
+\xf5\xcb\xe1\xf0\x98\x5d\x3d\x7b\x1e\x01\xf3\xef\x9a\x77\x8f\xdb\
+\xfd\x3c\xf4\x17\x30\x7f\xe0\x01\xaf\x5b\xbc\xe7\x82\xc1\xf7\xf7\
+\xe0\x89\x26\x9e\x76\xe3\x09\x68\x33\x49\x5b\xf0\xef\xf7\xa4\x20\
+\x2a\x79\x3b\xda\xda\x0e\x3d\xee\xf3\xbd\x89\x1e\x53\x83\xfe\xc2\
+\x7c\x9e\x53\x22\xf1\x0e\x0b\x06\x3f\xde\xd5\xab\xd7\x51\x2e\xf4\
+\xbf\x74\xf3\xce\xd2\xeb\x2f\x83\xfe\x02\xe6\x0f\x3c\xe0\x75\x8b\
+\x77\x9f\xc7\xf3\x22\x97\x93\xe5\x0e\xec\x48\x12\x15\x39\xb6\xfc\
+\x7c\x27\xf4\x17\xf6\xf1\xd0\x99\x9a\x57\xc3\xe1\xcf\xc1\xfc\xff\
+\x97\x57\x56\x54\x54\x03\xfd\x05\xcc\x1f\x78\xc0\xeb\x16\xef\x06\
+\xbb\xfd\x01\xae\x26\xcb\xe1\xe1\xf0\x58\x7c\x0e\x7e\xe8\x2f\x2c\
+\xe7\x95\xe8\xb4\x8d\xaf\x97\x96\xce\x06\xf3\xff\x5b\x32\x1e\xaf\
+\x10\xfa\x0b\x98\x3f\xf0\x80\xd7\x2d\xde\x45\x66\xf3\x0d\x5c\x4b\
+\x96\x63\xab\xab\xd7\x57\xc8\xe5\x4d\xd0\xbe\xdc\xe3\xb5\x1a\x0c\
+\x67\x4e\xae\xab\xdd\x91\xcd\xe6\xbf\xb4\xb1\xf1\x1b\xe8\x2f\x60\
+\xfe\xc0\x03\x5e\xb7\x79\x27\xeb\xf5\xe7\x73\x25\x59\xce\x6f\x6c\
+\xfc\xfe\x74\x8b\xe5\x5a\xbb\xdd\x9a\x0b\xed\xcb\x5d\x9e\x52\xa9\
+\xe0\x5f\x66\x36\xdf\xbc\xbe\xb9\xf9\xe7\x6c\x33\xff\x3d\xf8\xd3\
+\x2b\xd0\x5f\xa8\x31\xff\x84\x9f\xfe\x83\x60\x03\x8f\x8d\xbc\x16\
+\x85\x62\xe0\x9e\xb6\xb6\xf6\x8e\xda\x8d\x69\x0b\xa6\xcd\x24\x6d\
+\xc1\xbf\xdf\x93\x82\x32\xc9\x5b\xdf\xda\x7a\xf8\x16\x8f\xe7\x25\
+\xbf\xdd\xaa\x87\xf6\xcd\x1e\x1e\x9a\xa5\xf1\x71\x9f\xef\xf5\x5d\
+\x6d\x6d\x47\xe9\xec\x7f\x54\xf3\xee\x75\xb9\x5e\x80\xfe\x92\x71\
+\x1e\x31\xf5\x7f\xc2\x93\x04\x49\x20\xd8\xc0\x63\x1b\xaf\xb4\xa8\
+\xa8\x96\xcd\xc9\x72\x4c\x55\xd5\xfa\x52\x83\xbe\x01\xda\x37\x7b\
+\x79\x55\x85\x85\x8d\xb3\x6b\x6a\xb6\x65\x83\xf9\x23\xa1\x49\xab\
+\xa0\xbf\x64\xdc\xfc\x73\x13\x2a\x00\x48\xef\x13\x96\x42\xb0\x81\
+\xc7\x36\x1e\xba\x49\x6e\x37\x9e\x6c\x90\x76\xe1\x09\x68\x13\x49\
+\x9b\xf1\xef\x77\xa7\xa0\x4c\xf1\x56\xb5\xb6\xee\xbf\xd4\xe1\xb8\
+\xaf\xb2\xb2\x54\x01\xed\x0b\x3c\x6c\x11\xdc\xe2\x70\x3c\xba\xa3\
+\x67\xcf\x43\x6c\xec\xcf\xc9\xf0\xb0\xa2\xbd\x0e\xfa\x4b\x46\xcd\
+\x9f\x78\xdf\x4f\xec\x02\x00\xff\xb1\x08\x3f\xfa\x97\x42\xb0\x81\
+\xc7\x36\x5e\xb1\x48\xa4\x65\x5b\xb2\x7c\xbb\xac\x6c\xa1\x47\xa7\
+\x2d\x81\xf6\x05\x5e\xc7\x05\x3d\x36\x38\xae\xb2\x72\x25\x57\xcd\
+\x1f\x89\x78\x2b\x25\xf4\x97\x8c\x98\x3f\x1f\x7f\xdb\x6f\x5e\xcc\
+\xa9\xff\xf1\x1f\x0b\xf0\xa3\x7f\x09\xe9\xdd\xc2\x10\x6c\xe0\xb1\
+\x89\xc7\x67\x4b\xb2\x5c\xd7\xd2\x72\xe0\x62\x87\xe3\x9e\xa6\xa6\
+\xba\x42\x68\x5f\xe0\xc5\x58\xf2\x6e\x76\x3a\x1e\xdb\xd0\xda\x7a\
+\x84\x6b\xe6\xbf\xb4\xa1\xe1\x5b\x68\xdf\x8c\xf1\x04\xb8\x8e\x17\
+\x00\xf1\x2a\x05\x21\xa9\x00\x90\x40\xb0\x81\xc7\x46\xde\xc6\xd6\
+\xd6\x3f\x99\x6e\xfe\x13\x6b\x6b\xb6\x45\x34\xea\x7a\x68\x5f\xe0\
+\x25\xca\x43\x6f\x1d\x9c\x51\x5f\xff\x25\x57\xcc\x1f\xe9\xfd\xd2\
+\xd2\x59\xd0\xbe\x19\xe1\x89\x70\x3f\x27\x0a\x80\xdc\x78\xd7\x08\
+\x04\xa4\x02\x40\x0c\xc1\x06\x1e\x5b\x79\xb3\xea\xeb\xbf\x66\xb2\
+\xf9\xdf\xe3\xf3\xbd\x11\x08\x78\x35\xd0\xbe\xc0\x4b\x96\xe7\xb3\
+\x59\x4c\x4f\xa1\x77\x0b\x70\xc0\xfc\x91\xee\x77\xbb\x87\x41\xfb\
+\xa6\x9d\x47\x78\x38\x51\x00\xe4\xc5\x3a\xf5\x9f\x8b\x57\x08\x44\
+\x01\x20\x82\x60\x03\x8f\xcd\xbc\xcf\xaa\xab\x37\x33\xd1\xfc\xd7\
+\x35\x37\xff\xda\xdb\xa0\x3f\x17\xda\x17\x78\xdd\xe5\x9d\xa6\xd5\
+\x5e\xb2\xa5\xa5\xe5\x2f\x36\x9b\x3f\xd2\x39\x3a\xdd\x10\x68\xdf\
+\xb4\xf2\x88\xb3\xf7\x44\x01\xc0\x8f\x65\xfe\x39\x78\x75\x90\x4f\
+\xba\x5e\x00\xc1\x06\x1e\xab\x79\xef\x96\x97\x2f\x66\x9a\xf9\x4f\
+\xad\xa9\xd9\xec\x33\xe8\x4b\xa1\x7d\x81\x97\x2e\x5e\x44\x2a\x2d\
+\x5f\x58\x5f\xbf\x8f\xad\xe6\x8f\x54\x5e\x58\xd8\x00\xed\x9b\x56\
+\x9e\x94\x54\x00\x08\xe2\xdd\xf4\x47\x2e\x00\xf8\x09\xcf\x12\x04\
+\xc1\x06\x1e\xc3\x78\x4d\x4d\x75\x32\x9f\xdd\xe6\x28\xd1\x6a\x6a\
+\x47\x56\x54\x2c\xdd\xd4\xb3\x67\xfb\x4e\x4c\xbb\x52\x10\x5a\x0f\
+\xad\xbf\x91\xa4\xee\xf0\x5e\x0c\x85\x46\x3b\x9d\x76\x2d\xb4\x2f\
+\xf0\xd2\xcd\x93\xf0\x78\xc5\x23\xa3\xd1\x99\x54\xf6\xe7\x74\xf2\
+\x0a\x78\x3c\x05\xb4\x6f\x5a\x79\x44\x01\x20\x8a\xe9\xe7\xf8\x4a\
+\x39\xa4\x67\x04\xc1\xfc\x81\xc7\x44\x5e\x8e\x42\x28\xd4\x05\x65\
+\xb2\xd2\xd6\xe2\xe2\x01\xe8\xb5\xa1\xd7\xd9\x6c\xf7\x3d\xec\xf1\
+\xbc\xf2\x4a\x38\x3c\x6e\x6c\x45\xc5\x72\x74\xcd\x7f\x5d\x6b\xeb\
+\x21\xa6\x25\xb7\xed\x2d\x2d\x47\x2e\x35\x9b\x6f\x87\xf6\x05\x5e\
+\x86\x79\x39\xb7\xd9\xed\x8f\xb3\xcd\xfc\x97\x37\x34\x7c\x07\xed\
+\x9b\x76\x9e\x34\xa1\x7b\xf8\x48\x05\x40\x2e\x98\x3f\xf0\xa8\xe6\
+\x95\x95\x95\x14\x7b\x4d\x06\x7f\xa9\x46\xd3\xd2\x4b\xad\x3e\xf9\
+\x6c\xa3\xf1\xca\x1b\xed\xf6\x07\x1f\xf1\x7a\x5f\x7f\x23\x1c\x9e\
+\x34\xa1\xa2\x62\xf5\x92\xfa\xfa\x6f\x91\x89\xb2\xf1\xc8\x66\x63\
+\x53\xd3\x1f\x3d\x8b\x8b\x07\x41\x7f\x39\xce\xcb\x31\xf3\xf9\x96\
+\xf2\xa2\xa2\xea\x36\xb5\x7a\xe0\x99\x46\xe3\x85\x57\xda\xed\xb7\
+\xdc\xe9\x76\x3f\xfa\x90\xcf\xf7\xfc\x63\x81\xc0\x88\x67\xc3\xe1\
+\x37\x5e\x8c\x44\x46\x8e\x88\x46\x3f\x44\xff\x7d\x26\x1c\x7e\xf3\
+\x81\x50\xe8\xf5\xbb\x83\x81\x11\x37\xf9\xfd\x4f\x5f\xec\x74\xde\
+\x75\xaa\xcd\x36\xa4\x55\xa3\x19\x18\x95\xcb\x2b\xb5\x02\x81\x09\
+\xe3\x9e\x00\xe3\xed\xef\xe5\x14\x9d\xee\xc2\x6d\xad\xad\x07\xd9\
+\x30\x3e\x90\xde\x8f\x46\xe7\xc2\xf8\x48\x3b\x2f\xb1\xa7\xf7\x48\
+\x05\x00\x98\x3f\xf0\xd2\xce\x43\x13\xf2\xf4\x52\x2a\x4f\x3d\xcf\
+\x60\xb8\x16\xcd\x68\xf6\xb8\xd7\xfb\xce\x2b\x25\x25\x33\xc7\x56\
+\x55\x6d\x9a\xdf\xd0\xf0\xc3\xfa\x9e\x3d\x8f\x32\x29\x19\xa5\x93\
+\x87\x0a\x97\x60\x61\x61\x45\x36\xf6\x17\x8f\xd5\xe2\x6a\x31\x19\
+\x4f\xbe\xd4\xed\x1e\xfa\x78\x49\x64\xe4\xe8\xea\xea\xb9\x2b\x5a\
+\x5b\xf7\x7c\x39\x60\xc0\xa1\xff\x0c\x1c\xd8\x9e\xa8\xbe\xc5\xb4\
+\x67\xe0\x80\xf6\xdd\x03\xfe\x11\xfa\xfc\x6d\x87\xdf\x7d\xd9\xbf\
+\xff\xc1\xa5\x2d\x2d\xbb\x3e\xa9\xaa\x9a\xf9\x74\x28\xf4\xda\xe5\
+\x36\xdb\x0d\xf5\x0a\x45\x0b\x3a\xb5\x9c\x8d\xe3\xb7\x5a\xa1\x68\
+\x5d\xdb\xd8\xf8\x33\xd3\xcd\x1f\xad\x77\x97\xcf\xfb\x1a\xe4\x53\
+\x9a\x78\xa9\x1a\x3f\x04\x1b\x78\x89\x2c\x1e\xb1\x38\xc0\x96\x23\
+\x91\x74\xf2\xa6\x55\x57\x6f\xd2\x4b\x24\xd6\x6c\xe8\x2f\x4a\x1e\
+\x4f\x52\x87\x19\xce\x0d\x6e\xd7\x7d\xef\x60\x06\xbc\xbc\x57\xaf\
+\xef\x77\x61\x46\x8d\xb4\x1b\x33\xeb\x6f\x70\x23\x4f\x56\x68\x3d\
+\xb4\x3e\xc1\x4a\x95\xb7\xb6\x67\xcf\x6f\x46\x96\x97\x4f\xbc\xc6\
+\xe5\xbc\xab\xd6\x68\xe8\x93\x2d\x8f\x5e\x3a\xa5\xd2\xe0\xb4\xba\
+\xba\x2f\x98\x3e\xde\xce\xb2\x5a\x6f\x86\x7c\x4a\x3f\x0f\x82\x03\
+\xbc\x8c\xf0\xa6\x56\x55\x6d\xcc\x26\xf3\xff\x30\x1a\x9d\xab\x14\
+\x08\xe4\x1c\x6e\xdf\xbc\x5a\x85\xa2\xe9\x6e\x8f\xe7\x89\xe9\x75\
+\x75\xab\xbe\xec\xd7\xef\xf0\x37\xc7\x8e\xce\xfb\x63\x26\xfd\x8f\
+\xd0\x67\xf4\xfd\xb7\x29\x28\x93\xbc\xad\xfd\xfb\x1d\x1c\x5d\x57\
+\xbb\xe2\x46\xaf\xf7\xc9\x72\x85\x02\xcd\x3f\x9f\xc3\xe5\x33\x31\
+\x9f\x56\x56\xae\x63\xf2\x78\xab\xd1\xeb\xfa\x43\x3e\x05\xf3\x07\
+\x1e\x47\x79\x57\x5b\xad\xf7\x66\x8b\xf9\xa3\xfb\x16\x64\x32\xa9\
+\x88\x6b\xed\x2b\xe3\xf1\x8a\xce\x30\x18\x2e\x7a\xab\xb4\x74\xec\
+\x8e\xde\xbd\x7f\xa5\xca\xac\xa9\xe0\x6d\xe9\xdd\xfb\xc7\x97\xa3\
+\xd1\x51\xa7\x18\x0c\xe7\x28\x78\xbc\x02\xae\x8d\xdf\x80\xdd\x6a\
+\x7c\xaf\xa2\x62\x29\x53\xc7\x9b\xc3\x64\xb0\x43\x3e\x05\xf3\x07\
+\x1e\x47\x79\x7e\xb5\x2a\xba\x01\x1b\xe8\x84\xd0\xa0\xdf\x81\x27\
+\x82\x64\xb5\x03\x5f\x9f\x89\xbc\x97\x82\xc1\xd1\x72\x79\x91\x80\
+\x2b\xed\x2b\xe7\xf1\xa4\xa7\xe9\xf5\xe7\xbf\x57\x5e\x3e\xe9\x8b\
+\xfe\xfd\x0f\x32\xc1\xac\x33\xcd\xdb\xdb\xaf\xdf\xfe\x37\x4b\x4b\
+\xc7\x0c\xd2\xe9\xce\xc4\x42\x20\xe2\xca\xf8\x15\x8b\xc5\x05\xef\
+\x95\x94\xcc\x62\xda\x78\x9b\xd7\xd0\xf0\x03\xe4\x53\x30\x7f\xe0\
+\x71\x9c\xf7\x49\x65\xe5\x3a\x2e\x9b\xff\xd3\x3e\xdf\x48\xa3\x51\
+\x9f\xc7\x85\xf6\xad\x2e\x2c\x6c\x18\x56\x52\xf2\xfe\x6e\xcc\x0c\
+\xbf\xc1\x4d\xb4\x33\x7d\x7d\xec\xba\x7c\xff\xf6\x9d\x24\xa1\xcf\
+\x5f\xc7\x58\x87\x4d\xbc\x1d\x7d\xfa\xfc\xf6\x54\x28\xf4\x7a\x58\
+\x2a\xad\xe0\xc8\xf8\x15\xbc\x16\x89\x4c\x64\xd2\x78\x7b\xbb\xb4\
+\x74\x01\xe4\x53\x30\x7f\xe0\x71\x9c\x77\xb1\xcd\x7a\xdf\xc6\xd6\
+\xd6\xf6\x1d\x98\x76\xa6\x20\xb4\x1e\x5a\x7f\x03\x49\x4c\xe1\x3d\
+\xec\x76\xbf\x16\x0e\x07\x72\xd8\xdc\xbe\xe8\x14\xff\x10\xab\xf5\
+\xe6\xf9\x8d\x8d\x5b\xd9\x68\xd6\x99\xe6\x4d\x6f\x68\x58\x7b\xbe\
+\xdd\x76\x73\xc4\xed\xd4\xb3\x7c\xfc\xe6\xbd\x14\x08\x8c\x66\xca\
+\x78\xbb\xdf\xed\x7e\x19\xf2\x29\x98\x3f\xf0\x38\xce\xf3\xeb\xb4\
+\x01\x2e\x9a\xff\x53\x3e\xdf\xbb\x6c\x36\x7f\x13\x9f\x6f\x7d\xd8\
+\xef\x7f\x71\x67\x9f\x3e\xbf\x7f\xd3\xbf\x7f\x7b\x22\xfa\x1a\xd3\
+\xae\xfe\xfd\xda\x77\x92\x84\x3e\x7f\x9d\xe0\xfa\x6c\xe6\xad\xee\
+\xd5\xeb\xe7\x5b\x7d\xbe\xe7\x9c\x26\xa3\x8b\xc5\xe3\x37\xef\xd5\
+\x70\x78\x02\x13\xc6\xdb\x60\x83\xe1\x1a\xc8\xa7\xd4\x9b\x7f\xc2\
+\x4f\xff\x41\xb0\x81\x97\x2e\xde\x98\xf2\xf2\xa5\x5c\x32\xff\x61\
+\xc1\xe0\x27\x6c\x3d\xed\x1f\x90\xcb\x4b\x5e\x2d\x29\xf9\xe4\x8b\
+\xbe\x7d\x0f\x73\xd5\xac\x33\xc9\xdb\xdb\xa7\xcf\x81\xe7\x42\xa1\
+\x77\xac\x05\x05\x2e\x96\x8e\x5f\xc1\xbb\x91\xc8\x4c\xba\xc7\x5b\
+\xad\x5c\xde\x06\xf9\x94\x52\x1e\x31\xf5\x7f\xc2\x93\x04\x49\x20\
+\xd8\xc0\x4b\x07\xef\x22\xa3\xf1\x06\xae\x98\xff\x6b\xa1\xd0\x44\
+\xa5\x52\xc1\x67\x5b\x7b\x04\x74\xda\xf2\x57\xa3\xd1\x31\x5f\xf6\
+\xef\x7f\xf4\x6b\xdc\xe0\x12\xd5\x57\x98\x90\x01\xee\x20\x09\x7d\
+\xfe\x2a\x49\x0e\x97\x78\xfb\xb0\x02\x0a\x15\x02\x26\x3e\xdf\xc6\
+\xc2\xf1\x2b\xfe\xb8\xb4\x74\x11\x9d\xe3\x6d\x5b\x4b\xcb\xa1\x89\
+\x15\x15\xeb\x9f\xf4\x7a\xdf\xb9\xc0\x64\xba\xae\xb4\xa8\xa8\x16\
+\x6d\x17\xe4\xd3\x8c\x99\x7f\x6e\x42\x05\x00\xe9\x7d\xc2\x52\x08\
+\x36\xf0\xd2\xc1\x43\x73\xf9\x6f\x6b\x6e\x3e\xc2\x76\xf3\x47\x77\
+\x53\xb3\xed\x51\x3f\xbf\x5e\x17\x7a\xb6\x34\xfa\x09\x66\x58\x47\
+\xb2\xd5\xac\x33\xc9\xdb\xd7\xaf\xdf\xc1\x27\x02\x81\xd7\xd0\xcc\
+\x97\x6c\x1a\xbf\x45\x3c\x9e\xec\xf3\xf2\xf2\x55\x4c\x1a\x6f\x28\
+\x47\x4c\xab\xac\xdc\xf2\xac\xdf\xff\xc1\xc5\x46\xe3\x4d\x95\x32\
+\x59\x93\x56\x28\x2c\x84\x7c\xda\x6d\xf3\x27\xde\xf7\x13\xbb\x00\
+\xc0\x7f\x2c\xc2\x8f\xfe\xa5\x10\x6c\xe0\xa5\x8b\xf7\x61\x69\xe9\
+\xbc\x1d\x78\x22\xe8\x4a\xdb\xf1\x84\xb1\x9e\xa4\x0d\xf8\xf7\x3b\
+\x52\x50\x3a\x79\x13\x2a\x2a\xd6\xe9\x44\x42\x19\x5b\xda\x03\xdd\
+\xb4\x76\x93\xcf\xf7\xec\xe6\xde\xbd\xff\xfa\xaa\x5f\xbf\xf6\xaf\
+\x53\x10\x5a\x6f\x67\xbf\xbe\xed\x3b\x48\x42\x9f\x81\xf7\x6f\x6d\
+\xef\xdd\xfb\xb7\xeb\xec\xf6\xbb\xb0\x66\xe0\xb3\x65\xfc\xea\x0a\
+\x0b\xb5\x53\xeb\x6a\xf7\x31\x71\xbc\x11\xbc\x75\xad\xad\x47\x27\
+\xd4\xd6\xec\x7a\x22\x18\x1c\x8b\x6e\x28\xae\xd7\xeb\x06\x15\xf3\
+\xf9\xc5\x90\x9f\x13\x36\x7f\x3e\xfe\xb6\xdf\xbc\x98\x53\xff\xe3\
+\x3f\x16\xe0\x47\xff\x12\xd2\xbb\x85\xc1\x0c\x81\xd7\x6d\xde\xb9\
+\x06\xc3\x55\x6c\x35\xff\x05\x75\x75\x5f\x99\x0a\x0a\x4c\x6c\x68\
+\x8f\xa6\xa6\xba\xc2\x53\xed\xb6\xab\x16\xb7\xb4\x7c\x0b\x66\x4d\
+\x3d\x6f\x71\x53\xd3\xee\x7e\x1a\xcd\x29\x6c\x19\xbf\x21\x83\xbe\
+\x62\x41\x43\xc3\xcf\x4c\x34\xff\x58\xbc\xb9\xb5\xb5\x7b\x87\x63\
+\x45\xc1\x55\x16\xcb\xdd\x0d\x45\x45\x7d\xd5\x62\xb1\x0a\xf2\xf3\
+\xbf\x78\x02\x5c\xc7\x0b\x80\x78\x95\x82\x90\x54\x00\x48\xc0\xbc\
+\x80\x97\x2e\x1e\x1a\xa0\x5b\x9a\x9b\x0f\xb3\xcd\xfc\xd7\x34\x35\
+\xfd\xea\x2a\x90\x94\xb0\xa1\x3d\xfc\x46\x43\xe9\x07\x55\x55\x8b\
+\xb6\xe3\x06\xf6\x25\x6e\x6c\xc9\x0a\xad\x87\xd6\xdf\x4e\x12\xf0\
+\x92\xe3\xbd\x52\x5e\x3e\x05\xbd\xed\x92\x0d\xe3\xb7\x5a\xa7\x1d\
+\xb0\xba\xa9\xe9\x00\x5b\xcc\xbf\x2b\x2d\xaa\xab\xfb\xfa\xd5\x70\
+\x78\xfc\xb5\x56\xeb\xfd\xcd\x0a\xc5\x89\x7a\xa1\xd0\x90\xc5\xf9\
+\x59\x84\xfb\x39\x51\x00\xe4\xc6\xbb\x46\x20\x20\x15\x00\x62\x30\
+\x2f\xe0\xa5\x9b\xf7\x4e\x38\x3c\x63\x47\x4b\x4b\x3b\x59\xdb\x31\
+\x6d\xc0\xb4\x9e\xa4\x0d\xf8\xf7\x3b\x52\x50\x3a\x79\x5b\x9b\x9b\
+\x0f\x35\xca\xe5\xfd\x98\xde\x1e\xe5\xe5\xd1\xa2\x2b\xdc\xae\x07\
+\x37\xf4\xe9\xb3\x1f\xcc\x9a\x39\xbc\x35\x6d\x6d\xbf\x9d\x6b\xb7\
+\xdd\xd2\xd4\x54\x27\x63\xfa\xf8\x1d\xa4\xd5\x9e\x4f\xf7\x78\xcb\
+\x04\x6f\x49\x5d\xdd\x77\x23\x4a\x22\x33\xae\x71\x39\x9f\xee\x65\
+\x34\x9c\x97\x25\xd3\x0f\x13\x1e\x4e\x14\x00\x79\xb1\x4e\xfd\xe7\
+\xe2\x15\x02\x51\x00\x88\xc0\xbc\x80\x97\x09\xde\x69\x5a\xed\x25\
+\x6c\x4a\x1e\x83\x8d\xc6\x6b\x18\x3f\xdd\xb2\x56\x53\xf1\x79\x7d\
+\xdd\x7a\x30\x6b\xe6\xf2\x46\x57\x57\x2f\x34\xf3\xf9\x16\xa6\x8f\
+\xdf\x1b\xac\xd6\x87\xb8\x64\xfe\x9d\xf1\x4a\xb5\xda\x66\x8e\xe7\
+\x67\xe2\xec\x3d\x51\x00\xf0\x63\x99\x7f\x0e\x5e\x1d\xe4\x93\xae\
+\x17\x80\x79\x01\x2f\x23\x3c\x34\xeb\xdc\x96\xe6\xe6\x83\x6c\x48\
+\x1e\x8f\xfb\x7c\x23\x99\xde\x1e\x83\x6d\xb6\x5b\x37\xf4\xee\xbd\
+\x7f\x7b\xdf\x3e\xed\x48\x3b\xfa\x62\xe6\x85\xe9\xab\x14\x84\xd6\
+\x43\xeb\x13\x2c\xe0\xa5\x97\xb7\xb9\x67\xcf\x5f\x4e\xd5\x68\x06\
+\x33\x7c\xfc\x9e\xf0\x66\x38\x3c\x85\xab\xe6\xff\x4e\x34\xba\x38\
+\x0b\xf2\xb3\x94\x54\x00\x08\xe2\xdd\xf4\x47\x2e\x00\xf8\x09\xcf\
+\x12\x04\x66\x08\xbc\x14\x79\xe8\x39\x7a\xa6\x27\x8f\xcf\xcb\xcb\
+\x57\xfb\x7c\x6e\x15\x93\xef\xde\x7e\xbd\xac\x6c\xfa\x36\xcc\x64\
+\x08\x6d\xc7\x4c\xe6\x0b\xdc\x70\x92\xd5\x17\xc7\x8c\xab\x6f\x3b\
+\xf0\x32\xcf\x7b\x31\x14\xfa\xa0\xe8\x58\x2d\xcc\xcc\xf1\x8b\x8a\
+\xf4\xd9\x55\x55\xbb\xb8\x66\xfe\xe8\x73\x6b\x71\xf1\x49\x59\x90\
+\x9f\x89\x02\x40\x14\xd3\xcf\xf1\x95\x72\x48\xcf\x08\x82\xf9\x03\
+\x2f\xe3\x3c\x74\xad\x11\x0d\xc8\x75\x24\xa1\xcf\xdb\xf0\x81\x9b\
+\xac\xb6\xe1\xeb\xa7\x8b\xb7\xbc\xbe\xfe\x47\xa7\x4e\x1b\x60\x6a\
+\xfc\xca\x14\x8a\xfa\x45\x2d\x2d\xdf\x82\xb9\xb2\x97\x37\xbf\xa1\
+\x61\x87\x5b\x2c\x0e\x32\x75\xfc\xfa\x44\xa2\xc8\xfa\xa6\xa6\x3f\
+\xa9\x18\x6f\x54\xf1\xa6\x56\x55\x6d\xc3\x76\xad\x47\x16\xe4\x67\
+\x69\x42\xf7\xf0\x91\x0a\x80\x5c\x30\x7f\xe0\x51\xc5\x0b\x3a\x6c\
+\xba\x65\x4d\x4d\xfb\x99\x98\x3c\x36\x37\x35\x1d\x69\xd0\x69\x4f\
+\x66\x6a\xfc\x2e\x30\x9b\xaf\xdd\xd2\xa7\xcf\x41\x30\x57\xf6\xf3\
+\xb6\xb5\xb5\xfd\x71\x92\x56\x7b\x36\x53\xc7\xef\x20\x8d\xe6\x3c\
+\xae\x98\x3f\xfa\xfe\x2c\x9d\x6e\x48\x96\xe4\xe7\xc4\x9e\xde\x23\
+\x15\x00\x60\xfe\xc0\xa3\x94\xf7\x6c\x38\x3c\x81\x89\xc9\xe3\x7a\
+\x97\xeb\x59\x86\xc6\x8f\xff\x4c\x30\x38\x12\xcc\x95\x7b\xbc\x7b\
+\xbd\xde\xe7\xb0\xf6\xcd\x61\xe2\xf8\x7d\xdc\xeb\x7d\x9b\x0b\xe6\
+\xbf\xb4\xbe\xfe\x7b\x6c\x77\x84\x90\x9f\x3b\xdc\x03\xc0\x4b\x71\
+\x01\x33\x04\x5e\x77\x78\x7d\x0d\xfa\x0b\x99\x96\x3c\x3e\x2c\x2f\
+\x5f\x53\x56\x56\x52\xcc\xb4\xf8\x15\xf0\x78\x8a\x31\x15\x15\x0b\
+\xb7\xf7\xc1\x4c\x86\x24\xf4\x79\x1f\xa6\x2f\x52\xd0\x3e\x7c\x7d\
+\xe0\x31\x83\xf7\x76\x34\x3a\xc9\xed\x76\x68\x18\x38\x7e\x25\x33\
+\xaa\xaa\x76\xb2\xd9\xfc\xd1\xbf\xa3\x79\x01\x20\x3f\xa7\x69\x01\
+\x33\x04\x5e\x77\x79\x1e\x8f\x4b\xbd\xb6\xa1\xe1\x77\xa6\x24\x8f\
+\x65\x8d\x8d\x7f\xfa\x8d\x86\x32\xa6\xc5\xcf\xce\xe7\x3b\xe6\xd6\
+\xd7\x6f\x07\x73\xe5\x3e\x6f\x4c\x6d\xcd\x5a\xb7\xd5\xec\x66\xda\
+\xf8\x0d\xcb\x64\xd5\x6b\x9a\x9b\x0f\xb1\xd5\xfc\xd1\xbd\x0c\x12\
+\x1e\x4f\x09\xf9\x19\xcc\x1f\x78\x0c\xe2\x3d\xe7\xf3\x7d\xb4\xbd\
+\xb9\xb9\x3d\x19\x6d\xc3\xb4\x1e\xd3\x3a\x92\xd6\xe3\xdf\x6f\x4f\
+\x41\x04\xef\x0c\xab\xe5\x06\xa6\xc5\x2f\x2a\x97\x57\xad\x69\x69\
+\xf9\x2f\x98\x6b\xf6\xf0\x66\x37\x35\x7d\x85\xe6\x75\x60\xda\xf8\
+\xbd\xdc\xe1\x78\x24\x9d\xe3\x2d\xdd\xe3\x37\x16\xef\x01\x97\xeb\
+\x15\xc8\xcf\x60\xfe\xc0\x63\x18\xaf\x45\xa1\x38\x89\x09\xc9\xe3\
+\xf9\x50\x68\x32\xd3\xe2\x57\xa3\x50\x34\x6f\x6e\x6b\xfb\x0d\xcc\
+\x35\xfb\x78\x58\xd1\xf7\x43\x50\x26\x8b\x32\x69\xfc\x56\x57\x57\
+\xc8\xdf\x8e\x46\x97\xb2\xcd\xfc\xb7\x34\x36\x1e\xb1\xe6\xe7\xbb\
+\x20\x3f\x83\xf9\x03\x8f\x79\x3c\xfe\xea\xfa\xfa\x5f\xe8\x4c\x1e\
+\xf3\xeb\xeb\x7e\x66\xda\x69\xd7\x86\xe2\xe2\xbe\x5b\x7b\xf5\xda\
+\x8f\x0c\x61\x2b\x49\xe8\xf3\x5e\xdc\x38\x92\xd5\x5e\x7c\x7d\xe0\
+\xb1\x83\xb7\xb1\x67\xcf\x9f\xcb\x8a\x8a\x6a\x98\x34\x7e\x7d\x06\
+\x7d\xe9\xfa\x86\x86\xbf\xd8\x62\xfe\x48\x23\x02\x81\xcf\x21\x3f\
+\x83\xf9\x03\x8f\xa1\xbc\xc7\xbd\xde\x77\xb7\xe1\x03\xb7\x2b\x6d\
+\xc5\x07\xf8\x5a\x92\xd6\xe1\xdf\x6f\x4b\x41\x64\xde\xa9\x66\xd3\
+\x75\x4c\x8a\x5f\x1f\x95\xea\xe4\x1d\x3d\x7b\x1e\xd8\xd6\xbb\x77\
+\xfb\x56\x92\xd0\xe7\xbd\x98\xf6\xa5\xa0\xbd\xf8\xfa\xc0\x63\x17\
+\x6f\x4b\x6b\xeb\xef\xe8\x4c\x10\x93\xc6\xef\x95\x66\xf3\xd0\xee\
+\x8c\xb7\x74\x8f\xdf\x78\xbc\x68\x61\x61\x3d\xe4\x67\x30\x7f\xe0\
+\x31\x94\x87\x1d\xed\xf6\xa3\x2b\x79\xbc\x1e\x8d\x2e\x40\xaf\xce\
+\x65\x4a\xfc\x9a\x8a\x8b\xfb\xef\xec\xd5\xeb\x20\x98\x21\xf0\xc8\
+\x45\x40\x69\x51\x51\x2d\x83\xc6\x6f\xde\xf8\xb2\xb2\x75\x6c\x30\
+\xff\x8f\xa3\xd1\xa5\x90\x9f\x3b\x65\xf6\x00\xf3\x02\x1e\x53\x78\
+\x79\xcb\xeb\xea\x7e\xa4\x3a\x79\x2c\x6d\x68\xd8\xef\xd1\x6a\xa2\
+\x4c\x89\x5f\x9d\x42\xd1\xba\xb5\xad\x6d\x3f\x98\x21\xf0\x3a\x6a\
+\x63\x4b\xcb\xcf\x41\x99\xac\x94\x29\xe3\xd7\x2f\x95\x96\x6f\x6e\
+\x6c\x3c\xc2\x64\xf3\x47\xea\xa3\x54\x9e\x0e\xf9\xf9\x7f\x8d\x1f\
+\x9f\xf7\x27\xe1\x49\x82\x24\x60\x5e\xc0\xcb\x34\xef\x41\xb7\xfb\
+\x35\xaa\x93\xc7\x25\x76\xfb\x03\x4c\x89\x1f\xba\xd6\xbb\x09\x3b\
+\xd2\x43\x86\xb0\x85\x24\xf4\x79\x0f\x6e\x10\xc9\x6a\x0f\xbe\x3e\
+\xf0\xb8\xc1\x5b\xd3\xdc\xfc\x83\x5d\x2c\xf6\x33\x65\xfc\xde\x69\
+\xb7\x3f\xc7\x64\xf3\x9f\x59\x55\xb5\x0b\xdb\xcc\x1c\xc8\xcf\xff\
+\x63\xfe\xb9\x09\x15\x00\xa4\xf7\x09\x4b\xc1\xbc\x80\x97\x29\x1e\
+\xba\x3e\xf7\x88\xc7\xf3\xd6\xea\xfa\xfa\xdf\xa8\x4c\x1e\xe3\xb1\
+\xe4\x10\x89\x04\x15\x4c\x88\x9f\x53\x22\xf1\xae\x6b\x6d\xfd\x09\
+\xcc\x10\x78\xf1\xb4\xb4\xa1\xe1\x2b\x83\x50\xa8\x67\xc2\xf8\x45\
+\x2f\x33\x5a\x52\x5b\xfb\x3d\x13\xcd\x1f\xe9\x3c\x83\xe1\x5a\xc8\
+\xcf\xff\x63\xfe\xc4\xfb\x7e\x62\x17\x00\xf8\x8f\x45\xf8\xd1\xbf\
+\x14\xcc\x0b\x78\xe9\xe4\xe9\x04\x02\xe3\x55\x16\xcb\xdd\xd3\x2b\
+\x2b\x77\xd2\x95\x3c\x5a\xf5\xba\xb3\x98\x10\x3f\x95\x58\xac\x9e\
+\x5f\x57\xb7\x67\x6b\xaf\x5e\xed\x5b\x48\x42\x9f\xf7\x60\xda\x9b\
+\x82\xf6\xe0\xeb\x03\x8f\x9b\xbc\x49\x55\x55\x6b\xd1\xbb\x34\x98\
+\x90\x0f\xce\xd4\x6a\x2f\x67\xa2\xf9\xa3\x4b\x8a\xd8\xe6\x89\x21\
+\x3f\x1f\xf7\x73\x3e\xfe\xb6\xdf\xbc\x98\x53\xff\xe3\x3f\x16\xe0\
+\x47\xff\x12\xd2\xbb\x85\xc1\xbc\x80\xd7\x1d\x9e\xb0\xbf\x4a\x75\
+\xee\xdb\xa1\xd0\x8c\x63\xd7\x0e\x9b\x9a\xda\x3b\xd3\x56\x4c\xeb\
+\x30\xad\x25\x69\x1d\xfe\xfd\xb6\x14\xd4\x19\xef\x95\x48\x78\x0e\
+\x43\xe2\x27\xfa\xac\xb2\x72\x39\x98\x21\xf0\x92\xe5\xbd\x5a\x5a\
+\x3a\xa3\xb2\xb2\x54\xc1\x80\x7c\x70\xc2\xe7\xa5\xa5\x6b\xa8\x1c\
+\xbf\x89\xf0\x6e\xb4\x58\x1e\x81\xfc\x7c\x9c\x27\xc0\x75\xbc\x00\
+\x88\x57\x29\x08\x49\x05\x80\x04\xcc\x0b\x78\xa9\xf2\x4a\x0a\x0a\
+\xaa\xd1\xf5\xfd\xe5\xb5\xb5\x3f\x6f\xc5\x07\x6e\x57\xda\x82\x0f\
+\xf0\x35\x24\xad\xc5\xbf\xdf\x9a\x82\x3a\xe3\xad\x6c\x68\x38\xec\
+\x90\x4a\x43\x4c\x88\xdf\x4b\xe1\xf0\x18\x64\x08\x9b\x49\x42\x9f\
+\x77\xe3\x89\x3e\x59\xed\xc6\xd7\x07\x5e\x76\xf0\xee\x0e\xf8\x5f\
+\x67\x42\x3e\x28\x2f\x2c\x6c\xa0\x6a\xfc\x26\xc2\x5b\xdf\xd0\x70\
+\x40\x29\x12\x69\x20\x3f\x1f\xe3\x89\x70\x3f\x27\x0a\x80\xdc\x78\
+\xd7\x08\x04\xa4\x02\x40\x0c\x66\x08\xbc\x64\x79\x0a\xa1\x50\x77\
+\xb9\xd1\x78\xc7\xe4\x8a\x8a\xad\x99\x1e\xec\xc9\xf2\xee\x71\x3a\
+\x47\x30\x21\x7e\x57\xd8\xed\x77\x82\x19\x02\xaf\xbb\xbc\x53\xf5\
+\xfa\x0b\x99\xd0\x9f\x9f\xf3\xfb\x47\x33\xc1\xfc\x91\xd0\x3d\x45\
+\x90\x9f\x8f\xdf\xc3\x27\x26\x15\x00\x79\xb1\x4e\xfd\xe7\xe2\x15\
+\x02\x51\x00\x88\xc0\x0c\x81\x97\x28\x2f\x14\xf2\x17\xf7\x53\x2a\
+\xcf\x7c\x2d\x18\x9c\xb2\x11\x3b\xca\xa6\x6a\xb0\x27\xc3\x5b\xd3\
+\xd0\xf0\x67\x67\x47\x06\x54\xc7\xaf\xae\x58\xd1\x6b\x43\x6b\xeb\
+\xe1\xcd\x6d\x6d\xed\x84\xb6\x60\xda\x8d\x69\x4f\x0a\xda\x8d\xaf\
+\x0f\xbc\xec\xe3\x6d\x69\x6d\xdd\xef\x15\x89\x4a\xe8\xce\x07\x68\
+\x86\xc0\x15\xd8\xb8\xa7\xdb\xfc\x37\x37\x35\x1d\x75\x48\x24\x3e\
+\xc8\xcf\xc7\xcf\xde\x13\x05\x00\x3f\x96\xf9\xe7\xe0\xd5\x41\x3e\
+\xe9\x7a\x01\x98\x21\xf0\xe2\xf2\x4a\xf4\xba\xe6\x3b\x3d\xee\xd7\
+\x97\xd5\xd6\xfe\x1f\xd5\x83\x3d\x59\xde\xad\x36\xdb\x53\x74\xc7\
+\xcf\x20\x91\x58\x16\x35\x35\xfd\xb0\x09\x4b\xde\x84\x50\x42\xdf\
+\x85\x27\xf4\x64\xb5\x0b\x5f\x1f\x78\xd9\xcb\x9b\x5f\x5b\xbb\x47\
+\xca\xe3\xc9\xe9\xce\x07\x0f\xfa\xbc\x1f\xd1\x69\xfe\x48\xaf\x85\
+\x42\x93\x21\x3f\x1f\xbf\x6f\x8f\x28\x00\x04\xf1\x6e\xfa\x23\x17\
+\x00\xfc\x84\x67\x09\x02\x33\xcc\x4a\x9e\xd7\x6e\x75\x9e\x6f\xb5\
+\xdc\xfd\x49\x45\xc5\x66\x3a\x07\x7b\x32\xbc\x55\x75\x75\xbf\x49\
+\x78\xbc\x62\x3a\xe3\x67\x34\xea\xf3\xde\xab\xa8\x58\x02\xe6\x05\
+\xbc\x74\xf3\x5e\x0e\x85\x3e\xa3\x3b\xbf\xf8\xf4\xda\xf0\xaa\x86\
+\x86\x43\x74\xe6\x83\x0a\x99\xac\x05\xf2\xfd\x71\x8e\x04\x3f\x93\
+\xdf\x23\xde\x4a\x39\xa4\x67\x04\xc1\xfc\x81\xf7\xaf\x45\xa9\x54\
+\xf0\x7b\x1a\xf4\xe7\xbe\x10\x0e\x4f\x5d\x89\x0d\x72\xba\x2b\xfd\
+\x64\x79\x37\x58\x2c\x0f\xd3\xdd\x1e\x43\x5c\xce\x87\xc1\xbc\x80\
+\x97\x29\xde\x59\x7a\xfd\x65\x74\xe7\x97\x07\xdc\xee\xd7\xe9\xca\
+\x07\x63\xa3\xd1\x55\x90\xef\xff\x87\x25\x4e\x74\xc2\x9f\x1c\xfc\
+\x1e\x00\x30\x7f\xe0\xfd\xcf\xe2\x13\x89\x22\x77\xd9\xed\x2f\xce\
+\xae\xad\xf9\x81\x09\x37\xf8\xa4\xc2\x43\x4f\x20\xc8\x8e\xcd\x5b\
+\x42\x5f\x7b\x94\x6a\x34\x2d\xeb\x7b\xf6\x3c\x04\xe6\x05\xbc\x4c\
+\xf1\x36\x36\x37\xff\xde\xd9\x6b\x6f\xa9\xcc\x2f\x68\x8e\x8f\x75\
+\xf5\xf5\x7f\xd1\x91\x0f\x06\xa8\x54\xe7\x40\xbe\x3f\xae\xc4\x9e\
+\xde\x23\x15\x00\x60\xfe\xc0\x3b\xb6\xa0\x19\xbe\xce\xd7\xeb\xaf\
+\xff\xac\xb4\x74\xed\x66\x7c\x40\xae\x26\x69\xcd\xdf\x37\xdb\x1c\
+\x1b\xa0\xc9\x8a\x0e\xde\x0d\x36\xdb\xa3\x74\xb6\x87\xc7\xe3\x52\
+\x4f\xa9\xab\xdd\xbd\xb1\x67\x4f\xf4\x8a\xd7\xf6\x4d\x98\x76\x62\
+\xda\x95\x82\x76\xe2\xeb\x6f\x24\x09\x78\xc0\x23\xf4\x59\x45\xc5\
+\x0a\xac\xdb\xe5\xd2\x99\x5f\x1e\x76\xbb\xdf\xa0\x3a\x1f\xcc\xae\
+\xae\xfe\x02\xed\x37\xe4\xfb\x24\x79\xa9\x1a\x3f\x98\x2b\x77\x79\
+\xa8\x00\x98\x5d\x55\xf5\xe5\xe6\xc6\xc6\xf6\x35\x98\x56\x93\x84\
+\x3e\xa3\xef\xb7\xa4\x20\x3a\x78\xeb\x1a\x1a\x0e\x14\x8b\x44\x5a\
+\x3a\xdb\xe3\x16\x8f\x67\x38\x98\x17\xf0\xa8\xe2\x5d\x66\x32\xdd\
+\x46\x67\x7e\x41\x53\x5b\x6f\x6a\x6c\x3c\x4a\x65\x3e\xb8\xc8\x60\
+\xb8\x19\xf2\x3d\xbc\x22\x18\x78\x69\xe2\xd5\x2b\xe4\x7d\xd9\x6e\
+\xfe\x48\x8f\xba\x5c\x6f\xd3\xd9\x1e\xe5\x7a\x5d\xf3\xda\x96\x96\
+\xc3\x60\x5e\xc0\xa3\x8a\xb7\xb1\xa5\xe5\x4f\x93\x48\xe4\xa2\x33\
+\xbf\xbc\xec\xf7\x4f\xa4\x2a\x1f\x2c\xab\xad\xfd\x59\x27\x12\xca\
+\x20\xdf\x83\xf9\x03\x2f\x8d\xbc\x7b\x3c\x9e\x77\xd9\x6c\xfe\x48\
+\xe4\xb7\xa7\x51\x1d\xbf\xb2\xb2\x92\xe2\xb1\x55\x55\x9b\xc0\xbc\
+\x80\x47\x35\xef\xad\xd2\xd2\x79\x74\xe6\x97\x4a\x99\xac\x89\xaa\
+\x7c\xb0\xb6\xa1\xe1\xc0\x1d\x1e\xf7\x1b\x1e\xa3\x21\x00\xf9\x1e\
+\xcc\x1f\x78\x69\xe2\x85\x5c\x0e\xc3\xc4\xca\xca\x2f\xd8\x6a\xfe\
+\xaf\x05\x02\x53\xe8\x8c\xdf\xc5\x0e\xc7\x7d\x1b\xf0\x84\xbe\x03\
+\x4f\xcc\xc9\x6a\x07\xbe\xfe\x06\x92\x80\x07\xbc\x44\x78\x27\x9b\
+\x4c\x57\xd2\x99\x5f\x3e\x89\x46\x57\x50\x99\x0f\x96\x37\x34\x1c\
+\x44\x07\x2d\x5e\xad\x26\x04\xf9\x1e\xcc\x1f\x78\x69\xe0\xd5\x6a\
+\x34\x03\x37\x26\x70\x3d\x8f\x69\xe6\x8f\x54\x5f\x54\xd4\x9b\xae\
+\xf8\xb9\xad\x66\xf7\xd2\xa6\xa6\xdf\x37\xb6\xb6\xb6\xef\xc0\xb4\
+\x33\x05\xa1\xf5\xd0\xfa\x1b\x48\x02\x1e\xf0\x12\xe5\xcd\xaa\xab\
+\xfd\xd6\xe9\xb4\x6b\xe9\xca\x2f\xe8\xe5\x5f\x74\xe4\x83\x0d\xf5\
+\xf5\x87\x1e\x77\xbb\xdf\xed\xec\x89\x08\xc8\xf7\x60\xfe\xc0\x4b\
+\x92\x87\xe6\xcf\x67\x9b\xf9\xcf\xa8\xa8\xd8\x83\xed\x56\x0f\xba\
+\xe2\xf7\x60\xc0\xff\x01\x98\x17\xf0\xe8\xe6\xdd\x6c\xb7\x3d\x46\
+\x63\x7e\xe1\x2f\xad\xad\xfd\x91\xae\x7c\x80\x1d\xb8\x1c\x79\xca\
+\xeb\xfd\xb0\xe3\xd4\xc0\x90\xef\xc1\x0c\x81\x97\x1c\x4f\x3c\xb5\
+\xb2\x72\xd7\x66\x7c\x80\xc5\xd3\x26\x7c\x40\xae\x22\x69\x35\xfe\
+\xfd\xe6\x14\x94\x0a\xef\x0a\xa3\x71\x28\x5d\xf1\x8b\xea\x75\x4d\
+\xeb\x5b\x5b\x8f\x82\x79\x01\x8f\x6e\xde\xa6\xa6\xa6\xbf\x4c\x7c\
+\xbe\x95\xae\xfc\x72\x87\xdd\xfe\x3c\xdd\xf9\x00\x9d\xc1\x7c\xde\
+\xeb\xfd\xd4\x29\x12\x85\x21\xdf\x1f\x67\xf6\x00\x33\x04\x5e\xc2\
+\x3c\xf4\xca\x4f\x34\x90\xd8\x60\xfe\xe8\x14\x20\x7a\x23\x21\x5d\
+\xf1\x7b\xb3\xb4\x74\xc1\x76\x3c\x01\x27\xab\xed\x78\x02\x5f\x4f\
+\xd2\x06\xfc\x7b\xe0\x01\x2f\x15\xde\xf3\x81\xc0\x28\xba\xf2\x8b\
+\x53\x2a\x0d\xd2\x9d\x0f\xc8\x85\xc0\x30\xbf\xff\xf3\xa0\x4c\x56\
+\x9a\xc5\xf9\x9e\x98\xfa\x3f\xe1\x49\x82\x24\x60\x86\xc0\x43\xcb\
+\x9d\x76\xfb\x0b\x9b\x1b\x1a\xda\xbb\xd2\x26\x4c\xab\x31\xad\x22\
+\x69\x35\xfe\xfd\xe6\x14\x94\x2a\x0f\x0d\x72\xba\xe2\x57\xa3\xd7\
+\x9d\x08\xe6\x05\x3c\x26\xf1\xb6\xb5\xb6\x1e\x75\x8b\xc5\x41\xba\
+\xf2\xcb\x9b\x25\x91\xa5\x74\xe6\x83\xce\xf4\x8a\xdf\x3f\x39\x24\
+\x93\xd5\x64\xa1\xf9\xe7\x26\x54\x00\x90\xde\x27\x2c\x05\x33\x04\
+\x1e\xbe\x88\x26\x97\x95\x6d\x67\xb2\xf9\x23\x35\x15\x16\xf6\xa7\
+\x2b\x7e\x1f\x97\x95\x2d\xd9\xd1\xd2\xd2\x9e\xac\xb6\x63\xda\x80\
+\x69\x3d\x49\x1b\xf0\xef\x81\x07\xbc\xee\xf2\x46\x04\x83\x9f\xd1\
+\x95\x5f\x06\x9a\x8c\x57\x31\xc9\xfc\xc9\xbc\xe1\xa1\xd0\x9c\x2a\
+\xbd\xae\x5f\x96\x98\x3f\xf1\xbe\x9f\xd8\x05\x00\xfe\x63\x11\x7e\
+\xf4\x2f\x05\x33\x04\x1e\xb1\x44\x0a\x0a\x6a\x36\x34\x34\x1c\x61\
+\xaa\xf9\x2f\xaa\xae\xfe\x2f\xb6\x99\x39\x74\xc4\xaf\x49\xa9\x1c\
+\x08\x66\x03\x3c\xa6\xf2\xc8\xa7\xbe\xa9\x1c\x1f\xe8\x71\xe2\x25\
+\xb5\xb5\xfb\x99\x66\xfe\x64\xde\xeb\x91\xc8\xc2\xea\xa2\xa2\x36\
+\x0e\x9b\x3f\x1f\x7f\xdb\x6f\x5e\xcc\xa9\xff\xf1\x1f\x0b\xf0\xa3\
+\x7f\x09\xe9\xdd\xc2\x60\x86\xc0\x3b\xb6\xdc\x62\xb3\x3d\xcd\x44\
+\xf3\x47\xba\xcf\xe1\x78\x85\xae\xf8\x8d\x2e\x2b\x5b\xb6\x1d\x4f\
+\xc4\x89\x6a\x1b\x9e\xc0\xd7\x91\xb4\x1e\xff\x7e\x7b\x0a\x02\x1e\
+\xf0\xba\xd2\xcb\xa1\xd0\x38\xba\xf2\xcb\x0b\x3e\xdf\x67\x4c\x35\
+\x7f\x32\xef\xc3\x70\x78\x71\x6d\x51\x51\x5f\x8e\xe5\x7b\x01\xae\
+\xe3\x05\x40\xbc\x4a\x41\x48\x2a\x00\x24\x60\x86\xc0\xeb\xb0\x08\
+\x26\x94\x95\x6d\x66\x9a\xf9\x23\x55\xc8\x64\xcd\x74\xc4\x0f\xdd\
+\x24\x09\x66\x03\x3c\x26\xf3\xb6\xb6\xb4\x1c\x25\x3f\x1b\x4f\xe5\
+\xf8\xe8\x5b\x5c\x7c\x26\xd3\xcd\x9f\xac\x4f\x4a\x4a\x96\x37\x2b\
+\x14\x27\x72\x20\xdf\x8b\x70\x3f\x27\x0a\x80\xdc\x78\xd7\x08\x04\
+\xa4\x02\x40\x0c\x66\x08\xbc\x4e\x2f\x05\x14\x16\x56\x2d\xaf\xab\
+\x3b\xbc\x12\x1b\x2c\x84\xd0\x80\xda\x88\x0f\xa8\x64\xb5\x11\x5f\
+\xbf\x3b\xbc\x79\x55\x55\xff\xc1\x36\xed\x04\x3a\xe2\xf7\x72\x20\
+\x30\x6e\x7b\x73\x73\x7b\xa2\xda\x86\x69\x3d\xa6\x75\x24\xad\xc7\
+\xbf\xdf\x9e\x82\x80\x07\xbc\x44\xf4\x90\xcb\xf5\x2a\x4d\xf9\x45\
+\xbc\xaa\xae\xee\x0f\x2a\xf3\x41\x3a\x78\xa3\xa3\xd1\x35\xad\x4a\
+\xe5\xa9\xbc\x0e\x73\x8a\xb0\x24\xdf\x13\x1e\x4e\x14\x00\x79\xb1\
+\x4e\xfd\xe7\xe2\x15\x02\x51\x00\x88\xc0\x0c\x81\x17\x8b\x77\x8d\
+\xc3\xfe\x2c\x53\xcc\x1f\xe9\x6e\x87\xe3\x25\x3a\xe2\x87\x8e\xaa\
+\xb6\x36\x37\x1f\x05\xb3\x01\x1e\xd3\x79\xeb\x1b\x1a\xf6\x17\xe5\
+\xe7\xab\xe9\xc8\x2f\x4f\x7b\x3c\x1f\xb3\xc9\xfc\xc9\xfa\xbc\xb4\
+\x74\x63\x3f\x95\xea\x6c\x74\x80\xc1\x92\xfc\x4c\x9c\xbd\x27\x0a\
+\x00\x7e\x2c\xf3\xcf\xc1\xab\x83\x7c\xd2\xf5\x02\x30\x43\xe0\xc5\
+\xe4\x45\xa3\x61\xe5\xa8\xd2\xd2\xcd\x4c\x30\x7f\xa4\xb2\xc2\xc2\
+\x46\x3a\xe2\x77\xaf\xd3\x39\x7c\x1b\x9e\x90\xe3\x69\x2b\x9e\xc0\
+\xd7\x92\xb4\x0e\xff\x7e\x5b\x0a\x02\x1e\xf0\x92\xe5\x5d\xe1\xb0\
+\x3f\x42\x47\x7e\x41\x47\xd2\x6c\x34\x7f\xb2\xc6\x97\x97\x6f\x0d\
+\xa9\x55\xf5\x2c\xc8\xcf\x52\x52\x01\x20\x88\x77\xd3\x1f\xb9\x00\
+\xe0\x27\x3c\x4b\x10\x98\x61\xd6\xf3\xd0\x60\x58\x57\x57\x77\x88\
+\xee\xc1\xb9\xa4\xa6\xe6\x67\x8d\x46\x9d\x4f\x43\xfc\x44\xab\xea\
+\xeb\x7f\x01\xb3\x01\x1e\x5b\x78\x53\xaa\xab\xbf\xac\xaf\xaf\x2e\
+\xa2\x3a\xbf\xc8\x79\x3c\xe9\xba\xfa\xfa\x83\x6c\x35\x7f\xb4\xde\
+\xdb\x91\xc8\xf2\x48\x24\xa8\x62\x41\x7e\x26\x0a\x00\x51\x4c\x3f\
+\xc7\x57\xca\x21\x3d\x23\x08\xe6\x0f\xbc\xa4\x78\xd7\x9a\xcd\x0f\
+\x6e\xaa\xaf\x6f\x4f\x46\x1b\x31\x61\xc6\xd9\xbe\x92\xa4\x55\xf8\
+\xf7\x9b\x52\xd0\xb3\x1e\xcf\x68\x3a\xe2\x77\x9a\x4e\x77\x11\x98\
+\x0d\xf0\xd8\xc6\x6b\xd4\xe9\x4e\xa3\x23\xbf\xbc\x1d\x0a\xcd\xa5\
+\x22\x1f\x64\x82\x37\xb9\xa2\xe2\x6b\x8f\xd5\xe2\x62\x49\x7e\x96\
+\x26\x74\x0f\x1f\xa9\x00\xc8\x05\xf3\x07\x5e\x8a\xbc\x3c\x74\xb3\
+\x0c\x9d\x83\xf3\x24\xa3\xe1\x6a\x3a\xe2\xf7\x51\x34\xba\x78\x5b\
+\x53\x53\x7b\x2c\x6d\xc5\xb4\x0e\xd3\x5a\x92\xd6\xe1\xdf\x6f\x4b\
+\x41\xc0\x03\x5e\x77\x79\xcf\x06\x83\xe3\xe9\xc8\x2f\x17\xeb\xf5\
+\xb7\xb3\xd1\xfc\x17\xd5\xd6\xfe\x59\xaa\x51\x37\xb2\x28\x3f\x27\
+\xf6\xf4\x1e\xa9\x00\x00\xf3\x07\x5e\xca\x3c\x97\x58\x1c\x5a\x57\
+\x57\x77\x90\x8e\xc1\x89\xd6\x77\x9a\x4d\x5e\xaa\xe3\x87\xde\x32\
+\xb6\x15\x4f\xb0\x5d\x69\x0b\x9e\x70\xd7\x90\xb4\x16\xff\x7e\x6b\
+\x0a\x02\x1e\xf0\xd2\xc1\xdb\xd8\xd0\x70\x48\x2d\x16\xab\xa8\xce\
+\x2f\x28\x4f\xb0\xcd\xfc\xd1\xfa\xfd\xf4\xfa\x8b\x39\x99\xef\x53\
+\x35\x7e\x30\x43\xe0\x75\x5c\x86\x98\x4c\xf7\x6c\xc4\x07\x4d\x67\
+\xda\x80\x0f\xc8\x15\x24\xad\xc4\xbf\xdf\x98\x82\x08\xde\x87\xa5\
+\xa5\x1b\xe9\xd8\xdf\x1b\x2c\x96\x87\xc1\x6c\x80\xc7\x56\xde\x39\
+\x3a\xdd\xd5\x74\xe4\x97\xd9\x15\x15\x5f\x65\x32\x1f\xa4\x9b\x77\
+\x95\xc3\xfe\x34\xbc\x22\x18\xcc\x10\x78\xf1\x79\xb9\x1f\x47\x22\
+\x2b\xa8\x1c\x9c\x88\x73\xbd\xc3\x3e\x8c\x8e\xfd\x9d\x5a\x51\xb1\
+\x1d\xcc\x06\x78\x6c\xe5\xbd\x1f\x89\xcc\xa7\x23\xbf\x3c\xe4\x72\
+\xbd\xcd\x16\xf3\x7f\xca\xef\x9f\xdc\xd4\x54\x57\x08\xe6\x0f\x66\
+\x08\xbc\x04\x78\x76\xb1\xd8\xbf\xaa\xa6\xe6\x2f\xaa\xcc\x1f\xa9\
+\x45\xaf\x3f\x87\xea\xfd\xf5\x8a\x44\x25\x60\x36\xc0\x63\x33\x6f\
+\x73\x53\xd3\x51\xb9\x50\xa8\xa7\x3a\xbf\x9c\xa6\x56\x5f\xc6\x06\
+\xf3\xff\x28\x1a\xdd\x8c\xde\x63\x00\xe6\x0f\x66\x08\xbc\x24\x78\
+\x97\x18\x8d\x77\x50\x65\xfe\x48\x5e\xbd\xd6\x46\xf5\xfe\xde\x64\
+\xb5\x3e\xbe\xa5\xb1\xb1\xbd\xa3\xd0\x7b\xc7\xd7\xe0\xef\x28\x27\
+\xb4\x06\xff\x7e\x4b\x0a\x02\x1e\xf0\x32\xc9\x3b\x4f\xaf\xbf\x81\
+\xea\xfc\xe2\xd5\xeb\xca\x98\x6e\xfe\x33\xaa\xab\x7f\xf4\xe9\xb5\
+\x61\x30\x7f\x30\x43\xe0\x25\xcf\xcb\xf9\x20\x12\x59\x4a\x85\xf9\
+\x8f\x2d\x2b\xdb\x49\xc7\xfe\x4e\x2a\x2f\xdf\x02\x66\x03\x3c\xb6\
+\xf3\xde\x0d\x85\xe6\x50\x9d\x5f\xd0\x29\x75\x64\xb0\x4c\x35\xff\
+\xa5\x75\xb5\x87\xaa\x75\xda\x01\x60\xfe\x60\x86\xc0\x4b\x91\x37\
+\x40\xa5\x3a\x9f\x8a\xd3\x7c\x0f\xba\x5c\xef\x50\xbd\xbf\x6a\x3e\
+\xdf\x02\x66\x03\x3c\x2e\xf0\x36\x34\x34\x1c\x54\xf0\x78\x05\x54\
+\xe7\x97\xa7\x03\x81\x29\x4c\x34\x7f\xc4\x39\xc3\x64\xbc\x19\xcc\
+\x1f\xcc\x10\x78\xa9\xf1\x72\x86\x98\xcd\xf7\x2d\xab\xad\x39\xb4\
+\xa2\xae\xae\x9d\xd0\x4a\x4c\x1b\x30\x6d\x4c\x41\x1b\xf0\xf5\x3b\
+\xe3\x9d\xa6\x52\x5d\x4a\xf5\xfe\x9e\xa9\xd3\x5d\xb9\x19\x4f\xae\
+\x48\x9b\xf0\x84\xbb\x8a\xa4\xd5\xf8\xf7\x9b\x53\x10\xf0\x80\x47\
+\x25\xaf\x4d\x2e\x3f\x99\xea\xfc\x72\x81\xd9\x7c\x6f\x26\xf2\x41\
+\x77\x79\x77\xb8\x5c\x6f\x66\x83\xf9\x27\xfc\xf4\x1f\x98\x21\xf0\
+\x12\x5d\x34\x02\x81\xf9\xed\x60\x70\x21\x1a\x48\xcb\x49\x42\x9f\
+\xd7\xe3\x03\x2d\x59\xad\xc7\xd7\xef\x8a\xe7\x16\x8b\x83\x54\xef\
+\xef\x4b\x81\xc0\x78\x30\x1b\xe0\x71\x85\x77\x3f\xfe\x86\x40\x2a\
+\xf3\x4b\x59\x51\x51\x4b\x26\xf2\x41\x77\x78\xaf\x84\x43\x0b\xcb\
+\xca\x4a\x8a\x39\x9e\xef\x89\xa9\xff\x13\x9e\x24\x48\x02\x66\x08\
+\xbc\x78\x0b\x7a\xdf\xf7\xa2\xaa\xaa\x9f\xa9\x34\xff\x55\xb5\xb5\
+\x07\xb0\x3f\x9d\x47\xf1\xfe\xe6\xae\xa8\xa9\xf9\x8d\xee\xf7\x95\
+\x03\x0f\x78\xe9\xe2\xcd\x28\x2f\xdf\x43\x75\x7e\x91\xf2\x78\x72\
+\x26\x99\xff\x67\x65\x65\x7b\x9d\x66\xa3\x2d\x0b\xcc\x3f\x37\xa1\
+\x02\x80\xf4\x3e\x61\x29\x98\x21\xf0\x62\x2c\x92\x87\x5d\xae\x77\
+\x32\x39\x38\xbb\xe2\x8d\x89\x44\xd6\x51\xbd\xbf\x01\xa9\xb4\x0c\
+\xcc\x06\x78\x5c\xe3\x99\x0a\x0a\x4c\x54\xe7\x97\x59\xe5\xe5\x5f\
+\x33\xc1\xfc\xe7\x55\x57\xff\x1e\xd6\x69\x6b\xb2\xc0\xfc\x89\xf7\
+\xfd\xc4\x2e\x00\xf0\x1f\x8b\xf0\xa3\x7f\x29\x98\x21\xf0\x3a\x5b\
+\xc2\x52\x69\xc5\xa4\xb2\xb2\x9d\x74\x98\x3f\xd2\xa3\x4e\xe7\x7b\
+\x54\xc7\xef\x5c\xbd\xfe\x7a\x36\xbc\xb5\x0c\x78\xc0\x4b\x86\xd7\
+\x47\xaf\xbb\x90\xea\xfc\x32\xc2\xef\x9f\x42\xb7\xf9\x2f\xab\xab\
+\x3b\x4a\xc7\x3c\x22\x34\x98\x3f\x1f\x7f\xdb\x6f\x5e\xcc\xa9\xff\
+\xf1\x1f\x0b\xf0\xa3\x7f\x09\xe9\xdd\xc2\x60\x86\xc0\x23\x96\x13\
+\x2e\x35\x1a\xef\x5a\x5b\x53\x73\x88\x2e\xf3\x47\xba\xd8\x60\xb8\
+\x95\xea\xf8\x3d\xe3\xf1\x7c\x0a\x66\x03\x3c\xae\xf1\x6e\x75\x3a\
+\x5f\xa5\x3a\xbf\xdc\x68\x36\x3f\x41\xa7\xf9\x23\x5d\x6c\xb5\x3c\
+\x9c\x05\xf9\x5e\x80\xeb\x78\x01\x10\xaf\x52\x10\x92\x0a\x00\x09\
+\x98\x21\xf0\x88\x45\x27\x10\x18\xdf\x0e\x06\xe7\x52\x31\x38\xe3\
+\xf1\xaa\x8b\x8a\x7a\x53\x1d\xbf\x29\x95\x95\x5f\x83\xd9\x00\x8f\
+\x6b\xbc\xf7\xa3\xd1\x75\x54\xe7\x97\xfe\x0a\xc5\xb9\x74\x9a\xff\
+\xa3\x3e\xdf\xd8\x2c\xc8\xf7\x22\xdc\xcf\x89\x02\x20\x37\xde\x35\
+\x02\x01\xa9\x00\x10\x83\x19\x02\x8f\x58\xda\x94\xca\xd3\x17\x55\
+\x56\xfe\x1f\x13\xcc\x1f\x49\x2b\x10\x98\xa8\x8c\x9f\xdb\x62\x72\
+\x30\xfd\xad\x65\xc0\x03\x5e\x2a\xbc\xa5\x75\x75\x07\x23\x91\xa0\
+\x82\xca\xfc\xe2\x11\x89\xa2\x74\x99\xff\xc8\x92\x92\xb5\x1e\x8f\
+\x4b\xcd\xf1\x7c\x4f\x78\x38\x51\x00\xe4\xc5\x3a\xf5\x9f\x8b\x57\
+\x08\x44\x01\x20\x02\x33\x04\x1e\xbe\x88\xef\x77\x3a\xdf\x5c\x8f\
+\x0f\xa4\x75\xff\x5c\x3f\x3b\xae\xe5\xf8\xf7\xeb\x53\x50\x2a\xbc\
+\xd5\xb5\xb5\x07\xd1\xa5\x08\x2a\xe3\x57\xa7\xd7\x9f\xcc\xe4\xe9\
+\x4b\x81\x07\xbc\xee\xf0\x5c\x05\x92\x12\x2a\xf3\x8b\x8c\xc7\x2b\
+\x4a\x57\x3e\x48\x26\xbf\x4c\xa9\xac\xfc\xce\x63\xd0\x7b\x39\x9e\
+\xef\x89\xb3\xf7\x44\x01\xc0\x8f\x65\xfe\x39\x78\x75\x90\x4f\xba\
+\x5e\x00\x66\x08\x3c\x5e\x40\x28\x2c\x9b\x18\x8d\x6e\x5f\x5f\x5b\
+\xdb\x8e\xb4\x0e\xd3\x72\x4c\xcb\xea\xfe\xd1\x72\xfc\xfb\xf5\x29\
+\x28\x55\xde\xe4\x68\x74\x17\xd5\xf1\x3b\xcf\x6a\xb9\x1b\xcc\x06\
+\x78\x5c\xe5\xf5\x53\xa9\x06\x53\x9d\x5f\xd0\xa3\xc3\xe9\xc8\x07\
+\x89\xe6\x97\x85\xb5\x35\x07\xca\x35\xd8\x81\x3f\xf7\xf3\xbd\x94\
+\x54\x00\x08\xe2\xdd\xf4\x47\x2e\x00\xf8\x09\xcf\x12\x04\xe6\xca\
+\x65\xde\x09\x17\xeb\xf5\xb7\xaf\xae\xa9\x39\xc8\x34\xf3\x47\x7a\
+\xcd\xef\x9f\x49\x75\xfc\x1e\xf0\x79\x47\x81\xd9\x00\x8f\xab\xbc\
+\x9b\xac\xd6\x27\xa9\xce\x57\x9f\x84\xc3\x6b\xa9\xcc\x2f\x03\xf5\
+\xfa\x2b\xb3\x24\xdf\x13\x05\x80\x28\xa6\x9f\xe3\x2b\xe5\x90\x9e\
+\x11\x04\xf3\xcf\x72\x1e\x7a\x45\xe8\xeb\x81\xc0\x6c\x2a\x2b\xf3\
+\x64\x79\xf7\xd9\xed\x6f\x50\x1d\xbf\xf7\x4a\x4a\xd6\x32\x71\xfa\
+\x52\xe0\x01\x2f\x1d\xbc\x97\xfd\xfe\xc9\x54\xe7\xab\xe7\xdd\xee\
+\xcf\xa8\xca\x2f\x37\x3a\x1c\xc3\xb3\x28\xdf\x4b\x13\xba\x87\x8f\
+\x54\x00\xe4\x82\xf9\x03\xaf\xa7\x5c\x7e\xca\xfc\x8a\x8a\x1f\x99\
+\x6c\xfe\x48\x97\x19\x8d\x43\xa9\x8e\x1f\x3a\x5d\xc9\xb4\xe9\x4b\
+\x81\x07\xbc\x74\xf1\x26\x44\xa3\x5b\xa9\xce\x57\xb7\x58\x2c\xcf\
+\x52\x91\x5f\x5e\x08\x04\x66\x19\x8d\xfa\xbc\x2c\xca\xf7\x92\x64\
+\xa6\xfb\xcd\x01\xf3\xcf\x7a\x9e\xe8\x3e\x87\xe3\xb5\x4c\x98\x75\
+\x26\x78\x03\x95\xca\x0b\xa8\x8c\x9f\x52\x20\x90\x83\xd9\x00\x8f\
+\xcb\xbc\x95\xd5\xd5\xfb\xb1\x21\xd0\x83\xca\x7c\x35\xd8\x60\xb8\
+\x29\xd3\xf9\x65\x69\x4d\xf5\x61\x93\x48\xe4\x82\x7c\xdf\xc5\x3d\
+\x00\xbc\x14\x17\x30\x57\x6e\xf0\xd0\xe3\x38\xe3\x4b\x4a\xb6\xb2\
+\xc5\xfc\x91\xea\x0b\x0b\xfb\x53\x19\x3f\xaf\x48\x54\x02\x66\x03\
+\x3c\xae\xf3\x8a\x45\x22\x2d\x95\xf9\xaa\x9f\x5e\x7f\x05\x15\xf9\
+\x65\x62\x69\xe9\x0e\x03\x9f\xef\x80\x7c\x9f\xa6\x05\xcc\x95\x13\
+\xbc\x1e\x17\xe8\x74\xb7\xae\xa8\xa9\x39\xb0\x0e\x1f\x28\x84\xd6\
+\xa2\x81\xd4\x61\x30\x2d\xc3\xbf\x5f\x97\x82\xd2\xcd\x0b\xcb\x64\
+\xd5\x54\xc6\xaf\x45\x2e\x3f\x19\xcc\x06\x78\x5c\xe7\x45\x0a\x0a\
+\x6a\xa8\xcc\x57\x8d\x3a\xed\xe9\x54\xe5\x97\x79\x15\x15\xff\x2d\
+\x29\x28\xa8\x06\xff\x00\xf3\xcf\x7a\x9e\x42\x28\xd4\xbd\xe2\xf7\
+\xcf\xa0\xc2\xac\x33\xc1\x73\x6b\xd4\x11\x2a\xe3\x77\x96\x56\x7b\
+\x2d\x1d\x67\x3a\x80\x07\x3c\x2a\x79\xbd\x8b\x8b\xcf\xa0\x32\x5f\
+\x45\xf4\xba\x16\x2a\xf3\xcb\xf2\xaa\xaa\xfd\x6d\x72\xf9\x69\xe0\
+\x1f\x60\xfe\xd9\xcc\x93\x4c\x89\x46\xf7\xad\xab\xa9\x69\xef\xa8\
+\xb5\x98\x96\xd5\xfe\x5b\x6b\x3b\xf9\x6d\x22\xca\x14\xcf\x67\xb3\
+\x98\xa8\x8c\xdf\x55\x46\xe3\x03\x60\x36\xc0\xe3\x3a\xef\x0c\x8d\
+\xe6\x2a\x2a\xf3\x95\x57\xa7\x8d\x50\x9d\x5f\xd6\xd4\xd4\x1c\x3d\
+\x4f\xa7\xbb\x19\xfc\x03\xcc\x30\x6b\x79\x7e\xa1\xb0\x7c\x4e\x59\
+\xd9\x77\x6c\x34\xff\x25\xd5\x55\x87\x9b\x9a\xea\x0a\xa9\x8c\xdf\
+\x5d\x36\xdb\x4b\x60\x36\xc0\xe3\x3a\xef\x32\x83\xe1\x1e\x2a\xf3\
+\x95\xcf\x6a\x36\xd0\x95\x5f\x6e\xb7\x58\x86\xf1\x48\xb3\x89\x82\
+\xf9\x83\xb9\x66\x15\x4f\xcd\xe7\x5b\xc7\x95\x94\x6c\x65\x93\xf9\
+\x23\x4d\xab\xac\xfc\x91\xea\xf8\x3d\xe9\x76\x8f\x02\xb3\x01\x1e\
+\xd7\x79\xb7\x5b\xad\x2f\x50\x9d\xaf\x56\xd7\xd4\x1c\xa1\x2b\xbf\
+\xbc\xe0\xf1\x8c\x43\x4f\x40\x81\xf9\x83\xb9\x66\x25\x4f\xca\xe3\
+\xc9\xdf\xf2\xfb\x17\xb0\xc5\xfc\x91\xc6\x97\x95\x7d\x43\x75\xfc\
+\xba\xba\x5f\x82\x2d\xf7\x4c\x00\x0f\x78\x89\xe8\x11\xa7\xf3\x7d\
+\xaa\xf3\xd5\xf2\xca\xca\x3f\xe9\xcc\x2f\xef\x07\x83\xcb\x3d\x3a\
+\xad\x3d\x9b\xcc\x3f\xe1\xa7\xff\xc0\x5c\xb9\xcf\x0b\x85\xfc\xc5\
+\x0f\x79\x3d\xa3\xd9\x60\xfe\x48\x63\x4b\x4b\xf7\x52\x1d\xbf\x91\
+\xc1\xe0\x62\x36\xdf\x33\x01\x3c\xe0\x25\xa2\x67\xdc\xee\xcf\xa9\
+\xce\x57\x0b\x2b\x2b\x7f\xa6\x3b\x7e\x63\xcb\xca\xf6\xf9\x34\xea\
+\xf2\x2c\xc8\xf7\xc4\xd4\xff\x09\x4f\x12\x24\x01\x73\xe5\x3e\x0f\
+\x5d\x53\xbf\xca\x6e\x7f\x6e\x69\xed\xb1\x9b\x64\x8e\x0d\x8c\x64\
+\x85\xd6\x43\xeb\x77\x54\xba\x79\x63\x4b\x4a\xb6\x52\x1d\xbf\x8f\
+\xc2\xe1\x55\x60\x36\xc0\xe3\x3a\xef\x59\x9f\x6f\x3a\xd5\xf9\x6a\
+\x76\x59\xd9\x77\x4c\xc8\x2f\xd3\x2a\x2a\x7e\xaa\x54\xa9\xfa\x72\
+\xdc\xfc\x73\x13\x2a\x00\x48\xef\x13\x96\x82\xb9\x66\x0f\xef\x34\
+\xb5\x7a\xc8\xea\xaa\xaa\xc3\x4c\x35\x7f\xf4\xfd\xc7\xe1\xf0\x3a\
+\xaa\xe3\x37\x3a\x12\xd9\x00\x66\x03\x3c\xae\xf3\x5e\x0a\x06\xe7\
+\x51\x9d\xaf\xa6\x44\xa3\x5f\x30\x25\xbf\x2c\xab\xae\x3e\xd0\x57\
+\xa1\x38\x87\xa3\xe6\x4f\xbc\xef\x27\x76\x01\x80\xff\x58\x84\x1f\
+\xfd\x4b\xc1\x5c\xb3\x8b\x87\x66\xd9\x5b\x5c\x59\xf9\x3b\x13\xcd\
+\x1f\xfd\xfb\x07\xa1\xd0\x0a\xaa\xe3\x37\xae\xa4\x64\x1b\x5d\xfb\
+\x0b\x3c\xe0\x51\xc5\x7b\x25\x14\x5a\x42\x75\xbe\x1a\x5f\x52\xb2\
+\x93\x49\xf1\x5b\x5d\x53\x73\xf4\x52\xbd\xfe\x2e\x8e\x99\x3f\x1f\
+\x7f\xdb\x6f\x5e\xcc\xa9\xff\xf1\x1f\x0b\xf0\xa3\x7f\x09\xe9\xdd\
+\xc2\x60\xae\x59\xc4\x43\xd3\x03\xcf\x2c\x2b\xfb\x86\x89\xc9\xed\
+\x9d\x60\x70\x11\xd5\xf1\x9b\x14\x8d\xee\x06\xb3\x01\x1e\xd7\x79\
+\x6f\x84\x43\x2b\xa9\xce\x57\x9f\x45\x22\x9b\x99\x18\xbf\x7b\x6c\
+\xb6\xd7\xb0\xcd\xcb\xe5\x40\xbe\x17\xe0\x3a\x5e\x00\xc4\xab\x14\
+\x84\xa4\x02\x40\x02\xe6\x9a\x9d\x3c\xad\x40\x60\x1a\x13\x0e\x6f\
+\x5a\x5b\x5d\xdd\xde\x99\xd6\x60\x5a\x5a\xf3\x6f\xad\xe9\xe2\xf7\
+\xf1\x94\x28\xef\xbd\x40\x60\x19\xd5\xf1\x9b\x10\x89\xec\xa0\x6b\
+\x7f\x81\x07\x3c\xaa\x78\x6f\x04\x03\xcb\xa8\xce\x57\xe3\x4a\x4a\
+\xb6\x33\x35\x7e\xc3\x3d\x9e\x29\xd8\x26\x4a\x58\x9c\xef\x45\xb8\
+\x9f\x13\x05\x40\x6e\xbc\x6b\x04\x02\x52\x01\x20\x06\x33\xcc\x6e\
+\x9e\x8c\xc7\x2b\x7c\xcd\xeb\x9d\xcd\xa4\xc1\x39\x2a\x18\x5c\x43\
+\x75\xfc\x46\x63\x85\x10\x98\x0d\xf0\xb8\xce\x7b\xdd\xe7\x9b\x4b\
+\x75\xbe\x9a\x12\x8d\xee\x65\x72\xfc\xb0\x7c\xb3\x1a\x4d\x9d\xce\
+\xc2\x7c\x4f\x78\x38\x51\x00\xe4\xc5\x3a\xf5\x9f\x8b\x57\x08\x44\
+\x01\x20\x02\x33\x04\x1e\xbe\xe4\x3f\x6c\xb7\xbf\xc7\x94\xc1\x89\
+\xce\x4a\x50\x1d\xbf\x77\xc3\xa1\xf5\x4b\xb0\x6d\x22\x84\xb6\x6f\
+\x35\xbe\xed\xc9\x6a\x35\xbe\xbf\xc0\x03\x1e\xd3\x78\xc3\xbd\xde\
+\xa9\x54\xe7\x97\x19\xa5\xa5\xdf\x32\xbd\x78\x42\x37\x2a\xda\xc4\
+\xe2\x00\x8b\xf2\x3d\x71\xf6\x9e\x28\x00\xf8\xb1\xcc\x3f\x07\xaf\
+\x0e\xf2\x49\xd7\x0b\xc0\x0c\x81\xf7\x3f\xcb\x95\x46\xe3\x43\x4c\
+\x48\x6e\xe3\x23\x91\x9d\x54\xc7\xef\xb5\x50\x68\x05\x98\x0d\xf0\
+\xb8\xce\x7b\xde\xe3\x19\x4f\x75\x7e\x99\x53\x56\xf6\x23\x1b\xe2\
+\xb7\xa0\xa2\xe2\xe7\xf2\xa2\xa2\x36\x96\xe4\x7b\x29\xa9\x00\x10\
+\xc4\xbb\xe9\x8f\x5c\x00\xf0\x13\x9e\x25\x08\xcc\x35\xeb\x78\x27\
+\x1a\x0d\x57\x2f\xac\xaa\x3a\x44\xe7\xe0\x9c\x8c\x55\xe3\x54\xc7\
+\xef\xa5\x40\x60\x21\x98\x0d\xf0\xb8\xce\x7b\xc2\xe9\xfc\x98\xea\
+\xfc\xb2\xa8\xb2\xf2\x77\xb6\xc4\x6f\x41\x75\xd5\xc1\x01\x06\xfd\
+\x95\x2c\xc8\xf7\x44\x01\x20\x8a\xe9\xe7\xf8\x4a\x39\xa4\x67\x04\
+\xc1\xfc\x81\x17\x93\x57\xa7\x51\x9f\x3c\xa3\xa2\xe2\x37\xba\x06\
+\xe7\xac\xb2\xb2\xef\xa8\x8e\xdf\xa3\x5e\xcf\x84\xa5\xd5\x55\xed\
+\xab\xab\xaa\xda\xd7\xa4\x20\xb4\x1e\x5a\x7f\x09\x49\xc0\x03\x1e\
+\xd3\x78\x43\xad\xd6\x97\xa9\xce\x2f\xcb\xab\xaa\x0e\xb2\xad\x78\
+\xba\xc4\x6a\x7d\x9c\xe1\xf9\x5e\x9a\xd0\x3d\x7c\xa4\x02\x20\x17\
+\xcc\x1f\x78\x89\xf2\x02\x1a\x75\xcd\x94\xd2\xd2\xaf\xe8\x18\x9c\
+\x4b\x2b\x2a\xfe\xa4\x7a\x7f\xef\x70\x3a\xdf\x59\x85\x27\xd2\x64\
+\x85\xd6\x43\x09\x77\x31\x49\xe8\x33\xf0\x80\xc7\x34\xde\x55\x06\
+\xc3\xc3\x14\xe7\x17\x21\x5b\xcf\x9c\x3c\xe0\x70\xbc\x8b\x6d\x7f\
+\x1e\x43\xf3\xbd\x24\x99\xe9\x7e\x73\xc0\xfc\x81\x97\x2c\x4f\x2f\
+\x14\x1a\x3e\x0e\x87\xd7\xd3\x31\x38\xb1\x4d\x13\x51\xb9\xbf\xd7\
+\x98\xcd\x8f\x83\xd9\x00\x8f\xeb\xbc\x73\xb5\xda\x1b\xa8\xcc\x2f\
+\xa6\x82\x02\x13\x9b\x2f\x9b\xbc\xec\xf3\xcd\x40\x37\x49\xb3\x36\
+\xdf\xa7\x6a\xfc\x60\x86\xc0\x43\x8b\x9c\xc7\x93\x8e\xf0\x7a\xa7\
+\x53\x7d\x5a\x53\x27\x10\x18\xa9\xdc\xdf\xf3\x35\x9a\x5b\xc0\x6c\
+\x80\xc7\x75\x5e\x3f\x85\xe2\x3c\x2a\xf3\x4b\x40\xad\xaa\x65\xeb\
+\x65\x93\xb9\xa5\xa5\x3f\x34\x15\x16\x0e\xe2\x4a\xbe\x07\x33\x04\
+\x5e\xaa\xbc\xbc\xfb\xec\xf6\xb7\xa8\x1c\x9c\x3e\x89\xa4\x94\xca\
+\xfd\xed\xa3\x50\x9c\xbb\xaa\xb2\xb2\x3d\x51\xad\xc4\xb4\xb8\xaa\
+\xb2\x7d\x11\xb6\xad\x84\xd0\xe7\x95\x49\x30\x80\x07\x3c\xaa\x79\
+\x95\x52\x69\x1b\x95\xf9\xa5\x4a\xab\x3d\x91\x8d\xe6\xff\xb2\xc7\
+\x33\xa3\x98\xc7\xd3\x82\xf9\x83\x19\x02\x0f\x5f\x2e\x33\x18\xee\
+\xa5\xea\xc8\xa6\x46\xa3\x39\x91\xca\xfd\x0d\x49\x24\x75\x60\x36\
+\xc0\xe3\x3a\xcf\xc0\xe7\x3b\xa8\xcc\x2f\xbd\xb4\x9a\x8b\xd8\x74\
+\xe6\x64\x69\x65\xe5\x01\x74\x36\x10\xdb\x8d\x1e\x60\xfe\x60\x86\
+\xc0\xeb\xb0\x0c\x54\x2a\x2f\xa0\x62\x70\xf6\xd6\xeb\x2f\xa6\x72\
+\x7f\xe5\x42\xa1\x1e\xcc\x06\x78\x5c\xe6\x2d\xaf\xac\x3c\xd2\xf1\
+\x7a\x76\xa6\xf3\xcb\xa9\x46\xc3\x2d\x6c\x31\xff\x31\xe1\xf0\x56\
+\xf4\x8e\x14\x2e\xe5\x7b\x30\x2f\xe0\xa5\x95\x47\x3e\x0b\x90\xc9\
+\xc1\x79\xa6\xc9\x78\x17\xc5\xfb\xdb\x63\x49\x45\xc5\x01\x30\x1b\
+\xe0\x71\x95\x37\xa9\xa4\xe4\x4b\xaa\xf3\xcb\x65\x56\xcb\x93\x6c\
+\x30\xff\xbb\xff\x7e\x31\x90\x08\xcc\x1f\xcc\x10\x78\x5d\x2c\xcd\
+\x72\xf9\x29\x2b\xab\xaa\x8e\x52\x31\x38\x6f\xb0\xdb\x5e\xa1\x7a\
+\x7f\xc7\x86\x42\xdb\x57\xe2\x89\xb6\xa3\x56\x54\xa2\xc4\x5b\xd9\
+\xbe\x10\xdb\x4e\x42\xe8\xf3\x8a\x2e\x7e\x1f\x4f\xc0\x03\x1e\xd5\
+\xbc\xd7\x7d\xbe\xf9\x54\xe7\x97\x87\xec\xf6\x0f\x98\x6c\xfe\xb3\
+\x4b\x4b\x7f\x44\x79\x8d\x6b\xf9\x1e\xcc\x0b\x78\x69\xe5\x39\x45\
+\xa2\xf0\xc2\x8a\x8a\xdf\xa9\x1a\x9c\x4f\x7a\x3c\x93\xa9\xde\xdf\
+\x67\x9d\xce\x71\x2b\x2b\x2a\xda\x3b\x6a\x05\xa6\x45\x58\x02\x5d\
+\x48\x12\xfa\xbc\xa2\x93\xdf\x26\x22\xe0\x01\x8f\x0e\xde\x9d\x66\
+\xf3\xab\x54\xe7\x97\xb7\x7d\xbe\x45\x4c\x35\xff\x97\xbd\xde\x39\
+\xe8\x51\x67\x2e\x9a\x7f\xc2\x4f\xff\x81\x19\x02\x2f\xde\x22\xe1\
+\xf1\x94\x93\x22\x91\xbd\x54\x0e\xce\x51\xa1\xd0\x7a\xaa\xf7\xf7\
+\x4a\xbd\xfe\x21\x30\x1b\xe0\x71\x95\x77\x9a\x5a\x7d\x0d\xd5\xf9\
+\x65\x7a\x34\xfa\x0d\xd3\xcc\x7f\x7e\x55\xe5\xc1\x0b\x75\xba\x3b\
+\xb1\xcd\x3b\x81\x83\xf9\x9e\x98\xfa\x3f\xe1\x49\x82\x24\x60\x86\
+\xc0\x8b\xb1\xe4\xbd\xe1\xf7\xcf\xa7\xfa\x9a\xdc\xfc\xb2\xb2\x5f\
+\xa9\xde\xdf\x9e\x45\x45\x67\x80\xd9\x00\x8f\xab\xbc\xa8\x54\xda\
+\x44\x71\x7e\x11\x10\x97\x0c\x99\x62\xfe\xa3\x22\x91\x9d\x41\xb1\
+\xb8\x82\xa3\xf9\xbe\x07\x3e\xe3\x6f\x4e\xa2\x53\x04\x8b\x3b\x99\
+\x5f\x18\xcc\x10\x78\xc7\x97\x7b\x2c\x96\xd7\xe9\xba\xa1\xa9\x80\
+\xc7\x53\x50\xb9\xbf\xe6\xfc\x7c\xcf\x0a\x3c\xf1\x2e\xc7\x84\x12\
+\xee\x02\x92\xd0\xe7\xe5\xf8\xbf\x27\x2b\xe0\x01\x8f\x6e\x9e\xc7\
+\x62\x32\x52\x99\x5f\x2c\xf9\xf9\x6e\x26\xdd\x30\x79\x97\xc3\xf1\
+\x9e\x58\x2c\x2e\xe0\xb0\xf9\x13\xef\xfb\x89\x5d\x00\xe0\x3f\x16\
+\xe1\x47\xff\x52\x30\x43\xe0\x75\xb6\x9c\xa5\x56\x5f\x4b\xe7\xdd\
+\xcc\xc1\x82\x82\x2a\x8a\xe3\x97\x33\xbf\xac\xec\x77\x30\x1b\xe0\
+\x71\x8d\xf7\x49\x38\xfc\x05\xd5\xf9\xa5\xae\xb0\x70\x00\x13\xcc\
+\x7f\x6a\x79\xf9\xcf\x6d\x5a\xed\xf9\x1c\xce\xf7\x3d\xf0\xb7\xfc\
+\xe6\x93\x0a\x80\x98\xaf\x07\x16\xe0\x47\xff\x12\xd2\xbb\x85\xc1\
+\x0c\x81\x77\x7c\x29\x97\xc9\x5a\x97\x97\x97\x1f\xa2\xf3\x51\xa6\
+\x53\x54\xaa\xcb\xa9\x8e\xdf\x08\x97\x6b\xce\x42\x2c\x61\x2e\x20\
+\x09\x7d\xc6\x62\xd1\xbe\x22\x05\xa1\xf5\x80\x07\x3c\xba\x79\xf7\
+\xba\x5c\x9f\x52\x9d\x5f\x2e\xd6\x6a\xef\xa2\xdb\xfc\x87\x05\x02\
+\x8b\x3c\x06\xbd\x97\xe3\xf9\x5e\x80\xeb\x78\x01\x10\xaf\x52\x10\
+\x92\x0a\x00\x09\x98\x21\xf0\xc8\x0b\x9a\x2d\x6c\x4e\x34\xfa\x23\
+\xdd\xcf\x31\xdf\x66\xb1\xbc\x44\x75\xfc\x2e\x35\x9b\x9f\x04\xb3\
+\x01\x1e\xd7\x78\xa7\xea\xf5\xb7\x50\x9d\x5f\x1e\x73\x38\x46\xd1\
+\x65\xfe\xf3\x2b\x2a\x0e\x5d\x60\x36\x3d\x54\x57\x57\x55\xc8\xf1\
+\x7c\x2f\xc2\xfd\x9c\x28\x00\x72\xe3\x5d\x23\x10\x90\x0a\x00\x31\
+\x98\x21\xf0\xc8\x0b\x7a\xf9\xcf\xa7\xa1\xd0\x66\x26\x4c\x62\xf2\
+\x06\xe9\xb9\x65\xaa\xe2\x57\xa3\x51\x9f\x8a\x25\x0f\x94\x40\x8e\
+\x25\xce\x65\x78\x22\x4d\x56\x68\xbd\x05\x38\x07\x78\xc0\xa3\x9b\
+\x17\xd4\x6a\xea\xa8\xce\x2f\x9f\x06\x83\x9b\xe8\x30\xff\x4f\x4a\
+\x4a\xf6\x84\x75\xda\xd6\x2c\xc8\xf7\x84\x87\x13\x05\x40\x5e\xac\
+\x53\xff\xb9\x78\x85\x40\x14\x00\x22\x30\x43\xe0\x75\x58\x4e\x78\
+\xde\xed\x9e\xc8\x94\x19\xcc\xe6\x96\x95\xfd\x44\x75\xfc\x02\x76\
+\xab\x71\x4e\x45\xc5\x11\x30\x1b\xe0\x71\x85\x37\x25\x1a\xfd\x95\
+\x86\x23\x61\x3e\xba\x84\x48\xb5\xf9\xdf\xe5\x74\x7e\x14\x72\x39\
+\x0c\x59\x90\xef\x89\xb3\xf7\x44\x01\xc0\x8f\x65\xfe\x39\x78\x75\
+\x90\x4f\xba\x5e\x00\x66\x08\xbc\xff\x59\xae\x35\x1a\x9f\x60\xda\
+\xf4\xa5\x5a\x81\xc0\x44\x75\xfc\x5e\xf3\xfb\x57\x83\xd9\x00\x8f\
+\x2b\xbc\xc7\xdc\x6e\xca\x27\xd5\x72\x88\x44\x25\x54\x9a\xff\xb4\
+\xf2\xf2\x5f\x48\xef\x0f\xc9\x86\x7c\x2f\x25\x15\x00\x82\x78\x37\
+\xfd\x91\x0b\x00\x7e\xc2\xb3\x04\x81\xb9\x66\x0d\xaf\x9f\x42\x31\
+\x98\x89\x73\xa1\x37\xca\x64\x27\x51\x1d\xbf\xcb\x0d\x86\x87\x96\
+\x97\x95\xb5\x27\xab\x65\x98\x16\x60\x09\x78\x3e\x49\xe8\xf3\xb2\
+\x14\x58\xc0\x03\x5e\xba\x78\xa7\xa8\x54\x57\x51\x9d\x5f\x06\x15\
+\x17\x5f\x42\x95\xf9\x8f\xf0\xfb\x97\xba\x8d\xfa\x40\x96\xe5\x7b\
+\xa2\x00\x10\xc5\xf4\x73\x7c\xa5\x1c\xd2\x33\x82\x60\xfe\xc0\xfb\
+\x9f\xc5\x27\x95\x56\x2c\x2a\x2b\xdb\xcf\xc4\xb9\xd0\xaf\x37\x1a\
+\x9f\xa2\x3a\x7e\x81\x82\x82\xea\x65\x78\x42\x4d\x54\x4b\x31\xa1\
+\x84\x3b\x8f\x24\xf4\x79\x69\x92\x1c\xe0\x01\x2f\xdd\x3c\x35\x9f\
+\x6f\xa1\x3a\xbf\xdc\x63\xb5\xbe\x45\xc5\xbb\x11\x2e\xb7\x98\x9f\
+\xaa\xae\xae\x90\x67\x61\xbe\x97\x26\x74\x0f\x1f\xa9\x00\xc8\x05\
+\xf3\x07\x5e\xc7\x45\x21\x14\xea\x26\x85\xc3\x5f\x33\x75\x06\xb3\
+\xb7\x7c\xbe\xc5\x34\xc4\xef\x84\x19\x91\xc8\x8f\x60\x36\xc0\x63\
+\x3b\xef\x63\xbf\x7f\x2b\x1d\xf9\x65\x6c\x30\xb8\x9d\x8a\x19\x12\
+\x2f\xb3\x58\x9e\xce\xd2\x7c\x2f\x49\x66\xba\xdf\x1c\x30\x7f\xe0\
+\x75\xb2\x08\xde\xf1\xf9\x96\x33\x79\xfa\xd2\x45\xe5\xe5\x07\x02\
+\x01\xaf\x92\xea\xf8\x3d\x60\xb1\xbc\x0f\x66\x03\x3c\xb6\xf3\xae\
+\x35\x1a\x9f\xa6\x3a\xbf\xa8\x79\x3c\x15\x55\xd3\x23\x63\xfb\x7a\
+\xb8\x44\xa5\x6c\x84\x7c\xdf\x35\x20\x25\xe3\x07\x73\xe5\x3e\xef\
+\x41\xbb\xfd\x43\x36\xcc\x85\x5e\xa1\x51\xf7\xa5\x3a\x7e\xf5\x85\
+\x85\x83\xc0\x6c\x80\xc7\x76\x9e\x4f\x28\xac\xa0\x3a\xbf\xb4\xc8\
+\x64\x27\x53\xf9\x6e\x84\x51\xc1\xe0\x26\xf4\xd4\x01\xe4\xfb\x34\
+\x2e\x60\xae\xdc\xe6\x5d\xa0\xd3\xdd\xc9\x96\x17\xa1\x0c\x36\x99\
+\xee\xa7\x21\x7e\xfc\x99\xe1\xf0\xcf\x4b\x4b\x4b\xdb\x3b\xd3\x12\
+\x4c\xf3\xb0\x04\x3b\x97\x24\xf4\x79\x49\x17\xbf\x8f\x27\xe0\x01\
+\x2f\xdd\xbc\x31\x7e\xff\x6e\x3a\xf2\xcb\xf5\x46\xe3\xd3\x54\xbf\
+\x18\xe9\x1a\x83\xe1\x71\xc8\xf7\x60\xfe\xc0\x4b\x60\xa9\x2b\x2a\
+\x3a\x71\x7e\x65\xe5\x11\xb6\xbc\x05\xed\x19\x8f\x67\x16\x1d\xf1\
+\xbb\xd7\x6c\x7e\x17\xcc\x06\x78\x6c\xe5\x5d\xa5\xd3\x3d\x4e\x47\
+\x7e\x79\xcf\xef\x5f\x45\xf5\xc1\xc5\xf2\xb2\xb2\xc3\xde\x82\x82\
+\x4a\xc8\xf7\x60\xfe\xc0\x8b\xb1\xb8\x84\xc2\xc0\xf4\xd2\xd2\xdf\
+\xd8\xf4\x0a\xd4\x39\x15\x15\x7f\x49\x24\x62\x09\xd5\xf1\xab\x29\
+\x28\xe8\x07\x66\x03\x3c\xb6\xf2\xd0\xb3\xf8\x54\xe7\x17\x5d\x7e\
+\xbe\x76\x41\x65\xe5\x51\x3a\xf2\xcb\xa7\xc1\xe0\x66\x74\x5f\x13\
+\xe4\x7b\x30\x7f\xe0\x75\xb2\x14\xf3\xf9\xc5\xa3\x22\x91\xdd\x6c\
+\x7c\x0b\x5a\x7d\x61\x61\x7f\x1a\xe2\x97\x33\x31\x18\xfc\x1a\xcc\
+\x06\x78\x6c\xe3\xbd\xe7\xf5\xae\xa5\x23\x5f\xf5\xd3\x6a\x86\xd0\
+\x99\x5f\x88\xc7\x86\x21\xdf\x83\x19\x02\x8f\xb4\x68\x34\xea\xfc\
+\xe7\x7c\xbe\x79\x6c\x7d\x05\xea\xad\x66\xf3\x4b\x74\xc4\xef\x72\
+\xad\xf6\xe1\x25\xd1\x68\xfb\x62\x4c\x73\xb1\xc4\x3a\x87\x24\xf4\
+\x19\x7d\xbf\x24\x05\x01\x0f\x78\x99\xe4\x9d\xaa\x54\x5e\x4d\x47\
+\xbe\xba\xcf\xed\x1a\x43\x67\x7e\x59\x5a\x51\x71\x04\xcd\xe3\x01\
+\xe6\x0f\x66\x08\x3c\x12\xef\x46\x9b\xed\x35\x36\xbf\xff\xfc\xb3\
+\x70\x78\x27\x1d\xf1\x43\x93\xa8\x2c\x8c\x46\x8f\x82\xd9\x00\x8f\
+\x2d\xbc\x39\xe1\xf0\x9f\x32\x1e\xaf\x90\xea\x7c\x85\x26\xe4\x99\
+\x58\x5a\xfa\x13\xdd\xf9\x65\x74\x30\xb8\x0d\xdb\x2c\x61\x36\x9b\
+\x7f\xc2\x4f\xff\x81\xb9\x72\x9f\x57\xa7\x52\x9e\xca\x85\x57\xa0\
+\xea\xf3\xf3\x9d\x74\xc4\xef\x49\xa7\x73\x16\x98\x0d\xf0\xd8\xc2\
+\xbb\xc7\x6c\x1e\x49\x47\xbe\x8a\x68\x35\xbd\x98\x92\x5f\x6e\x30\
+\x18\x9e\xc9\xd2\x7c\x4f\x4c\xfd\x9f\xf0\x24\x41\x12\x30\x57\xee\
+\xf3\xca\xd4\xea\xfe\xcf\x79\xbd\x73\xd9\xfc\xfe\xf3\xf3\xb5\xda\
+\xdb\xe9\x88\x5f\xbd\x56\x73\x16\x98\x0d\xf0\xd8\xc2\xeb\xea\xd9\
+\xff\x4c\xe7\xab\xab\xad\xd6\x61\x4c\xc9\x2f\x4b\xcb\xcb\x8f\x44\
+\xa4\xd2\xfa\x2c\x34\xff\xdc\x84\x0a\x00\xd2\xfb\x84\xa5\x60\xae\
+\xd9\xc3\x0b\xca\x64\xb5\x2f\x3a\x9d\x53\xd8\x66\xfe\x48\xef\xf9\
+\xfd\xab\xe9\x88\x5f\x53\x53\x5d\xe1\x3b\x7e\xff\x36\x94\x80\x17\
+\xe1\x89\x39\x59\xa1\xf5\xd0\xfa\xb3\x49\x02\x1e\xf0\xd2\xcd\x1b\
+\xe1\x72\xcd\xa3\x23\xbf\xa0\x31\x32\x26\x12\xf9\x8a\x49\x07\x17\
+\x1f\x05\x83\x3b\x03\x01\xaf\x26\x8b\xcc\x9f\x78\xdf\x4f\xec\x02\
+\x00\xff\xb1\x08\x3f\xfa\x97\x82\xb9\x66\x1f\xcf\x2f\x14\x96\x3f\
+\xe3\x74\x8e\x67\x8b\xf9\xc7\xba\x0c\x40\x45\xfc\x06\xea\x74\xd7\
+\x2e\x2a\x29\x69\x5f\x9c\x82\xd0\x7a\x73\xb0\xe4\x3c\x9b\x24\xf4\
+\x19\x78\xc0\x4b\x37\xaf\xb6\xb0\x70\x20\x1d\xe3\x23\xa2\x51\xb7\
+\x31\xf1\xcc\xe2\x75\x36\xeb\xcb\x59\x62\xfe\x7c\xfc\x6d\xbf\x79\
+\x31\xa7\xfe\xc7\x7f\x2c\xc0\x8f\xfe\x25\xa4\x77\x0b\x83\xb9\x66\
+\x21\xcf\x29\x12\x45\x1e\xb7\xdb\x47\x2f\x2d\x2f\x3f\xca\x86\xf7\
+\x9f\x5f\xa4\xd3\x0d\xa5\x23\x7e\x85\x85\x32\xe1\xc4\x40\xe0\x3b\
+\x30\x1b\xe0\x31\x95\xf7\x91\xd7\xbb\x05\xeb\xc2\x3d\xe8\x18\x1f\
+\x37\x9a\x4c\x2f\xd2\x91\x0f\xe2\xf1\xe6\x56\x54\x1c\x2d\x51\xab\
+\x7a\x73\x3c\xdf\x0b\x70\x1d\x2f\x00\xe2\x55\x0a\x42\x52\x01\x20\
+\x01\x33\x04\x9e\x4d\x2c\x0e\x3c\x64\xb7\x7f\xb4\xa4\xbc\xfc\x08\
+\x53\xcd\x1f\xe9\x43\xbf\x7f\x3d\x5d\xf1\x1b\xac\x56\xdf\x0e\x66\
+\x03\x3c\xa6\xf2\xfa\xca\xe5\xe7\xd3\x91\x5f\xec\x76\x6b\xee\xa4\
+\x70\xf8\x1b\xa6\x99\x3f\xc1\x1b\x1d\x0a\xa1\x27\x88\x44\x1c\xcd\
+\xf7\x22\xdc\xcf\x89\x02\x20\x37\xde\x35\x02\x01\xa9\x00\x10\x83\
+\x19\x02\x8f\xbc\x98\xf3\xf3\x3d\xf7\xd9\x6c\x23\x97\x94\x96\x1e\
+\x66\x9a\xf9\x13\xb2\x8b\xc5\x7e\x9a\xe2\x27\x9e\x10\x08\x7c\xb7\
+\x08\x4f\xd6\xb1\xb4\x10\x13\x4a\xe0\xb3\x48\x42\x9f\x17\x26\xb0\
+\x2e\xf0\x80\x97\x2c\xef\x43\x9f\x0f\xbd\xf6\x37\x87\x8e\xfc\x52\
+\x2a\x16\x37\x31\xd5\xfc\x09\xde\xcd\x46\xe3\x8b\x1c\xcc\xf7\x84\
+\x87\x13\x05\x40\x5e\xac\x53\xff\xb9\x78\x85\x40\x14\x00\x22\x30\
+\x43\xe0\x75\xc5\xf3\x68\x35\xe1\xdb\xec\xf6\xf7\x66\x95\x97\x1f\
+\x64\x92\xf9\x23\x5d\x6f\x36\x3f\x4f\x57\xfc\xce\x52\xa9\x6e\x04\
+\xf3\x02\x1e\xd3\x78\x2d\x45\x45\x67\xd1\x95\x5f\x1e\xb4\xd9\xde\
+\x67\xb2\xf9\x2f\xff\xfb\xa9\x80\xa3\x11\x89\xa4\x91\x43\xf9\x9e\
+\x38\x7b\x4f\x14\x00\xfc\x58\xe6\x9f\x83\x57\x07\xf9\xa4\xeb\x05\
+\x60\x86\xc0\x8b\xcb\x73\x1b\xf5\x81\x9b\xad\xd6\xb7\x16\x94\x96\
+\x1e\x58\x5e\x56\xd6\x9e\xac\xd0\x2b\x49\x17\xe0\xaf\x29\x25\x84\
+\x3e\x2f\x4b\x81\x45\xf0\x26\x44\x22\x3f\x46\x22\x41\x15\x4d\xf1\
+\x13\x8c\xf3\xfb\xbf\x5e\x14\x89\xb4\x77\xa6\x85\x98\x66\x63\x09\
+\x79\x16\x49\xe8\xf3\xc2\x2e\x7e\x1f\x4f\xc0\x03\x5e\x3c\xbd\xe7\
+\x76\x6f\x20\xae\xfd\x53\x9d\x5f\x64\x3c\x5e\x11\x56\xa8\xec\xa7\
+\x33\x1f\x24\xca\xfb\x2c\x10\x40\x6f\x47\x94\x70\x24\x3f\x4b\x49\
+\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\x2c\x41\x60\x86\xc0\
+\xc3\x79\x72\xa1\xd0\x80\x4e\xa3\x31\x65\xb0\xf7\xd6\x6a\x2e\xa1\
+\x2b\x7e\x27\x16\x17\x5f\x06\xe6\x05\x3c\xa6\xf0\xea\x0a\x0a\x06\
+\xd0\x95\x5f\xce\x2a\x2e\xbe\x8e\x0d\xe6\x4f\xe8\x36\x93\x69\x38\
+\x47\xf2\x33\x51\x00\x88\x62\xfa\x39\xbe\x52\x0e\xe9\x19\x41\x30\
+\x7f\xe0\xa5\xcc\x53\xf2\x78\x1a\x34\xcb\xd6\xbc\x92\x92\x3f\xe8\
+\x1c\xec\xcf\x7b\x3c\x0b\x68\x8c\xdf\x09\x6f\xb9\xdd\x6b\x16\xe2\
+\x49\x1c\x69\x01\x26\x94\xc0\x67\x92\x84\x3e\x2f\x20\xfd\x26\x19\
+\x01\x0f\x78\x89\xe8\x19\x9b\x6d\x2a\x9d\xf9\xe0\x43\xbf\x7f\x03\
+\x5b\xcc\x1f\x69\x69\x59\xd9\xd1\xa8\x58\xdc\xc2\x81\xfc\x2c\x4d\
+\xe8\x1e\x3e\x52\x01\x90\x0b\xe6\x0f\xbc\x74\xf1\x24\x58\x2d\x70\
+\x95\xc1\xf0\xf8\xdc\x68\xf4\xb7\x65\xf8\x80\x24\x84\x0d\xb2\x63\
+\x03\x72\x1e\x49\xe8\xf3\xd2\x0e\xbf\x4b\x54\x5d\xf1\x8c\x42\xa1\
+\x9b\xae\xf8\x95\x48\x24\x0d\x60\x5e\xc0\xa3\x93\x37\x2f\x14\x3a\
+\x64\xce\xcf\xf7\xd2\x95\x0f\x82\x42\x61\x55\x77\xc6\x6f\xba\xf3\
+\x41\xa2\xbc\xcf\x03\x81\x3d\xe4\x4b\x01\x2c\xcd\xcf\x89\x3d\xbd\
+\x47\x2a\x00\xc0\xfc\x81\x97\x76\x5e\x01\x8f\xa7\xb8\x42\xaf\x7f\
+\x68\x56\x24\xf2\x33\xd5\x83\xfd\x26\xa3\xf1\x05\x3a\xe3\xf7\x90\
+\xd9\xfc\x29\x98\x17\xf0\xe8\xe2\x5d\xaf\xd3\xbd\x48\x67\x3e\x78\
+\xc0\x6a\xfd\x80\x6d\xe6\x4f\xe8\x0e\x93\xe9\x95\xac\xc8\xf7\xa9\
+\x1a\x3f\x98\x21\xf0\x92\x59\x8a\x78\x3c\xd9\xc5\x7a\xfd\x7d\x13\
+\xa3\xd1\x9f\xa8\x1a\xec\xe8\xec\x83\xec\xd8\x9f\xa6\x27\x7e\xaa\
+\x82\x02\xd3\xf8\x50\xe8\xb7\x19\x58\x32\x26\x34\x13\xd3\xfc\x70\
+\xb8\x7d\x41\x0a\x42\xeb\xcd\x24\xb1\x80\x07\xbc\xae\xf4\xb9\xcf\
+\xf7\x8d\x52\x20\x90\xd3\x95\x0f\x74\x02\x81\x71\x71\x34\x7a\x88\
+\x8d\xe6\x4f\xa8\xac\xa8\xa8\x17\xbc\x22\x18\xcc\x0b\x78\x69\xe4\
+\xb9\xcc\x26\xeb\xd4\x68\xf4\x4f\xaa\x06\xfb\x05\x6a\xf5\x1d\x74\
+\xee\xef\x20\xad\xf6\x16\x30\x2f\xe0\x51\xcd\x6b\x2a\x2c\x3c\x8d\
+\xce\x7c\x70\x9d\xc1\xf0\x0c\x9b\xcd\x1f\xad\xf7\x71\x38\xfc\x65\
+\xc8\xe5\x30\x80\xf9\x83\x79\x01\x2f\x4d\xbc\x3e\x5a\xed\x10\x2a\
+\x07\xfb\xa4\x50\xe8\x6b\x6c\x13\xf2\xe9\xda\x5f\xf4\x12\x94\x17\
+\x5c\xae\xa5\x60\x5e\xc0\xa3\x8a\xf7\xb0\xc5\xf2\x19\x9d\xf9\x00\
+\x9d\xed\x9b\x5d\x52\xf2\x2b\x9b\xcd\x9f\xe0\xdd\x6c\xb7\xbd\x03\
+\xe6\x0f\xe6\x05\xbc\x34\xf1\xde\x0c\x04\x36\x52\x3d\xd8\x07\x2a\
+\x95\x17\xd2\x19\x3f\xaf\x4a\x59\x36\x2b\x14\xfa\x0b\xcc\x0b\x78\
+\x99\xe6\x4d\xf6\xfb\x7f\x76\x9a\x8c\x2e\x3a\xf3\xc1\x60\xb5\xfa\
+\x36\x2e\x98\x3f\xd2\x8b\x3e\xdf\x52\x8f\xc5\x64\x04\xf3\x07\xf3\
+\x02\x5e\x37\x79\xd5\x1a\xf5\xc9\xf3\xd1\x20\x2b\x2d\x6d\x5f\x96\
+\x82\xd0\x7a\x68\xfd\x79\x24\x25\xc2\xfb\xc8\xef\xdf\x84\x6d\xce\
+\x09\x74\xc6\xef\x0c\x85\xe2\xe6\xf9\x78\xc2\x4f\x54\xf3\x30\x21\
+\x43\x98\x4e\x12\xfa\x3c\x2f\x49\x0e\xf0\xb2\x87\xd7\x4b\xad\xbe\
+\x94\xe6\x7c\xc0\x9f\x18\x0c\x7e\x95\xce\xf1\x9b\xee\x7c\x10\x8f\
+\xf7\x69\x28\xf8\xf5\xe5\x66\xd3\x33\x6e\xad\xba\x14\x8e\xfc\xc1\
+\xbc\x80\x97\x26\xde\x53\x6e\xf7\xec\x25\xf8\x40\x4b\x56\x68\x3d\
+\x34\xc0\xe7\x92\x84\x3e\x27\xca\xeb\x59\x54\x74\x36\xcd\xf1\xeb\
+\xf1\x8c\xcd\x36\x63\x7e\x28\xd4\x9e\x88\xe6\x61\x9a\x81\x25\xf4\
+\xe9\x24\xa1\xcf\xf3\x12\x5c\x1f\x78\xd9\xc7\xbb\xdd\x62\xf9\x98\
+\xee\x7c\x70\xa6\x4a\x75\x7d\x26\xc6\x6f\xa6\x79\xf3\x22\x91\xfd\
+\xf7\xd8\xed\x9f\xa2\x83\x94\xfa\xfa\xea\x22\x38\xed\x0f\xe6\x05\
+\xbc\x74\x9e\x06\xd7\x6a\xaa\xe9\x1c\xec\x9f\xf8\xfd\xdb\x78\xf8\
+\xcb\x50\xe8\x8a\x9f\x42\x28\xd4\x4d\xf4\xfb\x7f\x00\xf3\x02\x5e\
+\xba\x79\xef\xfb\xbc\xfb\x82\x4e\x1b\xdd\x37\xac\x89\x26\x87\x42\
+\xff\x61\x93\xf9\xbf\xe1\x76\x2f\x39\xb9\xb8\x78\x88\xcf\x6a\x36\
+\x64\xd3\xdd\xfe\x09\x3f\xfd\x07\xe6\x05\xbc\x74\xf0\xee\x35\x9b\
+\xdf\xa3\x7b\xb0\xf7\x93\xcb\x2f\xa4\x3b\x7e\xf5\x12\xc9\xa0\xb9\
+\xa1\xd0\x51\x30\x2f\xe0\xa5\x8b\x37\x39\x18\x3c\x18\x51\x16\xf7\
+\xa2\x3b\x1f\xa0\x6b\xff\x6c\x30\xff\x89\xc1\xe0\xd7\x57\xe9\x74\
+\x8f\xa3\x37\x9b\x66\x61\x7e\x26\xa6\xfe\x4f\x78\x92\x20\x09\x98\
+\x21\xf0\xba\xc3\x53\x15\x14\x18\x17\x94\x94\x1c\xa4\xfb\x34\xdf\
+\x68\xbf\x7f\x77\x69\x69\x44\x4e\x77\xfc\x2e\xd3\x68\x1e\x9b\x87\
+\x27\x6f\xb2\xb0\xc2\xe0\x7f\x8c\x81\xd0\xdc\x4e\x7e\x9b\x88\x80\
+\x97\x1d\xbc\x93\x75\xba\xdb\xe9\xce\x07\x0a\x1e\xaf\x60\x6a\x28\
+\xf4\x03\x53\xcd\x7f\x41\x34\xfa\xd7\xc3\x36\xdb\xc7\x95\x05\x05\
+\x7d\xe9\x3e\x13\x48\xb3\xf9\xe7\x26\x54\x00\x90\xde\x27\x2c\x05\
+\x33\x04\x5e\x77\x78\x57\xeb\xf5\x4f\xd2\x6d\xfe\x04\x6f\x90\x4e\
+\x7b\x03\x03\xe2\x97\xf3\x8c\xc5\x32\x63\x5e\x30\xd8\x4e\x68\x2e\
+\xa6\xe9\x28\xa1\x77\xd0\x5c\xd2\x6f\x92\x11\xf0\xb2\x83\x77\xbb\
+\xf5\x7f\xae\xfb\xd3\x96\x0f\x2e\xd2\x68\xee\x61\xa2\xf9\xbf\xe9\
+\xf1\x2c\x3f\x45\xa9\xbc\xb2\xe3\x84\x60\x59\x6a\xfe\xc4\xfb\x7e\
+\x62\x17\x00\xf8\x8f\x45\xf8\xd1\xbf\x14\xcc\x10\x78\xa9\xf2\xd0\
+\x91\xc1\xcc\x70\xf8\x67\x26\x98\x3f\xe2\x7c\x16\x89\x7c\x8f\x5f\
+\x2b\xa5\x35\x7e\x12\x1e\xaf\xf8\x63\xb7\x7b\x2f\x98\x21\xf0\x52\
+\xe5\xbd\xec\x72\x6d\xf4\xf9\x3c\x5a\xba\xf3\x01\xba\xb7\x65\x76\
+\x49\xc9\x6f\x4c\x31\xff\x49\xa1\xd0\xb7\xe8\xa0\xc3\x94\x9f\xef\
+\x83\xfc\x7c\xdc\xcf\xf9\xf8\xdb\x7e\xf3\x62\x4e\xfd\x8f\xff\x58\
+\x80\x1f\xfd\x4b\x48\xef\x16\x06\x33\x04\x5e\xd2\xbc\xb3\x54\xaa\
+\x1b\x99\x62\xfe\x84\x86\x98\x4d\x2f\x30\x21\x7e\xd6\xbc\xbc\xd0\
+\x64\xbf\xff\x57\x30\x43\xe0\x25\xcb\x1b\xe5\xf3\x7d\xe7\x32\xe8\
+\x82\x4c\xc8\x07\xf7\x99\xcd\x23\xe9\x36\xff\xf9\xd1\xe8\x81\x47\
+\x6d\xb6\x4f\xab\x0b\x0a\xfa\x13\xa7\xf8\x21\x3f\x1f\xe7\x09\x70\
+\x1d\x2f\x00\xe2\x55\x0a\x42\x52\x01\x20\x01\x33\x04\x5e\x8a\xbc\
+\xdc\xcf\x82\xc1\xbd\x4b\xf0\x81\x1c\x4f\x8b\x31\xa1\x01\x3e\x87\
+\x24\xf4\x79\x71\x82\xeb\x27\xca\x9b\x8b\x25\x0b\x7d\x7e\xbe\x93\
+\x09\xf1\xab\x52\xab\x4e\x99\x14\x0c\x1c\x9a\x86\x25\x75\x42\x73\
+\xf0\x44\x9f\xac\xd0\x7a\x64\x0e\xf0\xb8\xc9\x1b\x17\xf0\xff\x19\
+\x54\xab\x9a\x99\x90\x0f\xbc\x42\x61\xe5\xa2\xd2\xd2\xa3\x54\x8e\
+\x5f\x32\xef\x2d\xaf\x77\xe5\x69\x4a\xe5\x35\x52\x1e\x4f\x0e\xf9\
+\xb9\x53\x9e\x08\xf7\x73\xa2\x00\xc8\x8d\x77\x8d\x40\x40\x2a\x00\
+\xc4\x60\x86\xc0\x4b\x95\x87\x9e\xbd\x5f\x12\x8d\xb6\x27\xa2\xc5\
+\x98\xe6\x62\x03\x7a\x0e\x49\xe8\xf3\xe2\x04\xd7\x4f\x96\xf7\xa4\
+\xcd\x36\x9e\x29\xf1\xeb\xa7\xd1\x5c\x07\x66\x08\xbc\x44\x78\x53\
+\x42\xa1\x23\xf5\x4a\xe5\x39\x0c\xc9\x07\x3d\x5e\x77\xb9\x96\x50\
+\x3d\x7e\x27\x05\x83\xff\xb9\x4e\xaf\x7f\xc6\x2a\x16\x07\x21\x3f\
+\xc7\xbd\x87\x4f\x4c\x2a\x00\xf2\x62\x9d\xfa\xcf\xc5\x2b\x04\xa2\
+\x00\x10\x81\x19\x02\xaf\x3b\xbc\xb7\x3c\x9e\x95\x4c\x34\x7f\x42\
+\x15\x52\x69\x2f\xa6\xc4\xef\x02\xbd\xfe\xa9\x39\x81\x40\xfb\xdc\
+\x14\x84\xd6\x9b\x86\x0c\xa2\x83\x80\xc7\x3d\xde\x20\x8d\xe6\x36\
+\xa6\xe4\x83\x7e\x0a\xc5\x79\x54\x8f\xdf\x9b\x8d\xc6\x97\xd0\x99\
+\x45\xc8\xcf\x71\x79\xc4\xd9\x7b\xa2\x00\xe0\xc7\x32\xff\x1c\xbc\
+\x3a\xc8\x27\x5d\x2f\x00\x33\x04\x5e\xca\xbc\xa8\x58\xdc\xcc\x64\
+\xf3\x47\xfa\xd4\xe7\xdb\x89\x6d\xaa\x90\x29\xf1\xbb\x56\xa3\x79\
+\x01\xcc\x10\x78\x5d\xf1\xce\xd1\x69\x1f\x62\x4a\x3e\x28\xe0\xf1\
+\x14\x53\x82\xc1\xef\xa9\x1e\xbf\xc3\x9d\xce\x39\x90\x9f\x13\xe2\
+\x49\x49\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\x2c\x41\x10\
+\x6c\xe0\x75\xc1\x7b\xda\x6e\x9f\xc4\x64\xf3\x27\x84\xee\x16\x66\
+\x50\xfc\x7a\xdc\xaa\xd7\xbf\x31\x07\x4f\xfc\xf1\x34\x1b\xd3\x54\
+\xcc\x18\x3a\x6a\x76\x82\xeb\x03\x8f\x3d\xbc\x8b\xf4\xfa\x67\x99\
+\x94\x0f\xee\x35\x9b\xdf\xa5\x63\xfc\x2e\x8c\x46\x8f\x14\xf3\x78\
+\x5a\xc8\xcf\x71\x79\x44\x01\x20\x8a\xe9\xe7\xf8\x4a\x39\xa4\x67\
+\x04\xc1\xfc\x81\xd7\x2d\x1e\x7a\xfc\x66\x51\x34\x7a\x94\xe9\xe6\
+\x8f\xb4\x28\x12\x39\xec\x11\x89\xa2\x0c\x8a\xdf\x09\x43\xf5\xfa\
+\x0f\xc0\x0c\x81\x47\x30\x86\x18\x0c\xaf\x32\x29\x1f\x54\x4a\xa5\
+\x6d\x74\x8e\xdf\xd3\x8b\x8b\xaf\x83\xfc\x1c\x97\x27\x4d\xe8\x1e\
+\x3e\x52\x01\x90\x0b\xe6\x0f\xbc\x74\xf0\x86\x9a\x4c\x6f\xb2\xc1\
+\xfc\x09\xbd\xe3\x76\xaf\x26\x5f\x57\x64\x40\x7b\x9c\x70\xab\x5e\
+\xff\x16\x98\x21\xf0\x2e\x31\x1a\x5e\x60\x58\x3e\x10\xa3\x19\x35\
+\xe9\x1c\xbf\xaf\xb9\x5c\x8b\x20\x3f\xc7\xe5\x25\xf6\xf4\x1e\xa9\
+\x00\x00\xf3\x07\x5e\xb7\x79\x4a\x1e\x4f\x33\xaf\xa4\xe4\xaf\xc5\
+\xf8\xc0\xee\xa8\x45\x98\xd0\x00\x9f\x4d\x12\xfa\xbc\xa8\x8b\xdf\
+\xc7\x53\xba\x78\xe7\xa9\xd5\x77\x30\xac\x3d\x7a\x5c\xab\xd1\x0c\
+\x9b\xe3\xf7\xb7\x93\x35\x1b\xd3\xd4\x63\x06\x41\x12\x32\x8d\x0e\
+\xbf\x4b\x54\xc0\x63\x2e\xef\x1c\xad\xf6\x51\xa6\xe5\x83\x6b\x8c\
+\xc6\xe7\xe8\x1e\xbf\x0b\xa3\xd1\xa3\x3a\x81\xc0\x08\xf9\x39\x0d\
+\xbc\x54\x8d\x1f\x82\x0d\xbc\xce\x96\x2b\x74\xba\x47\xd8\x66\xfe\
+\xc7\x8e\x40\x4a\x4a\x0e\x38\x25\x92\x52\xa6\xb5\xc7\xa5\x2a\xd5\
+\x63\xb3\x71\x63\x98\x85\x69\x0a\x66\x0e\x53\x82\x24\x61\x9f\x67\
+\xe1\xff\x9e\xac\x80\xc7\x4c\xde\xa4\x60\xe0\xe8\x29\x6a\xf5\x5d\
+\x4c\xcb\x07\xe5\x05\x05\x6d\xb3\x4a\x4b\x8f\x32\x61\xfc\x9e\xa3\
+\x54\xde\x02\xf9\x19\x5e\x11\x0c\x3c\x66\xf1\xc4\x53\x42\xa1\x1f\
+\xd9\x66\xfe\x04\xef\x5d\xbf\x6f\x5b\x20\xe0\xd5\x30\xad\x3d\x06\
+\x15\x16\x5e\x35\xc3\xe7\x3b\x0c\xe6\xca\x7d\xde\xf8\x80\xff\xaf\
+\x66\xb5\xea\x22\xa6\xe5\x83\x62\x3e\xbf\xf8\x93\x60\xf0\x6b\xa6\
+\x8c\xdf\xb7\x3c\x9e\x15\x90\x9f\xc1\xfc\x81\xc7\x20\xde\x69\x4a\
+\xe5\xb5\x8b\x4b\x4a\xda\x3b\x6a\x11\xa6\x39\xd8\xa0\x9d\x4d\x12\
+\xfa\xbc\xa8\x93\xdf\x26\xa2\x4c\xf2\xae\xb7\x58\xde\x64\x62\x7b\
+\x54\xa9\x94\xa7\x8f\xf5\xf9\xfe\x00\x73\xe5\x2e\xef\x63\xaf\xf7\
+\xc7\x52\x65\x71\x1f\x26\xf6\xbf\x07\xec\xb6\x71\x4c\x1b\xbf\x46\
+\x3e\xdf\x0e\xf9\x19\xcc\x1f\x78\xcc\xe0\xe5\x7c\xea\xf5\xee\x62\
+\xb3\xf9\x13\xaa\xd3\x68\xce\x64\x62\x7b\xf8\x35\xea\x86\xf7\x3d\
+\x9e\x6f\xc1\x5c\xb9\xc7\x7b\xdd\xed\xde\xe9\xd1\x6a\xa2\x4c\xcc\
+\x07\x27\xaa\xd5\xd7\x30\x71\xfc\x9e\xaf\x52\xdd\x05\xf9\x19\xcc\
+\x1f\x78\x0c\xe0\x35\xca\x64\xa7\x71\xc1\xfc\xd1\xe7\x89\x81\xc0\
+\xf7\x72\xa1\x50\xcf\xc4\xf6\xf0\xe8\xb4\xf6\x67\xcc\xe6\xf9\xb3\
+\x7c\xbe\xf6\x64\x35\x13\xd3\x14\xcc\x70\x26\x07\xfe\x11\xfa\x3c\
+\x33\x05\x16\xf0\xd2\xc7\xbb\xd7\x6c\x9e\x1c\xb0\x5b\x8d\x4c\xcc\
+\x07\x5e\x95\xb2\x6c\x52\x24\xf2\x07\x13\xc7\xef\xbb\x1e\xcf\x3a\
+\xc8\xcf\x60\xfe\xc0\x63\x00\xef\x75\x97\x6b\x29\x17\xcc\x9f\xe0\
+\xa1\x39\xce\xb1\xdd\xca\x67\x68\x7b\xe4\x5e\xa9\x56\x3f\x07\xe6\
+\xca\x6e\xde\x64\xbf\xff\xc8\x59\x3a\xed\x43\x4d\x4d\x75\x85\x4c\
+\xcc\x07\x41\x87\x4d\xf7\x8e\xdf\xbf\x8d\xc9\xe3\xd7\xa7\x51\x55\
+\x40\x7e\x06\xf3\x07\x1e\x8d\xbc\x90\x44\x52\xb7\x08\x1f\x94\x48\
+\x0b\x31\xa1\x01\x3e\x8b\x24\xf4\x79\x21\xe9\x37\xc9\x88\x2e\xde\
+\xcd\x06\xc3\x08\x26\xb7\x47\x93\x54\x7a\xe6\x78\x8f\xe7\x17\x30\
+\x57\xf6\xf1\x3e\xf4\x78\xbe\xaf\x52\x16\x9f\xc2\xe4\x7c\x70\xbf\
+\xdd\x3e\x9e\xe9\xe3\xf7\x42\x93\xf1\x71\xc8\xcf\x29\x31\x7b\x40\
+\x70\x80\x97\x16\xde\xe3\x56\xeb\xe7\x5c\x33\x7f\x42\x7d\xe4\xf2\
+\x0b\x98\xdc\x1e\x6a\x3e\xdf\xf2\xbc\xc5\xb2\x10\xcc\x95\x3d\xbc\
+\xfb\x2d\x96\x69\x76\xa3\xde\xc1\xe4\x7c\x70\xb6\xc1\x70\x1f\x1b\
+\xc6\xef\xdb\x7e\xff\x36\xc8\xcf\xc9\x19\x3f\x3e\xef\x4f\xc2\x93\
+\x04\x49\xc0\x0c\x81\xd7\xd5\xa2\xcb\xcf\x77\xcd\x2f\x29\x39\xc2\
+\x45\xf3\x47\x9a\x1d\x0e\xef\x77\x88\x44\x25\x4c\xbf\x01\xf3\xdc\
+\xe2\xe2\x7b\xa6\xba\xdd\x87\x66\x7a\xbd\xed\x84\x66\x60\x9a\x8c\
+\x19\xce\x24\xff\x3f\x42\x9f\x67\x90\x7e\x93\x8c\x80\xd7\x3d\xde\
+\x44\x8f\x67\xff\x89\x1a\xf5\xad\x4c\xcf\x07\x55\x2a\xe5\x89\x33\
+\x22\x91\xc3\x6c\x19\xbf\xae\x02\x49\x09\xe4\xe7\x84\xcd\x3f\x37\
+\xa1\x02\x80\xf4\x3e\x61\x29\x98\x21\xf0\xba\x5a\xd0\x5b\xff\x1e\
+\xb1\x5a\xc7\x8e\xf6\xfb\xf7\x72\xcd\xfc\x09\x8d\xf5\xf9\xbe\xe8\
+\xf8\x02\x12\x26\xb6\x87\x5b\x28\x2c\x7b\xd5\x6a\x5d\x0b\x66\xcd\
+\x3c\xde\x73\x16\xcb\x42\x74\x43\x1d\xd3\xf3\x81\x5b\xab\x2e\xfd\
+\x2c\x1c\xfe\x3f\x36\x8d\xdf\x21\x3a\xdd\xa3\x90\x9f\x13\x32\x7f\
+\xe2\x7d\x3f\xb1\x0b\x00\xfc\xc7\x22\xfc\xe8\x5f\x0a\x66\x08\xbc\
+\x44\x78\x1e\x8b\xc9\x52\xa9\x52\x0e\x3a\x5b\xaf\xbb\xef\x5e\x9b\
+\x6d\xec\x47\x1e\xcf\x8e\x05\x91\xc8\xd1\x45\x91\x48\x7b\x32\x5a\
+\x88\x69\x36\x36\xb0\x67\x91\x84\x3e\x2f\x4c\x92\x93\x4e\xde\x9b\
+\x2e\xd7\x2a\x6c\x57\xc5\x2c\x68\x8f\xdc\xb3\x14\x8a\xbb\x3e\xf3\
+\x79\xf7\x83\x59\xd3\xcf\x1b\xef\x72\xfd\x3a\xa0\xa8\xe8\xea\xa6\
+\xa6\x3a\x19\xd3\xc7\xaf\xc3\x64\xb0\xbf\xe7\xf3\xed\x66\xc2\x78\
+\x4b\x86\xf7\x89\xd7\xbb\x0b\xf2\x73\x5c\xf3\xe7\xe3\x6f\xfb\xcd\
+\x8b\x39\xf5\x3f\xfe\x63\x01\x7e\xf4\x2f\x21\xbd\x5b\x18\xcc\x10\
+\x78\x49\xf3\x8a\x78\x3c\x59\x58\x2c\x6e\x3a\xb3\xb8\xf8\xa6\xfb\
+\xcc\xe6\xf7\x3f\xf4\x78\xb6\xcc\x8f\x44\x8e\xb0\xc9\xfc\x09\x3d\
+\x69\xb3\x4d\x30\x1a\xf5\x79\xac\x28\xc6\xb4\x9a\xe8\x43\x16\xcb\
+\x1c\x30\x6b\xfa\x78\xf7\x1b\x0c\x63\x54\x05\x05\x26\x36\xf4\x97\
+\x50\xc8\xaf\x1e\xe6\x76\x2f\x63\x9b\xf9\x13\xf2\x0b\x85\xe5\x90\
+\x9f\xbb\xe4\x09\x70\x1d\x2f\x00\xe2\x55\x0a\x42\x52\x01\x20\x01\
+\x33\x04\x5e\x9a\x79\xe2\xb0\x44\x52\x8b\x5e\xeb\x79\xb7\xc9\xf4\
+\xce\xbb\x1e\xcf\xfa\xf9\xa1\xd0\x21\x26\x9b\x3f\xc1\xbb\xce\x6c\
+\x7a\x9d\x4d\xed\x51\xad\x52\x9e\xf9\xb6\xcd\xb6\x63\x06\x6e\x6e\
+\xc9\x68\x3a\xa6\x49\x98\xf9\x4d\xf4\xff\x23\xf4\x79\x7a\x0a\xac\
+\x6c\xe2\xa1\xcb\x30\xa8\xe8\x65\xcb\xf8\x45\x8f\x21\xde\x6b\xb3\
+\x7d\xce\x56\xf3\x47\xba\x5a\xab\x7d\x1a\xf2\x73\xa7\x3c\x11\xee\
+\xe7\x44\x01\x90\x1b\xef\x1a\x81\x80\x54\x00\x88\xc1\xbc\x80\x47\
+\x05\x4f\x26\x93\x8a\xc2\xca\xe2\x96\x13\x75\xda\x9b\x6e\xb4\x5a\
+\xde\x1d\xe1\xf1\xac\x9d\x1a\x89\x1c\x58\x80\x27\x82\x64\x85\xd6\
+\x43\x09\x63\x26\x49\xe8\x73\x3a\x78\x67\xea\x75\xf7\xb2\xac\x3d\
+\xf2\x4f\x2f\x2a\xba\x65\xac\xd3\xf9\xd3\x0c\x8f\xa7\x3d\x11\x4d\
+\xc7\x34\xc9\xe7\x6d\x9f\x48\x12\xfa\x3c\x3d\xc1\xf5\xb3\x91\xf7\
+\x89\xd3\xf9\xdd\x00\xa9\xf4\x0a\x74\x53\x26\x9b\xc6\xef\x95\x26\
+\xd3\xf0\x74\x8e\x8f\x74\x8f\xb7\x44\x78\x63\x7c\xbe\x2f\xb0\xdd\
+\xea\x01\xf9\xf4\x5f\xf7\xf0\x89\x49\x05\x40\x5e\xac\x53\xff\xb9\
+\x78\x85\x40\x14\x00\x22\x30\x2f\xe0\xd1\xc9\x93\xcb\x8b\x04\x76\
+\x91\x28\x3a\xa0\xa8\xe8\xd2\x5b\x0c\x86\x97\x5f\x75\xb9\x96\xcd\
+\x0a\x85\xf6\x33\x21\x19\x0d\xd2\x6a\x6e\x64\x5b\x7b\xc8\x78\xbc\
+\xc2\x0b\x94\xca\x87\xc6\x39\x9d\xbf\x82\xf9\xa7\x8f\x37\xd6\xe1\
+\xf8\xf1\x4c\x85\xe2\x76\x96\xdc\x23\xf2\x3f\xbc\x0b\x8c\xc6\xc7\
+\xd8\x6e\xfe\x84\xfc\x12\x49\x2d\xe4\xd3\xff\x79\x7a\x4f\x42\x2a\
+\x00\xf8\xb1\xcc\x3f\x07\xaf\x0e\xf2\x49\xd7\x0b\xc0\xbc\x80\xc7\
+\x44\x5e\xae\x35\x2f\x2f\x88\x4e\xf9\xd1\x99\x8c\xe6\x45\x22\x47\
+\xfb\xca\xe5\xe7\xb3\xb1\x3d\x0a\x78\x3c\xc5\xa5\xc5\xc5\x4f\x60\
+\x85\xc0\xef\xd3\x71\x03\x24\x34\x0d\x13\x32\xc0\x09\x24\xa1\xcf\
+\xd3\x3a\xfc\x2e\x51\x71\x99\x37\xc6\xe9\xfc\xe9\xdc\xe2\xe2\x7b\
+\xe5\x3c\x9e\x94\x8d\xe3\xed\x0c\x83\x7e\x28\x57\xcc\x1f\xe9\x06\
+\xbd\x7e\x18\xe4\xd3\xe3\x3c\x29\xa9\x00\x10\xc4\xbb\xe9\x8f\x5c\
+\x00\xf0\x13\x9e\x25\x08\x82\x0d\x3c\x9a\x78\xe8\xa6\x42\xba\x93\
+\xd1\xfc\x50\xe8\x30\x7a\x07\x02\x5b\xdb\x43\xc6\xe3\x15\x9d\x2d\
+\x97\xdf\xf5\x91\xc3\xf1\x2d\x98\x7f\xe2\xbc\x91\x76\xfb\xde\x93\
+\xe5\xf2\x1b\xb0\x10\x4a\xd8\x3a\xde\x06\xea\xb4\x37\x70\xc9\xfc\
+\x91\xc6\xf9\xfd\xdf\x62\xbb\x79\x02\xe4\xd3\xe3\x1c\x09\x7e\x26\
+\xbf\x47\xbc\x95\x72\x48\xcf\x08\x82\xf9\x03\x8f\xf1\xbc\xdb\x8d\
+\xc6\x37\x98\x90\x8c\xe6\x84\x42\x07\xab\x0b\x0a\x06\xb0\xbc\x3d\
+\xf8\xbd\x0a\x0b\x2f\x19\x66\xb3\x6d\x00\xf3\xef\x7a\x9d\x17\x4d\
+\xa6\xa5\x8d\x52\xe9\x59\xe8\x1a\x3f\x9b\xc7\x5b\x6f\x9d\xee\x8a\
+\x69\x25\x25\x47\xb8\x64\xfe\x84\xd0\xbc\x24\x90\x4f\x8f\xb3\xe2\
+\xdf\xc3\x47\x2a\x00\x72\xc1\xfc\x81\xc7\x16\xde\x2b\x4e\xe7\x12\
+\xa6\x24\xa3\x39\xe1\xf0\x81\x06\x99\xec\x14\x2e\xb4\x47\x50\xad\
+\x6a\xbe\x46\xaf\x7f\xe7\x53\x97\xeb\xf7\x69\x6e\x77\xfb\xf4\x14\
+\x84\xd6\x9b\xe8\xf5\xb4\x4f\xf0\x7a\x8f\x0b\x7d\x66\x23\x6f\x8c\
+\xc3\xf1\x7f\x57\xaa\x54\x2f\xa0\xcb\x4e\x5c\x68\xdf\x81\x7a\xdd\
+\x75\x5c\x35\x7f\xa4\xdb\x0c\x86\x57\x21\x9f\x1e\x53\x62\x4f\xef\
+\x91\x0a\x00\x30\x7f\xe0\xb1\x86\x37\xc5\xef\xff\x79\x61\x38\xdc\
+\xbe\x00\xd3\x2c\x6c\xe0\xcf\x24\x09\x7d\x46\xdf\x2f\x4c\x41\xa9\
+\xf2\xe6\x07\x83\x87\x7b\x17\x16\x9e\xc7\x95\xf6\x10\x89\x44\xd2\
+\x5e\x32\xd9\x45\x8f\xe8\xf5\xd3\x26\x39\x9d\x87\xa6\xe1\xc6\x19\
+\x4f\x53\x31\x4d\xc0\xcc\x74\x3c\x9a\x04\x07\x17\xfa\x3c\x35\xc1\
+\xf5\x99\xc0\xc3\xf6\xf7\xc0\xfd\x7a\xfd\xf8\x66\xa9\xf4\x6c\x2c\
+\x44\x02\xae\x8c\xb7\xd3\x74\xba\x3b\xe9\x1a\x1f\x54\xf1\x26\xfa\
+\xfd\x3f\x94\x97\x47\x8b\x20\x9f\x26\xc8\x4b\xd5\xf8\xc1\xbc\x80\
+\x47\x17\x4f\x2e\x14\xea\x51\x82\x98\x8f\x09\x25\x8c\x19\x24\xa1\
+\xcf\xf3\xf1\xc4\x92\xac\xba\xcb\x9b\x17\x0e\x1f\x3d\x49\xa1\x18\
+\xc2\xb5\xf6\x90\xf0\x78\xc5\xfd\xa4\xd2\x2b\x9e\x34\x1a\x67\xc7\
+\x2a\x06\xd8\x6c\xfe\xc8\xf4\x1f\xd6\xeb\xa7\xb4\xc9\x64\x17\xa2\
+\xa7\x25\xb8\x36\xde\x06\xeb\xf5\x8f\xd0\x3d\x3e\xa8\xe2\x55\x69\
+\xd4\xa7\x40\x3e\xcd\xf0\x02\xe6\x05\x3c\xba\x78\x15\x52\x69\x2f\
+\x26\x27\xa3\xb3\x95\xca\x5b\xb9\xda\x1e\x68\x66\xc7\x7a\x89\xe4\
+\xd4\x1b\x34\x9a\x37\x3e\xb0\xd9\xbe\x62\xb3\xf9\xbf\xe6\x70\xec\
+\xbd\x4a\xad\x1e\x51\x55\x50\x30\x90\x87\x3f\xc2\xc7\xc5\xf1\x76\
+\xa9\xd1\xf0\x7c\xb6\x98\x3f\xe2\xdc\x62\x31\x7f\x00\xf9\x14\xcc\
+\x1f\x78\x1c\xe5\x9d\xa1\x54\xde\xc8\xf4\x64\x74\x95\xc9\xf8\x72\
+\x7d\x7d\x75\x11\xd7\xdb\xc3\x9c\x9f\xef\xed\x5b\x58\x78\xd9\xf5\
+\x06\xfd\xfb\xaf\xda\x6c\xbb\x98\x6a\xfe\xe3\xbc\x9e\xa3\x2f\xd9\
+\xed\x5b\xaf\x36\x18\xde\x6d\xd3\xa8\xaf\xb4\x6b\x35\x61\xae\x8f\
+\xb7\x68\x34\xac\xbc\xdd\x6a\xf9\x38\x9b\xcc\x1f\xe9\xf3\x60\xe0\
+\xe7\x48\x24\xa8\x80\x7c\x0a\xe6\x0f\x3c\x0e\xf2\x6e\xb2\x58\xde\
+\x61\x43\x32\x7a\xc0\xe1\x98\xec\xf3\x79\xb4\xd9\xd4\xbe\xe8\x3d\
+\xf7\xe5\xc5\x8a\x93\x4e\x95\xcb\x6f\xbd\x45\xad\x1e\x39\xdc\x64\
+\x5a\x3b\xde\xe9\xfc\x6b\xaa\xcb\xd5\x9e\x88\xa6\x60\x1a\xef\xc1\
+\x0c\xdb\xe3\x39\x2e\xf4\x79\x4a\x82\xeb\x23\x61\x7f\xef\xcf\x61\
+\x26\xd3\xca\x9b\x34\x9a\xb7\x4e\x56\x28\x6e\x2a\x53\x16\x0f\x44\
+\x2f\xaa\xca\xb6\x17\x73\x3d\xe3\x72\x2e\xca\x16\xf3\x9f\x12\x0a\
+\x1d\xfc\xc0\xef\xff\x6a\x98\xcb\xb5\xf2\x01\x9b\x6d\xa2\x41\x28\
+\xf4\x40\x3e\x05\xf3\x07\x1e\x07\x79\x2f\xb8\x5c\x4b\xd9\x70\x24\
+\x82\xf4\x92\xcb\xb5\x5a\x97\x9f\xaf\xcd\xf2\xf6\xed\x21\xe7\xf1\
+\xf4\x3e\x81\xa0\xae\xa5\xa0\x60\xf0\xd9\x72\xf9\x3d\xd7\xa8\x54\
+\x23\x86\x6a\xb5\x9f\x3c\x6e\x30\xcc\x7a\xd9\x64\x5a\xf7\xbe\xd5\
+\xfa\xe5\x27\x76\xfb\x8f\x1f\x3b\x9d\xbf\x8f\x76\xb9\x0e\x92\xcd\
+\x7f\xbc\xc3\x71\xf0\x33\xbb\xfd\xb7\x8f\x6d\xb6\x1f\x46\x5a\xad\
+\x5f\x60\x45\xc5\x9a\xc7\x0c\x86\x99\x77\x6a\x34\xa3\xae\x56\xa9\
+\x86\x63\xbc\xa1\xcd\x52\xe9\x39\x1e\x81\xa0\x5a\xc9\xe3\x69\x78\
+\xf8\x94\xb0\xd9\x3a\x3e\x9c\x7a\x6d\xf8\x2d\x9f\x6f\x3b\x17\xcc\
+\x7f\x46\x20\xf0\xe7\x28\xaf\x77\xd7\x70\x87\x63\xe1\x43\x66\xf3\
+\xa7\xd7\xeb\x74\x2f\x0e\x56\x2a\xef\xec\x2d\x97\x5f\x58\x5e\x28\
+\xeb\xeb\x2d\x56\x54\xa3\xb7\x18\xa2\xf7\x19\x40\x3e\x05\xf3\x07\
+\x5e\x16\xf0\xd0\x29\x3e\x36\x25\xb7\x4f\xbd\xde\x3d\xb1\x1e\x23\
+\x83\xf6\xfd\x37\x0f\x25\x74\x74\x1a\x37\x1c\x0e\xe4\xc0\xf8\x48\
+\x9c\x17\xd1\x6a\x7a\x7d\x1a\xf0\x7f\xcf\xf4\xf1\x31\x3e\x14\xfa\
+\xed\x6d\xaf\x77\xc7\xf3\x36\xdb\xdc\xfb\x4c\xa6\x0f\xaf\xd1\x6a\
+\x9f\x39\x53\xa9\xbc\xa5\x57\x61\xe1\xb9\x51\xb1\xb8\x05\x5d\x5a\
+\x8a\x75\x33\x26\xe4\x53\x30\x7f\xe0\x65\x21\xcf\x65\x31\x79\xd8\
+\x78\x64\x83\x1d\xc9\xfc\xd1\xb3\xa8\xe8\x6c\x68\x5f\xe0\x65\x8a\
+\x77\x92\x56\x73\x2b\x3a\x15\xce\x94\xf1\xf1\x9e\xc7\xb3\xe5\x0a\
+\x8d\xe6\xf1\xd3\x94\xca\x1b\x9a\x55\xca\x0b\xa2\x1a\x75\x5f\x8f\
+\x4e\x5b\xe2\xf5\xba\x74\xd0\xbe\xf4\x9a\x7f\xc2\x4f\xff\x41\xb0\
+\x81\xc7\x24\x5e\xa5\x4a\x39\x08\x25\xa4\x79\x78\xa2\x4a\x56\x68\
+\x3d\xb4\xfe\x74\x92\xa8\xe4\x5d\xab\xd3\x3d\x8f\xed\x56\x2e\xb4\
+\x2f\xf0\xd2\xc5\xf3\xf9\xdc\xaa\x5b\xad\x96\x51\x74\xf4\xe7\x58\
+\xba\x07\x3b\xb2\x87\xf6\x65\x1c\x8f\x98\xfa\x3f\xe1\x49\x82\x24\
+\x10\x6c\xe0\x31\x85\x77\xb2\x4e\x7b\xdb\xbc\x50\x08\xcd\xc3\x9f\
+\xb4\xd0\x7a\x33\xb0\xc4\x34\x9d\x24\xf4\x99\x6a\xde\x8b\x36\xdb\
+\x3c\x15\x8f\xa7\x86\xf6\x05\x5e\x77\x79\x4e\x9d\x26\x38\xc2\xe5\
+\x5a\x4f\x67\x7f\xee\x4a\x4f\x5a\xad\xd3\xa0\x7d\x19\x67\xfe\xb9\
+\x09\x15\x00\xa4\xf7\x09\x4b\x21\xd8\xc0\x63\x0a\xef\x66\xa3\xf1\
+\x35\x36\x9b\x3f\xa1\xcf\xbc\xde\x6f\xca\xc4\xe2\x9e\xd0\xbe\xc0\
+\x4b\x95\xd7\xa4\x56\x9d\x37\x36\xe0\xff\x89\x89\xe6\x8f\xd6\x1b\
+\xe6\x74\xae\x84\xf6\x65\x94\xf9\x13\xef\xfb\x89\x5d\x00\xe0\x3f\
+\x16\xe1\x47\xff\x52\x08\x36\xf0\x98\xc2\x1b\x66\xb7\x2f\x60\xbb\
+\xf9\x13\x9a\x1b\x0a\x1d\xbd\x4a\xab\x7d\x46\x2e\x2f\x12\x40\xfb\
+\x02\x2f\xd1\x05\x4d\xd3\x7c\xb3\xc5\xf2\x1e\xd3\xfa\x73\x47\xde\
+\xdb\x5e\xef\x2e\x68\x5f\xc6\x98\x3f\x1f\x7f\xdb\x6f\x5e\xcc\xa9\
+\xff\xf1\x1f\x0b\xf0\xa3\x7f\x09\xe9\xdd\xc2\x10\x6c\xe0\xd1\xce\
+\x9b\xe0\xf7\xff\xc8\x05\xf3\x27\xf3\x5e\xf1\x78\x36\xf8\x34\xaa\
+\x0a\x68\x5f\xe0\xc5\x5b\xfc\x62\x71\xd5\x3b\x98\xb1\x32\xdd\xfc\
+\x11\x67\x4c\x30\xf0\x23\xb4\x2f\x23\x78\x02\x5c\xc7\x0b\x80\x78\
+\x95\x82\x90\x54\x00\x48\x20\xd8\xc0\x63\x02\x0f\x3d\xe3\xcd\x35\
+\xf3\x27\x78\x13\x82\x81\xfd\x83\xf4\xfa\xdb\x9a\x9a\xea\x64\xd0\
+\x5f\x80\xd7\xc9\x92\x7b\xbe\x5a\x7d\xcf\x94\x60\xf0\x10\x1b\xfa\
+\x33\xd2\xd4\x60\xf0\x30\xf4\x67\xda\x79\x22\xdc\xcf\x89\x02\x20\
+\x37\xde\x35\x02\x01\xa9\x00\x10\x43\xb0\x81\xc7\x14\x1e\x7a\x3e\
+\x98\x8b\xe6\x4f\xe6\x0d\x77\x38\x16\xa1\x67\xa0\xa1\xbf\x00\x8f\
+\x58\xdc\x42\x61\xd9\x9b\x4e\xe7\x3a\x36\xf6\x67\xb5\x50\x50\x04\
+\xed\x4b\x1b\x8f\xf0\x70\xa2\x00\xc8\x8b\x75\xea\x3f\x17\xaf\x10\
+\x88\x02\x40\x04\xc1\x06\x1e\x93\x78\xa7\xc8\xe5\xd7\xcc\xc3\x93\
+\x4d\x2c\xcd\xc5\x34\xbd\x43\x32\x42\x9a\x9b\xc0\xba\x4c\xe0\xcd\
+\x0c\x06\x0f\x5c\xa8\x52\xdd\x87\xed\x72\x3e\xf4\x97\xac\xe6\x89\
+\xae\xd6\x6a\x9f\x9d\x1d\x08\x1c\x66\x6b\x7f\x56\xf3\xf9\x16\x68\
+\x5f\x5a\x78\xc4\xd9\x7b\xa2\x00\xe0\xc7\x32\xff\x1c\xbc\x3a\xc8\
+\x27\x5d\x2f\x80\x60\x03\x8f\x51\xbc\x9b\xf4\xfa\x97\xb9\x6e\xfe\
+\x64\xbd\xed\x72\x6d\xf4\x0b\x04\x35\xd0\x5f\xb2\x8f\x57\x2e\x95\
+\xf6\xfe\xc8\xe3\xd9\xcd\xf6\xfe\x6c\x17\x89\xa2\xd0\xbe\xb4\xf0\
+\xa4\xa4\x02\x40\x10\xef\xa6\x3f\x72\x01\xc0\x4f\x78\x96\x20\x08\
+\x36\xf0\x92\xe4\x79\xc4\xe2\x40\xaa\x3c\xf4\xfc\xfc\xbc\x60\xb0\
+\xbd\x2b\xcd\xc5\x34\x1d\x25\xa4\x0e\x9a\x1b\x63\x1d\xa6\xf3\xe6\
+\x04\x83\x47\xef\x36\x1a\xdf\x97\x0b\x85\x7a\xe8\x7f\xdc\xe7\xe9\
+\xf3\xf3\x9d\x8f\x9a\xcd\xe3\xb9\xd2\x9f\x4b\xc4\xe2\x56\x68\x5f\
+\x5a\x78\x44\x01\x20\x8a\xe9\xe7\xf8\x4a\x39\xa4\x67\x04\xc1\xfc\
+\x81\x97\x11\x5e\x48\x26\x2b\x79\x25\x1a\xfd\x28\x55\xde\x04\x9f\
+\xef\x87\x6c\x32\x7f\xb2\xa6\xfa\x7c\xbf\x5f\xa0\x52\xdd\x83\x85\
+\x41\x08\xfd\x8f\x7b\x3c\x39\x8f\x27\x1d\xa2\xd5\x3e\x35\x33\x10\
+\x38\xc0\xa5\xfe\xdc\x28\x93\x9d\x06\xed\x4b\x0b\x4f\x9a\xd0\x3d\
+\x7c\xa4\x02\x20\x17\xcc\x1f\x78\x99\xe2\xe9\x04\x02\xe3\xda\xb6\
+\xb6\xaf\xef\xf6\x78\x9e\x48\x85\x27\xe6\xf1\x54\xd9\x6a\xfe\x64\
+\x7d\xec\x76\xef\x6b\x91\xcb\xcf\xc6\xef\xae\x86\xfe\xc7\x7e\x5e\
+\x6e\xff\xa2\xa2\xcb\x3e\xf7\x78\xbe\xe3\x62\x7f\x1e\x58\x54\x74\
+\x39\xf4\x17\x5a\x78\x89\x3d\xbd\x47\x2a\x00\xc0\xfc\x81\x97\x11\
+\x1e\x3a\xba\x99\xd5\xd8\xb8\xfe\xdb\x81\x03\xdb\x4f\x37\x99\x2e\
+\x4c\x85\x17\x16\x8b\x9b\xb2\xdd\xfc\xc9\xbc\x97\x5d\xae\x8d\xf5\
+\x4a\xe5\x39\xd0\xff\x58\xcb\x3b\xa1\xad\xb0\x70\xf0\x87\x2e\xd7\
+\x0e\x2e\xf7\xe7\xb3\x95\xca\xdb\xa1\xbf\x30\x98\x97\xaa\xf1\x43\
+\xb0\x81\x97\xe0\x92\xf7\x71\x65\xe5\x8c\x6f\x07\x0c\x68\xff\x06\
+\x53\xb9\x4e\xdb\x98\x0a\xef\x64\x85\xe2\x6a\x30\xff\x7f\xf3\x86\
+\xb9\x5c\x6b\x2a\x65\xd2\x01\xd0\xff\x58\xc3\xeb\xd1\x2c\x95\x9e\
+\x31\xd2\xe5\xda\x9c\x0d\xfd\x19\xbd\x0d\x10\xfa\x0b\x3b\x78\x10\
+\x1c\xe0\xa5\x9d\xf7\x5c\x28\xf4\x16\x61\xfe\x3b\x07\xf4\x3f\x8a\
+\xbf\x0a\x34\x69\xde\xf5\x5a\xed\xf0\xb9\x78\x12\x9a\xfb\xf7\xcd\
+\x71\xed\xd3\xb0\xe4\xd3\x51\x73\x48\xbf\x49\x46\x6c\xe7\x0d\xb7\
+\xd9\x96\xd4\x48\x24\x83\x90\xc1\x40\xff\x63\x24\x2f\xb7\xb5\xb0\
+\xf0\x9c\x37\x1d\x8e\x75\x5c\xec\x7f\x5d\xe9\x66\x9d\xee\x35\xe8\
+\x2f\x60\xfe\xc0\xcb\x42\xde\x0d\x0e\xc7\x3d\x84\xf9\xef\x1e\xd0\
+\xbf\x7d\x5e\x4b\xcb\xbe\x54\x79\xcf\x5b\xad\x73\xc0\xfc\xe3\xf3\
+\xde\x73\x3a\xb7\x0d\x2a\x2a\xba\x02\x0b\x99\x00\xfa\x33\x23\x78\
+\x92\xd3\x8b\x8b\x6f\x1c\xe5\x76\xef\xcb\x86\xfe\xd7\x51\x0f\x18\
+\x8d\x9f\x42\x7f\x01\xf3\x07\x5e\x96\xf1\x4e\xd5\xeb\x07\x93\xcd\
+\x7f\x17\xa6\xd7\x2a\x2a\xa6\xa6\xca\xfb\xdc\xeb\xfd\x9e\xca\xe4\
+\x36\xc9\xef\xff\x0d\x25\xed\xb1\x1e\xcf\x7f\xd8\x98\x7c\x51\xbc\
+\xd0\x64\x42\xe8\xe6\x49\xe8\xcf\xd4\xf3\xe4\x42\xa1\xe1\x72\x8d\
+\xe6\x89\x89\x5e\xef\x4f\xd9\x52\x7c\x76\xa6\x67\x2c\x96\x59\xd0\
+\x5f\xc0\xfc\x81\x97\x45\xbc\x5a\x85\xa2\x69\x5f\xdf\xbe\x07\xbe\
+\xc6\xcc\x1f\x19\xff\x4e\x5c\xb7\xfa\xfd\x2f\xa4\xc2\x93\xf0\x78\
+\xc5\xa9\x24\xa3\x19\x81\xc0\x01\x64\xe0\xef\xb8\x5c\x9b\x87\xd9\
+\x6c\x8b\x1e\x33\x9b\x27\xdc\x65\x30\x8c\xbc\x46\xa3\x79\x1e\x99\
+\xe3\xc9\x0a\xc5\x75\x6d\x6a\xf5\x65\xd5\x6a\xd5\xe9\x25\xca\xe2\
+\x36\x97\x46\x5d\x66\xd3\x6b\x6d\x4a\xa5\x82\x4f\xfc\x6d\x74\x34\
+\xcd\xe6\x33\x09\x33\xfc\xfe\x83\xe8\x28\xac\x42\x24\xea\x15\x0e\
+\x07\x72\xa0\x3f\x67\x94\x97\x8b\x2e\xc3\x3c\x66\xb1\x4c\x9c\xed\
+\xf7\x1f\xce\xa6\x33\x4f\x5d\xe9\x35\xbb\x7d\x35\xf4\x17\x30\x7f\
+\xe0\x65\x09\xcf\x29\x91\x78\xb7\xf4\xea\xf5\xd3\xd7\xfd\xb1\xa3\
+\xfe\xfe\xfd\xda\x77\x92\x74\xaa\xc5\x3c\x24\x95\xed\xf3\x09\x85\
+\x15\x1f\xb8\x5c\xbb\x86\x39\x9d\x6b\x1e\xb1\xd9\x66\xdf\x61\x31\
+\x8f\xb9\xce\x68\x7c\xf3\x42\xbd\xfe\xe9\x33\x8b\x8b\x6f\xed\x23\
+\x97\x5f\x5c\x2b\x91\x9c\x1c\x91\x48\x1a\xad\x79\x79\x21\x74\xf4\
+\xc5\x3b\xf6\xe4\x60\xf7\xf7\xb7\xa6\xa0\xe0\xc4\xb9\x81\x40\x7b\
+\x3c\xcd\xc1\x34\x0d\x25\xcc\x0e\x9a\x93\xc0\xba\x54\xf1\x46\xba\
+\xdd\xfb\xce\xd1\xeb\x1f\x71\x9a\x4d\x5e\xe8\xcf\xe9\xe3\xa9\xf8\
+\x7c\xdb\xa5\x2a\xd5\xa3\xa3\xdd\xee\x6f\xe8\x6c\x5f\x26\xf2\x46\
+\x39\x9d\x7b\xa0\xbf\x80\xf9\x03\x2f\x0b\x78\x2a\xb1\x58\xbd\xac\
+\xa5\x65\x4f\x67\xe6\x8f\x3e\x97\x14\x16\x56\xb2\x6d\x7f\x5d\x42\
+\x61\x39\xd7\x92\xf9\x94\x60\xf0\xc8\x53\x76\xfb\xfc\x13\xe5\xf2\
+\x2b\x25\xc7\x5e\xb2\x08\xfd\x39\x59\x9e\xdd\x64\x70\x9f\xa6\x50\
+\xdc\x88\x6e\xbe\x9c\x1d\x08\x1c\xe5\x82\x59\x67\x82\x37\xd1\xe7\
+\xfb\x05\xfa\x0b\xf3\xcc\x3f\xe1\xa7\xff\x20\xd8\xc0\x4b\x70\x11\
+\x4d\xa9\xab\x5b\xd1\x95\xf9\x7f\xd9\xbf\xff\xd1\x78\x47\xe5\x4c\
+\xdc\x5f\x74\x36\x81\xcb\xc9\x7c\xb6\xcf\x77\xf8\x29\xb3\x79\x7a\
+\x3f\xb9\xfc\x12\xe9\xb1\x29\x1b\xa0\x3f\x77\xc5\xf3\xe8\x75\xae\
+\x41\x5a\xed\x2d\x4f\xdb\x6c\x8b\x66\x25\x69\xfa\xd9\x68\xfe\x48\
+\x28\x4e\x55\x55\xe5\x45\x90\x4f\x19\xc3\x23\xa6\xfe\x4f\x78\x92\
+\x20\x09\x04\x1b\x78\x71\x96\x13\xde\x2e\x2d\x1d\xd7\x95\xf9\xa3\
+\xef\x97\xb5\xb4\xec\x65\xe9\xfe\xe6\x75\x75\x84\xc7\xb5\x64\x8e\
+\x8a\x81\xe1\x56\xeb\xe2\xf3\x54\xaa\x7b\xd0\x2b\x68\x79\x1d\x1e\
+\x29\xcc\xb6\xf1\x61\x34\xea\xf3\x22\xc5\x8a\x5e\x17\x18\x0c\x4f\
+\xbd\xe4\x74\xac\x9d\x12\x0c\x1e\xe5\xb2\x59\x67\x8a\xe7\x34\x1b\
+\x6d\x90\x4f\x19\x63\xfe\xb9\x09\x15\x00\xa4\xf7\x09\x4b\x21\xd8\
+\xc0\x8b\xb5\x3c\xe4\xf7\x0f\xfb\x0a\x33\x79\x64\xf8\x3b\x48\x42\
+\x9f\xd1\xf7\xa8\x00\x78\xaf\xbc\x7c\x12\x5b\xf7\xf7\x33\x8f\xe7\
+\xfb\x39\x78\x42\x23\x84\x15\x05\xed\x53\xd1\x1c\xfd\x1d\x34\xbb\
+\xc3\xef\x12\x15\x13\x79\xd8\x7e\x7f\x77\xa7\xc1\x30\xb2\xad\xb0\
+\xf0\x02\x93\x48\xe4\xca\x82\xfe\xdc\xc3\x9c\x9f\xef\xed\x5b\x54\
+\x74\xe9\x3d\x46\xc3\x27\x9f\xfa\xbc\x3f\x71\xb9\x7d\xa9\xe2\x79\
+\xb4\x9a\x28\xe4\x53\x46\x98\x3f\xf1\xbe\x9f\xd8\x05\x00\xfe\x63\
+\x11\x7e\xf4\x2f\x85\x60\x03\xaf\xab\xe5\x32\xab\xf5\xc6\x78\xe6\
+\x8f\x34\xd4\xe3\x79\x92\xad\xfb\x8b\x26\x6f\x81\x64\x1e\x6c\xff\
+\xd0\xeb\xfd\xee\x6e\x8b\x65\xfc\x29\x3a\xed\x5d\x01\xa5\xb2\xb9\
+\xb0\x50\x26\x64\x79\x7f\x16\x86\x04\x82\x3a\x34\x5d\xed\x23\x66\
+\xf3\xf8\x71\x5e\xef\x0f\xd9\x6e\xd6\x99\xe0\x05\xd4\xaa\x16\xc8\
+\xa7\xb4\x9b\x3f\x1f\x7f\xdb\x6f\x5e\xcc\xa9\xff\xf1\x1f\x0b\xf0\
+\xa3\x7f\x09\xe9\xdd\xc2\x10\x6c\xe0\xfd\xcf\xd2\x4f\xa3\x39\x65\
+\x5f\x9f\x3e\x47\x76\xf6\xeb\xdb\xbe\x83\x24\xf4\xf9\xab\x7e\xfd\
+\xda\xbf\x26\xe9\x74\xa3\xf1\x42\xb6\xee\xef\x93\x66\xf3\x54\x48\
+\xe6\xff\xe6\xcd\xf0\xf9\x0e\xbd\xe3\x74\x6e\xba\xc7\x68\xfc\xe8\
+\x1c\xa5\xf2\xce\xea\x82\x82\x01\xe8\xa5\x4f\x0c\x6c\xdf\x1e\xe8\
+\x4e\xfd\x3a\x89\xe4\x24\x74\x79\xe3\x7e\xa3\xf1\xd3\xf7\x1c\x8e\
+\x6d\xb3\x02\x81\x23\x60\xd6\x99\xe7\x55\x15\x2b\x4e\x81\x7c\x4a\
+\x2b\x4f\x80\xeb\x78\x01\x10\xaf\x52\x10\x92\x0a\x00\x09\x04\x1b\
+\x78\x1d\x97\xa8\x5c\x5e\xb5\xa3\x57\xaf\x3f\x13\x31\x7f\xa4\x88\
+\x54\x5a\xce\xd6\xfd\xbd\x4d\xaf\x7f\x1b\x92\x79\xe2\xbc\xa9\x3e\
+\xdf\x9f\x6f\x3b\x1c\x9b\x1f\x33\x99\x26\xa3\x29\x9c\xcf\x54\x28\
+\x6e\x41\xaf\x85\x0d\x8a\x44\xb5\x2e\xb5\x2a\xea\xb1\x98\x2c\x4d\
+\x4d\x75\x85\xe9\x68\xdf\xba\xba\xaa\x42\x74\x8d\x19\xcd\xe1\x10\
+\x51\x16\xf7\x6a\x56\x29\x2f\x38\x5b\xa5\xba\xe3\x26\xad\xf6\x95\
+\xa7\xcc\xe6\x69\xc8\xe8\xa7\xfb\xfd\x7f\x81\x59\x07\xda\x87\xa8\
+\xd5\x4f\xdd\xaa\xd7\xbf\x35\xc2\x6e\x5f\x36\xc9\xe7\xfb\x95\xaa\
+\xed\x6b\x96\xc9\xce\x81\x7c\x4a\x1b\x4f\x84\xfb\x39\x51\x00\xe4\
+\xc6\xbb\x46\x20\x20\x15\x00\x62\x08\x36\xf0\x3a\x2e\x26\xec\x68\
+\x6a\x6d\x6b\xeb\xf7\xc8\xf0\xb7\x93\x84\x3e\x7f\x89\x99\xfd\x57\
+\x1d\xf4\x45\xbf\x7e\x47\x95\xc7\xe6\xf3\x61\xe7\xfe\xa2\x67\xbc\
+\xc1\xfc\xd3\xcb\x9b\x1c\xf0\x1f\xfe\xd8\xef\xfb\xe1\x1d\x87\x63\
+\xcb\x4b\x36\xdb\x92\x17\xac\xd6\x79\x4f\x9a\xcd\xd3\x1f\x35\x9b\
+\x27\x3e\x60\x34\x8e\x41\x67\x15\xd0\xfd\x07\xe8\xbf\x0f\x1a\x8d\
+\x63\xd1\xf7\x4f\x9b\xcd\x33\xd0\xef\x46\xd8\x6c\x4b\x47\x62\xc6\
+\xfe\xa9\xdf\xf7\xe3\xe4\x60\xf0\x08\xb4\x47\x7c\x4d\xf5\xfb\xf7\
+\x63\x5d\x39\x97\x3c\x3e\x3c\x06\xbd\xb7\x4a\x59\x7c\x2a\xba\xa4\
+\x73\xa3\xd9\xf4\xde\x73\x0e\xc7\xf2\x09\x3e\xdf\x2f\xe9\xde\xbe\
+\x41\x85\x85\x57\x41\x3e\xa5\x85\x47\x78\x38\x51\x00\xe4\xc5\x3a\
+\xf5\x9f\x8b\x57\x08\x44\x01\x20\x82\x60\x03\xaf\xe3\x82\x1e\x15\
+\x9b\xdb\xd8\xb8\x2d\x51\xf3\x47\x5a\xda\xd4\xb4\x97\xcd\xf1\x43\
+\x33\x06\x4e\x3d\x96\xe0\x48\x42\x49\xcf\xef\x6f\x9f\x93\x82\xd0\
+\x7a\xc0\x03\x1e\x95\xbc\x97\x6d\xb6\xe5\x89\x8e\x0f\xf4\xe8\x6b\
+\x54\x24\xea\x75\xba\x5c\x7e\xe3\x2d\x3a\xdd\xeb\xc3\x2c\x96\x45\
+\x13\xdc\xee\x9f\x52\xdd\xbe\xc1\xc5\xc5\x77\x43\x3e\xa5\x9c\x47\
+\x9c\xbd\x27\x0a\x00\x7e\x2c\xf3\xcf\xc1\xab\x83\x7c\xd2\xf5\x02\
+\x08\x36\xf0\x3a\x2e\xfc\xd1\xd5\xd5\x0b\x93\x31\x7f\xa4\x91\x31\
+\x9e\x00\x60\x43\xfc\x9a\xd4\xaa\xf3\xc0\x6c\x80\xc7\x66\x1e\xba\
+\x24\xd2\xdd\xf1\xa1\xe0\xf1\x74\x65\x62\x71\xcf\x53\xe4\xf2\xeb\
+\x6f\xd2\xe9\x5e\xc5\x0a\x83\x85\x9f\xbb\xdd\xff\x17\x6f\xfb\xae\
+\x52\xab\x9f\x81\x7c\x4a\x39\x4f\x4a\x2a\x00\x04\xf1\x6e\xfa\x23\
+\x17\x00\xfc\x84\x67\x09\x82\x60\x67\x13\xaf\xc7\x4b\xe1\xf0\xa8\
+\x64\xcd\x1f\xe9\x6e\x8f\xe7\x29\x36\xc7\x2f\xa2\x2c\xee\x3d\x05\
+\x4b\x6c\xc7\x84\x26\x37\xc1\x13\x73\xb2\x42\xeb\xa1\xf5\x8f\xb3\
+\x80\x07\x3c\x8a\x78\xfd\x8b\x8a\x2e\xcb\xd4\xf8\x40\x13\x24\x95\
+\xab\x94\x27\x0e\xd2\x68\x6e\xbb\xd6\x68\x78\xf3\x39\x8b\x65\x01\
+\x56\x18\xfc\x40\xfc\x6d\x74\x0f\x0d\xe4\x53\xca\x79\x44\x01\x20\
+\x8a\xe9\xe7\xf8\x4a\x39\xa4\x67\x04\xc1\xfc\x81\xf7\xaf\xe5\x76\
+\xb7\xfb\xf1\x1d\x7d\x31\xd3\xef\xdb\xe7\xb8\xd0\xe7\x2f\x31\x7d\
+\x15\x47\xa7\xeb\x74\x17\xb2\x39\x7e\x5e\x9d\x36\x02\x66\x03\x3c\
+\x36\xf3\xbc\x12\x49\x39\xd5\xe3\x4d\xc5\xe3\xa9\xc3\x62\x71\x73\
+\x89\x58\xdc\x0a\xf9\x94\x72\x9e\x34\xa1\x7b\xf8\x48\x05\x40\x2e\
+\x98\x3f\xf0\x3a\x5b\xce\x31\x9b\xaf\xd8\x8e\x19\xf9\x36\xcc\xf4\
+\x09\xa1\xcf\x5f\xe0\x05\x40\x3c\x85\xa5\xd2\x0a\x06\xec\xaf\xa8\
+\xe0\xd8\x59\xcc\xe4\x79\x3e\x9f\x47\x0b\x66\x03\x3c\xb6\xf2\xa6\
+\x79\xbd\x07\x22\x91\xa0\x02\xf2\x5f\x56\xf1\x12\x7b\x7a\x8f\x54\
+\x00\x80\xf9\x03\xef\x5f\x4b\xa3\x4a\xd5\x6f\x73\x9f\x3e\x87\x52\
+\x35\xff\x7d\x7d\xfb\xfe\xcf\x13\x00\xdd\xdc\xbe\x1c\xf4\x6a\x60\
+\x5b\x41\x81\x33\x5a\x50\x50\xd9\x52\x5c\xdc\x77\x90\x4e\x77\xee\
+\x79\x36\xdb\x2d\x57\x7a\xdc\x8f\xdc\x11\xf0\x8f\x78\x34\x12\xf9\
+\x70\x44\x59\xd9\xe4\x31\x95\x95\x0b\x67\xd7\xd7\x6f\x5a\xdd\xd2\
+\xf2\xed\xae\x5e\xbd\xfe\x42\xdb\x32\xb3\xae\x6e\x43\xaa\xf1\x1b\
+\xef\xf5\xfe\x02\x66\x03\x3c\x36\xf2\x86\x39\x1c\xeb\x20\xff\x01\
+\xaf\xcb\x7b\x00\x78\x29\x2e\x10\x6c\x6e\xf3\xfc\x45\x45\xd1\xb5\
+\xbd\x7a\xfd\x9e\xaa\xf9\x23\x2d\x69\x6c\xdc\xfb\xaf\x6b\xea\x6e\
+\xa7\xde\x6b\x32\xf8\x4b\xb4\x9a\xda\x3a\x83\xbe\x7f\x6f\x95\xea\
+\xd4\xb3\x0d\x86\x4b\x87\x58\x2c\xb7\xde\xe1\x76\x3f\xf6\xb8\xdf\
+\xff\xea\xcb\x25\x25\x9f\x7c\x58\x51\x31\x73\x52\x4d\xcd\xaa\xc5\
+\x4d\x4d\x7b\x36\xf7\xec\xf9\x0b\x2a\x26\xc8\xec\x2f\x8e\x5d\x92\
+\x48\xfc\xcc\x04\x96\x05\x7f\x48\x35\x7e\xef\xd8\xed\x5b\xc1\x6c\
+\x80\xc7\x46\xde\xf5\x26\xd3\x48\xc8\x7f\xc0\x4b\xeb\x02\xc1\xe6\
+\x36\xcf\x54\x50\x60\x5a\xd0\xd4\xf4\x75\x77\xcc\x1f\x69\x53\x5b\
+\xdb\xcf\x9f\x57\x55\x2d\x9e\x55\x5f\xbf\x65\x71\x4b\xcb\xf7\x9b\
+\xfb\xf4\x39\xd8\x1d\x5e\xaa\xe6\x4f\x08\xdb\xb5\xfc\x54\xe2\xf7\
+\x8c\xc5\x32\x07\xcc\x06\x78\x6c\xe4\x0d\x50\xab\x6e\x82\xfc\x07\
+\x3c\x30\x7f\xe0\x25\xb4\x68\x85\xc2\xc2\xf1\x75\xb5\x1b\xbb\x6b\
+\xfe\xdd\x35\xeb\x4c\xf0\xb4\x02\x81\x29\x95\xf8\xdd\xad\xd7\x7f\
+\x08\x66\x03\x3c\x36\xf2\xc2\x2a\x55\x2b\xe4\x3f\xe0\x81\xf9\x03\
+\x2f\xee\xa2\xd1\xa8\xf3\xdf\x2c\x2f\x9f\xb5\xad\x0f\x66\xac\xb8\
+\xb6\x63\xda\x87\xe9\x8b\x14\xb4\x0f\x5f\x9f\x29\x3c\x74\xef\x40\
+\x2a\xf1\x1b\xa2\x56\x3f\x33\xcb\xe7\x6b\x8f\xa7\x99\x98\xa6\x60\
+\x09\x78\x72\xe0\x1f\xa1\xcf\x33\x13\x58\x17\x78\xc0\x4b\x37\x6f\
+\xbc\xcf\x7b\x28\x10\xf0\x2a\x21\xff\x01\x0f\xcc\x1f\x78\x71\x79\
+\x8f\x04\x83\x23\xb9\x6a\xfe\x48\xbd\x54\xaa\x93\x52\x89\xdf\xe9\
+\x0a\xc5\x2d\x54\x27\xf3\x09\x6e\xf7\x6f\x0f\x58\x2c\xd3\xc1\x0c\
+\x81\x97\x2a\x6f\xb8\xd3\xb9\x11\xf2\x1f\xf0\xc0\xfc\x81\x17\x97\
+\x37\xc4\xe5\x7c\x88\xcb\xe6\x8f\x34\xd8\x68\xbc\x32\x95\xf8\xb5\
+\x4a\xa5\xe7\x52\x91\xcc\x27\x7b\xbd\x7f\xde\x67\x30\x8c\xae\x2b\
+\x2c\x3c\xdd\xe7\x73\xab\xd0\x76\xbd\xe1\x74\xee\x06\x33\x04\x5e\
+\x2a\xbc\xdb\x74\xba\x77\x21\xff\x01\x0f\xcc\x1f\x78\x31\x79\x83\
+\xcc\xa6\x4b\xb9\x6e\xfe\x48\x37\xd9\xed\x0f\xa4\x12\xbf\xa8\x58\
+\xdc\x92\xa9\x64\x3e\xd5\xe3\x39\xf8\x88\xd1\x38\xb1\xad\xa0\x60\
+\xb0\x82\xc7\x2b\xe8\xb8\x7d\x67\xeb\xb4\x0f\x83\x19\x02\x2f\x15\
+\xde\xc9\x72\xf9\x75\x90\xff\x80\xd7\x09\xb3\x07\x04\x07\x78\xc7\
+\x78\x35\x3a\x6d\xdf\x8d\x6d\x6d\x07\xb8\x6e\xfe\x48\x8f\xfa\xfd\
+\xaf\xa7\x12\x3f\x73\x7e\xbe\x37\x9d\xc9\x7c\x86\xcf\x77\xf8\x29\
+\xb3\x79\x46\x1f\x99\xec\x12\x19\x8f\x57\x14\xab\x7d\x6d\x06\xbd\
+\x17\xfb\xfd\x11\x30\x43\xe0\x25\xcb\xf3\x0b\x04\xb5\x90\xff\x80\
+\x47\x36\x7e\x7c\xde\x9f\x84\x27\x09\x92\x40\xb0\xb9\xcb\xf3\xea\
+\x75\x65\x4b\x5b\x5b\x7f\xda\x8a\x99\xe3\x56\xdc\x60\xf7\xe2\x06\
+\x9b\xac\xf6\xe2\xeb\x6f\x25\x89\x69\xbc\x97\xcb\xca\xa6\xa4\x12\
+\x3f\xcc\xa4\x0b\xbb\x9b\xcc\x31\x13\x3f\xfa\x82\xd9\xbc\xe0\x44\
+\x85\xe2\x6a\xf1\xb1\x99\x52\x13\x6f\xdf\x27\xcd\xe6\x99\x60\x86\
+\xc0\x4b\x86\x87\xfd\xf7\x08\xd6\xad\xc4\x90\xff\x80\x47\x32\xff\
+\xdc\x84\x0a\x00\xd2\xfb\x84\xa5\x10\x6c\x6e\xf2\x3c\x56\xb3\x7d\
+\x46\x7d\xfd\xbe\xad\xbd\x7b\xb7\x23\x6d\xc3\xb4\x17\xd3\xbe\x14\
+\xb4\x17\x5f\x7f\x2b\x49\x4c\xe4\x8d\xae\xa9\x59\x93\x6a\xfc\x26\
+\x7b\xbd\xfb\x53\x49\xe6\xc3\x2d\x96\x15\xa7\x29\x14\x37\xa3\xd7\
+\xac\xa6\xda\xbe\xe8\xf2\x00\x98\x21\xf0\x92\xe1\xbd\x65\xb7\x6f\
+\x81\xfc\x07\x3c\x92\xf9\x13\xef\xfb\x89\x5d\x00\xe0\x3f\x16\xe1\
+\x47\xff\x52\x08\x36\x07\xcd\xdf\xe3\x52\x7f\x5c\x55\xb5\x2a\x9b\
+\xcc\x1f\x71\xe6\x36\x36\x7e\x9b\x6a\xfc\xde\x77\x38\x76\xcf\xfc\
+\xfb\x48\xbe\x7d\x32\x96\x6c\x27\x05\xfe\x11\xfa\x3c\x03\x4f\xcc\
+\x48\xaf\xdb\xed\x1b\xce\x2d\x2e\x1e\x6a\xe4\xf3\xed\x69\x6a\x5f\
+\xe1\x38\x8f\xe7\x97\x99\xa4\xbf\xd1\x95\x12\xd9\xbe\x64\x04\xbc\
+\xd4\x79\x6f\xd9\x6c\x9b\xad\x42\xa1\xff\x5a\x83\xfe\xad\xb1\x7e\
+\xdf\x7e\x2a\xb7\xef\x4e\x9d\xee\x7d\xc8\x7f\xc0\xc3\xfd\x9c\x8f\
+\xbf\xed\x37\x2f\xe6\xd4\xff\xf8\x8f\x05\xf8\xd1\xbf\x84\xf4\x6e\
+\x61\x08\x36\x47\x78\x75\x75\x55\x85\x2f\x45\x4b\x26\x67\x9b\xf9\
+\x23\x6d\x6c\x6b\x3b\x8c\xf6\x3f\x95\xf8\x3d\x6f\xb1\x2c\x9a\xe1\
+\xf5\xb6\x4f\xc6\x92\xeb\x24\xff\x3f\x42\x9f\xd1\xf7\xef\xda\xed\
+\xdb\x2f\x52\x2a\x1f\x32\xe6\xe5\xf9\x33\xd1\xbe\x37\x6a\x34\xaf\
+\xcf\xc4\xfe\x4e\x2c\xc5\xda\xbe\x99\x29\x08\x78\xdd\xe3\xb5\xca\
+\x64\x83\x89\xf6\xb5\x1b\xf5\x8e\xf3\x74\xba\x27\x3e\x72\xbb\x7f\
+\xa4\x62\xfb\x4e\x2b\x2a\xba\x09\xf2\x1f\xf0\x70\x3f\x17\x90\x0b\
+\x80\x78\x95\x82\x90\x54\x00\x48\x20\xd8\xdc\xe2\x0d\xf5\xfb\x5e\
+\xcd\x46\xf3\x27\x78\xba\xc2\x42\x6d\x2a\xf1\xbb\xd7\x68\x18\xd3\
+\x31\xf9\xbe\xed\x72\x7d\x79\xa9\x5a\xfd\x94\x47\x24\x2a\xcd\x74\
+\xfb\xa2\x1b\xba\xc0\x5c\xd9\xc3\x7b\x07\x2b\x08\xab\xaa\xca\x8b\
+\x3a\xb6\xaf\x44\x22\x96\x9c\x58\x58\x78\xf5\x48\x9b\x6d\x67\x26\
+\xb7\x2f\x20\x10\x34\x42\xfe\xcb\x7a\x9e\x08\xf7\x73\xa2\x00\xc8\
+\x8d\x77\x8d\x40\x40\x2a\x00\xc4\x10\x6c\x6e\xf1\xce\x73\xd8\xef\
+\xdc\x82\x99\xe0\x16\xdc\x10\xf7\xe0\x46\x99\xac\xf6\xe0\xeb\x6f\
+\x21\x89\x2d\x3c\x9f\x48\x14\x49\x25\x7e\x43\x0c\x86\x57\x51\xd2\
+\x1d\xe9\x76\xff\x67\x88\x5e\xf7\x6a\xa4\x58\xd1\x16\x0e\x07\x72\
+\xa8\x6c\x5f\x74\x96\x01\xcc\x95\x1d\xbc\xde\x2a\xe5\x95\x71\xda\
+\xf7\x84\x7a\x89\xe4\xd4\x17\xcd\xe6\xa5\xe9\xde\xbe\xe9\x5e\xef\
+\x51\x39\x8f\x27\x85\xfc\x97\xd5\x3c\xc2\xc3\x89\x02\x20\x2f\xd6\
+\xa9\xff\x5c\xbc\x42\x20\x0a\x00\x11\x04\x9b\x5b\xbc\xde\x26\xe3\
+\xb9\x1b\xdb\xda\x8e\x64\xb3\xf9\xa3\x7f\x6f\x28\x2e\xee\x9b\x4a\
+\xfc\x4a\x55\xaa\x01\x15\x2a\xe5\xc0\xfa\xfa\xea\x22\xba\xda\xf7\
+\xec\xe2\xe2\xbb\xc0\x5c\x99\xcf\x7b\xc3\xe9\xdc\x53\x59\x59\xaa\
+\x48\xb4\x7d\x7d\x02\x41\xdd\x83\x7a\xfd\xe7\xc8\xb8\xd3\xb1\x7d\
+\xef\xd8\x6c\x3b\x20\xff\x65\x35\x8f\x38\x7b\x4f\x14\x00\xfc\x58\
+\xe6\x9f\x83\x57\x07\xf9\xa4\xeb\x05\x10\x6c\x0e\xf1\x4a\x75\xda\
+\xd6\xd5\x3d\x7b\xee\xdf\xd2\xab\x57\xfb\x56\x4c\x7b\x30\xed\x4d\
+\x41\x7b\xf0\xf5\xb7\x90\xc4\x36\xde\xe9\x3a\xdd\xc5\x6c\x6d\x5f\
+\xf4\x24\x01\x96\xf8\x8f\x80\x59\x33\x9b\xd7\x47\xa5\xba\x36\x95\
+\xf6\xd5\xe5\xe7\xbb\x6e\xd4\x68\x5e\x9d\xe4\xf1\xec\xef\xce\xf6\
+\xdd\xad\xd7\x8f\x82\xfc\x97\xd5\x3c\x29\xa9\x00\x10\xc4\xbb\xe9\
+\x8f\x5c\x00\xf0\x13\x9e\x25\x08\x82\xcd\x0a\x9e\x4f\xaf\x0d\x2f\
+\x68\x6c\xfc\x1e\xcc\xff\x6f\x5d\x6d\xb5\x0e\x65\x73\xfb\x3e\x61\
+\x32\x4d\xa7\xcb\x0c\x5f\xb0\x58\x16\x27\x72\x33\x62\x36\x9b\xff\
+\x5b\x2e\xd7\x97\x65\x65\x25\xc5\xdd\xe9\x2f\x68\x9e\x88\x0b\x55\
+\xaa\x87\xd0\x0d\x83\xa9\x6c\xdf\x19\x0a\xc5\x6d\x90\xff\xb2\x9a\
+\x47\x14\x00\xa2\x98\x7e\x8e\xaf\x94\x43\x7a\x46\x10\xcc\x9f\x43\
+\x3c\x8f\xc5\x64\x99\x54\x57\xbb\x1d\xcc\xff\x1f\x3d\xe0\xf5\xbe\
+\xc4\xe6\xf6\x6d\x96\x4a\xcf\x99\x8e\x25\xf9\x49\x98\x19\x4c\xf4\
+\xff\x23\xf4\x79\x3a\x6e\x44\xc9\x2a\x1e\x6f\x22\x76\x44\x8a\x5e\
+\x88\x84\xae\x5b\x63\xca\x1d\x66\x36\x2f\xeb\x0e\x2f\xdd\xdb\xc7\
+\x24\xde\x00\xb5\xea\xa6\xb4\x15\xef\x3e\x8f\x76\xa0\x5a\x75\xcb\
+\xeb\x0e\xc7\xee\x64\xb6\x2f\x22\x16\xf7\x84\xfc\x97\xd5\x3c\x69\
+\x42\xf7\xf0\x91\x0a\x80\x5c\x30\x7f\x6e\xf1\x22\x91\xa0\xea\xdd\
+\xf2\xf2\xc5\x9b\x71\x43\xdc\x8d\x1b\x62\xb2\xda\x8d\xaf\xbf\x99\
+\x24\x36\xf3\x46\x84\xc3\x63\xd8\xdc\xbe\x05\x05\x12\xf1\x27\x5e\
+\xcf\x2f\x54\x99\x21\xba\x49\xcd\x9c\x9f\xef\x21\x6f\x03\x9a\xdf\
+\x60\xbc\xcb\xf5\x1b\x53\xcc\x7a\x9a\xd7\x7b\xb8\x97\x4c\x76\x11\
+\x3a\x43\x41\xa7\xf9\xbf\xed\x72\x7d\x83\xc6\x5d\xba\xfb\x0b\x7a\
+\x74\xb5\xbe\xa0\xe0\xb4\x17\x2d\x96\x25\x89\x6c\x93\x94\xc7\x93\
+\x43\x3e\xcd\x6a\x5e\x62\x4f\xef\x91\x0a\x00\x30\x7f\x0e\xf1\x9a\
+\x9a\xea\x0a\x9f\x88\x44\x3e\x05\xf3\xff\xb7\x46\x57\x54\x2c\x66\
+\x7b\xfb\x5e\x6d\x30\xbc\x9d\x69\x73\x9d\xe4\xf1\xfc\x75\xa6\x42\
+\x71\x07\xf6\x67\x3b\x7d\xd2\xa1\x4d\x26\xbb\x90\x29\x47\xea\x03\
+\x0b\x0b\xaf\xc4\x37\x8b\x7f\x8b\x56\xfb\x0e\x5d\x67\x12\x06\x69\
+\x34\xb7\x67\xba\xbf\xa0\x1b\x06\xef\xd7\xeb\x3f\xc7\x8a\x9e\xa3\
+\x9d\x6d\xd3\x7b\x76\xfb\x1e\xc8\xa7\xc0\x4b\x14\x90\x92\xf1\x43\
+\xb0\x99\xcd\xbb\xde\xed\x7e\x06\xcc\xbf\x73\x2d\xa8\xab\xdb\xc3\
+\xf6\xf6\x0d\x29\x8b\xdb\x26\xfa\x90\xf9\x60\x26\xe4\xf1\xb4\xcf\
+\x48\x41\x68\x3d\xb4\xfe\x44\x92\x08\xde\x70\xb3\x79\x45\x22\x13\
+\x1a\xdd\xa5\xd5\x8e\x4a\x84\x97\xee\xed\x23\xff\xee\x32\x95\xea\
+\xc9\x8e\xdb\x85\x26\xc1\x99\xe6\xf1\x1c\x4e\x85\x97\xea\xf6\xbd\
+\xeb\x72\xfd\x27\x10\xf0\x2a\xa9\xea\x2f\xe8\x86\xc1\xeb\xd5\xea\
+\x57\x27\xba\xdd\xfb\xc9\xdb\x75\xaf\x4e\x37\x06\xf2\x29\xf0\x32\
+\xba\x40\xb0\x99\xcb\x3b\xdd\x6a\xb9\x6e\x73\x5b\x5b\xfb\x16\x4c\
+\xbb\x31\xed\x49\x41\xbb\xf1\xf5\x37\x93\xc4\x15\xde\xd6\x96\x96\
+\xbf\xb8\xd0\x5f\x5e\x76\x38\xb6\xa5\xdb\x5c\x27\xbb\x5c\x07\xce\
+\x91\xcb\xef\x46\xd7\xf9\x13\xd9\x2e\xf4\x92\xa4\x0f\xec\xf6\x7d\
+\x74\x99\x3f\x2a\x40\xb0\xcd\xe8\xf4\x20\xa6\x5c\x24\xea\xf3\xb9\
+\xcb\xf5\x33\x55\xc5\xc9\x29\x6a\xd5\x50\x3a\xfa\x8b\x84\xc7\x53\
+\x9e\xaf\x50\x3c\x30\xda\xe1\xf8\x01\x6d\xdb\xd9\x72\xf9\x5d\x90\
+\x4f\x81\x07\xe6\x9f\x85\xbc\x26\x9d\xee\xd4\xf5\x3d\x7b\x1e\x42\
+\x86\xb8\x0b\x37\xc4\x64\xb5\x0b\x37\xd4\x4d\x24\x71\x8d\x17\xef\
+\x15\xbc\x14\xb4\x6f\x5e\x73\x71\x71\xbf\x8a\xc2\xc2\xfa\x54\x79\
+\x67\x16\x17\xdf\x99\x4e\x73\x1d\x61\xb1\xac\xb6\xe6\xe5\x05\x93\
+\xdd\x5f\xbf\x48\xd4\x30\xc1\xe7\x3d\x4c\xb5\xf9\x3f\x6b\x36\x2f\
+\x40\xa7\xfc\x63\x6d\x1b\xba\x77\xe1\x6d\xab\x75\x7b\xa6\xcd\xff\
+\x43\xb7\xfb\xbf\x68\x86\x3f\x9a\xf3\x81\x68\x40\x61\xe1\x55\x3e\
+\xb1\xb8\x1a\xf2\x29\xf0\xc0\xfc\xb3\x8c\x57\xa2\xd5\xd4\x2e\x6b\
+\x6e\xfe\x0d\xcc\x3f\xbe\x1c\x12\x89\x8f\x86\xf6\xcd\xa9\x93\xcb\
+\x7b\x3e\xea\xf3\xbd\xbe\xaa\xb1\xf1\x47\xb4\x1d\x1f\x94\x95\xcd\
+\x49\x95\x27\xe7\xf1\xf4\x98\x19\x1d\xe9\xae\x79\x7d\xee\x71\x1f\
+\x1c\xac\x54\xde\x9f\xe8\x51\x7f\x67\xdb\x77\xae\x46\xf3\x04\x95\
+\xe6\xff\x96\xcd\xb6\x8d\x7c\xa3\x5b\xac\xed\x73\x9b\x8d\xa6\x07\
+\xcd\xe6\xd9\x99\xdc\x3e\xac\x18\xbb\x03\xf2\x15\xf0\xc0\xfc\x81\
+\x47\x0b\xcf\x67\x36\xfa\x66\xd6\xd7\x7f\x03\xe6\x9f\x98\xaa\x15\
+\x8a\x56\x2a\xda\xb7\xa9\xa9\x4e\x56\x21\x97\x37\x3e\xe0\x76\xbf\
+\xb4\xbc\xa1\xe1\x3f\x1d\xb7\x63\x67\x5b\xdb\x51\x13\x9f\x6f\x4d\
+\xb5\xbf\x3c\x6a\x34\x4e\x9d\x8e\x1b\x53\x3c\x4d\xc3\x84\x0c\x6b\
+\x02\x49\xc3\x6c\xb6\x0d\xae\x02\x49\xb4\xbb\xfb\x8b\x66\xbd\x7b\
+\xd2\x6a\x5d\x86\xf8\xd3\x12\xdc\x9e\x44\xb6\xaf\x33\xde\xc7\x4e\
+\xe7\x77\x2a\x3e\xdf\x96\xcc\xf6\x55\x57\x57\xc8\x2f\xd3\xeb\x5e\
+\xc9\xc4\xf6\x7d\xe2\x70\xfc\xf0\xf7\x99\x78\xc8\x57\xc0\x03\xf3\
+\x07\x1e\xc5\xbc\x90\xcb\x61\x18\x53\x53\xb3\x11\xcc\x3f\x71\x9d\
+\xa4\xd5\x9e\x97\xc9\xf6\x2d\xd7\xeb\x9a\x6f\xf5\x78\x86\x2d\xac\
+\xab\xfb\x32\xde\xb6\x5c\x6f\xb7\xdf\x9f\x6a\x7f\xa9\x97\x4a\xcf\
+\x4c\xc5\xbc\x3e\xf3\x78\x0e\x0d\xd6\x69\x9f\x54\x2a\x15\xfc\x74\
+\xf5\x67\xa7\x4e\x13\x1c\xeb\x72\xfd\x92\x49\xf3\x9f\xe0\x72\xfd\
+\xe1\x14\x0a\x2b\x52\x1d\x6f\x7d\x0b\x0b\x2f\x43\xf7\x39\xa4\x73\
+\xfb\xf0\x7b\x26\x20\x5f\x01\x0f\xcc\x1f\x78\xd4\xf2\xd0\x91\xd7\
+\xab\xa5\xd1\x59\x60\xfe\xc9\xf1\x2e\x76\x38\xee\x4e\x77\x7b\xa0\
+\x4b\x30\xe8\xe9\x8b\x29\x75\xb5\x7b\x92\xd9\xbe\xb9\xb5\xb5\xbb\
+\x79\xa4\x1b\xd9\x92\xec\x2f\xfc\x31\x4e\xe7\x4f\xc9\x98\xd7\x70\
+\xbb\x6d\x8b\x4f\xad\x6a\xc8\x44\x7f\x6e\x29\x28\x18\x9c\x29\xf3\
+\xc7\x3e\x1f\xa9\x91\x48\x06\x75\x77\xbc\x05\x05\x82\xfa\x4f\x9d\
+\xce\xef\xd3\xb1\x7d\x28\xf6\x45\xc7\xee\x85\x84\x7c\x05\x3c\xf6\
+\x98\x7f\xc2\x4f\xff\x41\xb0\x99\xcd\xbb\x2f\xe0\x7f\x77\x53\xcf\
+\x9e\xed\x3b\x31\xed\x4a\x41\x68\x3d\xb4\xfe\x46\x92\xb2\x81\x77\
+\x8b\xc7\x33\x3c\x1d\xed\x61\x2b\x2c\xf4\x5e\xe5\x74\x3e\xfa\x79\
+\x4d\xf5\xd6\xee\x6c\x5f\xa5\x4c\xd6\x94\x6a\x7f\xb9\x56\xad\x7e\
+\x39\x11\xf3\x1a\xe7\xf3\x1e\xbe\x40\xab\x7d\x36\x12\x09\x2a\x32\
+\xd9\x9f\x6f\xd7\xe9\xde\x4f\xb7\xf9\x23\x9d\x5c\x58\x78\x5d\xba\
+\xc6\x9b\x46\x20\x30\xbf\x62\xb1\xac\xeb\xee\xf6\xa1\xbb\xef\x21\
+\x5f\x01\x8f\x45\x3c\x62\xea\xff\x84\x27\x09\x92\x40\xb0\x99\xc9\
+\xbb\xdc\xe9\x7c\x10\xcc\x3f\x35\xde\x93\xa1\xd0\xe8\x54\xdb\x03\
+\x5d\xb3\x1f\x62\x36\xdf\x31\xa1\xb2\x72\x6d\xba\xb6\xef\x49\xbf\
+\xff\x9d\x54\xfb\x8b\x57\x28\xac\x8c\x67\x5e\x23\x1c\x8e\xad\x01\
+\xb5\xaa\x85\x8a\xfe\x8c\x5e\x43\xfb\xae\xcd\xb6\x3b\x9d\xe6\x7f\
+\x85\x52\xf9\x5c\x06\xc6\x9b\xf8\x3e\x9d\x6e\x6c\xaa\xdb\x37\xce\
+\xe9\xfc\x95\xfc\x34\x09\xe4\x2b\xe0\xb1\xc0\xfc\x73\x13\x2a\x00\
+\x48\xef\x13\x96\x42\xb0\x99\xc7\x1b\x68\x34\x5c\xba\xa1\x67\xcf\
+\xa3\x60\xfe\xa9\xf1\xde\x2a\x2d\x5d\x98\xe4\x5b\xf8\xf4\x17\x19\
+\x8d\x37\x8e\xa9\xa8\x58\x96\x89\xed\x5b\xdf\xd4\xf4\xbb\xd3\x69\
+\xd7\xa6\xda\x5f\xde\xb0\x5a\x37\x4f\x77\xbb\xdb\x09\x4d\xc3\x34\
+\xd1\xeb\x69\x1f\xef\xf5\x1e\xb9\x48\xab\x7d\x31\x14\xf2\xab\xa9\
+\xec\xcf\x1e\x81\xa0\x7a\xaa\xdb\x7d\x98\xbc\x4d\x9d\x6d\xdf\x04\
+\xaf\xf7\xb8\xd0\xe7\x69\x9d\xfc\xf6\x9e\xbf\x27\xb6\x39\x21\x43\
+\xe3\xad\x07\x76\x14\xff\x60\x2a\xdb\x77\x51\x71\xf1\xa3\x90\xaf\
+\x80\xc7\x22\xf3\x27\xde\xf7\x13\xbb\x00\xc0\x7f\x2c\xc2\x8f\xfe\
+\xa5\x10\x6c\x66\xf1\xaa\xf4\xba\x7e\x6b\x9b\x9b\x0f\x80\xf9\xa7\
+\xce\x9b\x54\x53\xbd\x23\x5e\x7b\xa8\xc5\x62\xd5\xb9\x06\xc3\x55\
+\xa3\xca\xca\xe6\x63\x3f\x3e\x9a\xe9\xed\x3b\xc5\x62\xbe\x2a\xd5\
+\xfe\x72\x46\x51\xd1\x6d\x1d\xcd\xeb\x65\xbb\x6d\x07\x9a\x31\x90\
+\xae\xfe\x7c\xae\x5c\x7e\x6f\x77\xcd\xff\x79\xa3\x71\x09\x86\x12\
+\x66\x7a\xbc\x35\x88\xc5\x67\x4c\x70\x3a\xff\x48\x74\xfb\xc6\x39\
+\x1c\xbf\x15\xf0\x78\x0a\xc8\x57\xc0\x63\x89\xf9\xf3\xf1\xb7\xfd\
+\xe6\xc5\x9c\xfa\x1f\xff\xb1\x00\x3f\xfa\x97\x90\xde\x2d\x0c\xc1\
+\x66\x00\xcf\x67\xd0\x97\x2f\x6a\x6c\xfc\x09\xcc\xbf\x7b\x3c\xac\
+\x80\xfa\xb5\xb3\x78\xa3\x53\xba\xa7\x69\xb5\x97\x8c\x8c\x46\x67\
+\x6c\x6b\x69\x39\x4c\xe5\xf6\xbd\x55\x56\xb6\x28\xd5\xfe\x52\xcc\
+\xe3\x69\xa7\x60\x47\xdc\xd8\x51\x37\x76\xd4\xef\x39\x72\x89\x4e\
+\x3b\x3c\x10\xf0\x6a\x68\xee\xcf\x39\xcf\x98\x4c\x0b\xa6\xe1\xa6\
+\x8a\x84\xb6\x6f\xc2\xdf\x67\x26\x8e\x0b\x7d\x9e\x4a\xfa\x0d\xa1\
+\xb7\x2d\x96\x1d\x92\x63\xbb\x46\xcd\x78\xb3\x8b\x44\xd1\xf7\x6d\
+\xb6\x2f\x13\xd9\xbe\x4b\x8a\x8b\x9f\x84\x7c\x05\x3c\x96\xf0\x04\
+\xb8\x8e\x17\x00\xf1\x2a\x05\x21\xa9\x00\x90\x40\xb0\x19\x62\xfe\
+\x76\x9b\x63\x4a\x6d\xcd\xde\x1d\xb8\x71\x24\xab\x1d\xb8\xd1\x6c\
+\x20\x69\x23\xfe\x7d\x36\xf2\x78\xc7\x5e\xbb\x7e\xec\x30\xae\xe0\
+\x24\x8d\x66\xf0\x6b\x91\xc8\xc4\xad\xad\xad\x07\xe9\xda\xbe\x75\
+\x3d\x7b\x1e\xf5\x6a\x35\xa1\x54\xfb\xcb\x83\x06\xc3\x94\x57\x6d\
+\xb6\x5d\x11\x65\x71\x1f\xa6\xf4\x67\x74\xb3\xdd\x58\xa7\xf3\xe7\
+\x64\xcd\xff\x13\xbb\xfd\x07\x7d\x7e\xbe\x93\xea\xf1\xe6\xd6\x69\
+\x1c\x4f\x58\x2c\xcb\x63\x6d\xdf\x78\x97\xeb\x4f\xac\xe3\xa8\x20\
+\x5f\x01\x8f\x05\x3c\x11\xee\xe7\x44\x01\x90\x1b\xef\x1a\x81\x80\
+\x54\x00\x88\x21\xd8\x0c\x31\x7f\x9f\x47\xfb\x41\x45\xc5\xca\x1d\
+\xad\xad\xed\x3b\x53\x10\x5a\x6f\x23\xa6\x0d\x24\x6d\xc4\xbf\xcf\
+\x56\xde\x45\x46\xe3\x0d\x2f\x05\x83\x63\x36\x36\x37\xef\x67\xca\
+\xf6\x5d\x67\xb3\x3d\x94\x6a\x7f\xf1\xaa\x94\x65\xa8\x9f\x30\xad\
+\x3f\xd7\x8b\xc5\x67\x26\x63\xfe\x98\xc1\xee\xf7\x0b\x04\x35\x74\
+\x8d\x37\xf4\x3a\xdf\x1b\xf4\xfa\x0f\xbb\xda\xbe\x0c\xdd\x90\x08\
+\x3c\xe0\xa5\x9b\x47\x78\x38\x51\x00\xe4\xc5\x3a\xf5\x9f\x8b\x57\
+\x08\x44\x01\x20\x82\x60\x33\x83\x57\x5f\x5f\x5d\xf4\x42\x24\x3c\
+\x01\xcc\x9f\xfb\xbc\x79\xb5\xb5\x7b\x79\x5d\xbc\xdc\x86\xcd\xfd\
+\xf9\x06\xbd\xee\x83\x44\xcc\x7f\x8a\xdb\x7d\xb4\x4e\x22\x39\x8d\
+\x09\xfb\x7b\x8a\x5c\x7e\x0b\xb6\x8d\x47\x3a\x16\x27\xe8\x72\x0b\
+\xe4\x2b\xe0\x31\x9c\x47\x9c\xbd\x27\x0a\x00\x7e\x2c\xf3\xcf\xc1\
+\xab\x83\x7c\xd2\xf5\x02\x08\x36\x43\x78\xb7\x79\xdc\x23\xc0\x5c\
+\xb3\x87\x57\x2d\x93\xb5\x30\xa9\xff\x79\x6d\x16\x5b\x77\x79\x6e\
+\xb7\x53\xff\xaa\xdd\xbe\x3b\x96\xf9\x23\x9d\x52\x54\x74\x33\x93\
+\xc6\x6f\x99\x48\xd4\x77\xac\xd3\xf9\x0b\xb1\x7d\x57\xa9\x54\x2f\
+\x41\xbe\x02\x1e\x0b\x78\x52\x52\x01\x20\x88\x77\xd3\x1f\xb9\x00\
+\xe0\x27\x3c\x4b\x10\x04\x3b\xe3\xbc\xf3\xac\xd6\xdb\xc1\x5c\xb3\
+\x8b\xf7\xb4\xcf\xf7\x1e\x13\xfa\x5f\x53\x53\x5d\xe1\x59\x56\xcb\
+\x4d\x6b\x9b\x9a\x7e\x1b\xa4\x52\x9d\xd3\x5d\x1e\x9a\x87\xe0\x33\
+\xb7\xeb\x50\x57\xe6\x7f\xb5\x4a\x35\x8c\x89\xe3\xf7\xd8\x1b\x05\
+\x2d\x96\x1d\x93\x9c\xce\x03\x72\x1e\xcf\x00\xf9\x0a\x78\x2c\xe0\
+\x11\x05\x80\x28\xa6\x9f\xe3\x2b\xe5\x90\x9e\x11\x04\xf3\x67\x08\
+\xaf\x97\xd1\x70\xf6\x96\xe6\xe6\x23\x3b\x70\x93\x48\x46\xdb\x71\
+\x83\x59\x4f\xd2\x06\xfc\x7b\xe0\x31\x9b\xb7\xbe\xb1\xf1\x0f\x74\
+\x63\x22\x9d\xfd\xcf\x6d\xd4\x07\x5e\x2b\x29\x99\x4b\x6c\xdf\x9a\
+\xc6\xc6\x5f\xba\x7a\x69\x51\x32\xdb\x77\xb6\x42\x31\x74\x9a\xcb\
+\xd5\xde\x51\xf7\x6b\xb5\xe3\x78\x31\x9e\xf5\x4f\x72\x7f\x25\xe9\
+\x1e\xbf\xe8\xe9\x90\xbe\x32\xd9\xa5\x90\xaf\x80\xc7\x12\x9e\x34\
+\xa1\x7b\xf8\x48\x05\x40\x2e\x98\x3f\x73\x78\xa5\x5a\x6d\xf3\xfa\
+\xe6\xe6\x3f\xc1\x5c\xb3\x93\x87\x1e\x45\xa4\xab\xff\x9d\x61\xb5\
+\xdc\xb0\x04\x3b\xea\xef\xb8\x7d\x1f\x97\x96\x2e\xc2\x7e\x9e\xd3\
+\xcd\xf1\x71\xc2\x53\x46\xe3\xdc\xa9\x98\xe9\x13\x7a\xd1\x64\x5a\
+\x8e\x7d\x2f\x4a\xc7\xfe\x9e\xa6\xd3\x5d\xb4\xb8\xb6\xf6\x9b\x68\
+\x41\x41\x15\xe4\x17\xe0\x65\x31\x2f\xb1\xa7\xf7\x48\x05\x00\x98\
+\x3f\x43\x78\x3e\x9d\x26\xb8\xa4\xae\xee\xbb\x1d\x2d\x2d\xed\xc9\
+\x6a\x3b\xa6\x0d\x98\xd6\x93\xb4\x01\xff\x1e\x78\xec\xe1\x7d\x54\
+\x52\xb2\x80\xea\xfe\xe7\x35\x19\xfc\x2f\x47\x4b\x66\xc5\xda\xbe\
+\x6b\xad\xd6\xfb\xbb\x3b\x3e\xd0\x69\xf4\xd1\x76\xfb\xff\x21\xf3\
+\x7f\xdb\x6a\xdd\xad\xe2\xf1\xd4\xe9\x18\x6f\xb5\x72\x79\xdb\x96\
+\xe6\xe6\x83\x68\x3b\x37\x36\x36\xfe\x75\xb2\x46\x73\x3e\xe4\x17\
+\xe0\x01\x2f\x36\x20\x25\xe3\x87\x60\x67\x86\xe7\x36\x1b\x4d\x53\
+\x2b\x2b\xb7\x80\x19\x02\xcf\xc0\xe7\x3b\xa8\xea\x7f\xa7\x98\xcd\
+\xd7\x2c\x6e\x68\xf8\x35\xde\xf6\x6d\x69\x6a\x3a\x5c\x2a\x91\xd4\
+\x76\x77\x7c\xd4\x4b\x24\xa7\x7e\x6a\xb7\xff\x88\xae\xaf\xa7\x63\
+\xbc\xb9\xc4\xe2\xd0\x9a\x86\x86\x5f\x3a\x6e\xef\xed\x0e\xc7\x53\
+\xc4\xa5\x05\xc8\x2f\xc0\x03\x5e\x9a\x16\x08\x76\xfa\x79\xe8\x2d\
+\x6d\x1f\x44\xa3\xf3\xc1\x0c\x81\x87\x74\x83\xcd\xf6\x70\xa6\xfb\
+\x9f\xd3\x6c\xf2\x8e\x28\x89\xcc\x48\x66\xfb\xe6\xd6\xd4\xec\xed\
+\xf8\x0a\xdc\x54\xb6\x0f\x4d\x12\x94\x8e\xf1\x86\xde\xd3\xb0\xa0\
+\xb6\xf6\xcb\xae\xb6\xf7\xf5\x70\x78\xb2\x5a\x28\x28\x82\x7c\x05\
+\x3c\xe0\x81\xf9\x33\x92\xd7\xd4\x54\x27\x7b\xca\xef\xff\x70\x3b\
+\x9e\x84\x93\xd1\x36\xdc\x60\xd6\x91\xb4\x1e\xff\x1e\x78\xec\xe5\
+\xcd\xad\xae\xfe\x02\x1d\xbd\x66\xaa\xff\x9d\x64\x32\x5e\x35\xbf\
+\xbe\xfe\xe7\x54\xb6\xef\x19\xac\xaf\x32\x61\xbc\xa1\x9b\x25\xc7\
+\x95\x97\xaf\x8d\xd7\x1e\x9f\x57\x55\x6d\xf3\xe8\xb4\x25\x90\xaf\
+\x80\x07\x3c\x30\x7f\xc6\xf1\x6e\xb0\xdb\x1e\x05\x33\x04\x5e\x47\
+\x55\x15\x2b\x7a\xa5\xbb\xff\xb9\xcc\x26\xe7\xb0\x48\x78\x6a\x77\
+\xb7\x6f\x90\x46\x73\x1e\xcd\xe3\x2d\xf7\x8d\x48\x64\x6a\xa2\xed\
+\x31\xaf\xa1\xe1\xa7\x1a\x83\x7e\x10\xe4\x2b\xe0\x01\x0f\x82\xc3\
+\x18\xde\x69\x3a\xdd\x65\x60\x86\xc0\xeb\x8c\xf7\x68\x30\xf8\x49\
+\x3a\xfb\xdf\x89\x26\xe3\xe5\xc8\x08\xd3\xb1\x7d\xab\x1a\x1b\x7f\
+\xc5\xdf\x5d\x40\xcb\x78\x7b\xc4\xed\x7e\x3d\xd9\xf6\x58\xd3\xdc\
+\x7c\xe8\x7c\x83\xe1\x7a\xc8\x57\xc0\x03\xf3\x87\xe0\xd0\xce\xab\
+\x29\x56\xf4\xd9\xd2\xd4\x74\x68\x7b\x73\x73\x7b\x32\xda\x86\x69\
+\x3d\xa6\x75\x24\xad\xc7\xbf\xdf\x9e\x82\x80\xc7\x4c\xde\xb2\xc6\
+\xc6\xfd\x41\xa7\xcd\xd0\xdd\xfe\xa7\x2b\x2c\xd4\x3e\x1f\x0a\x4d\
+\x4c\xf7\xf6\x8d\x2c\x2d\x5d\x51\x59\x59\xaa\xa0\x7a\xbc\x5d\x69\
+\x36\x0f\xed\x4e\x7b\xa0\xe2\x01\xc3\xe4\x41\xbe\x02\x1e\x98\x3f\
+\x04\x87\x16\x9e\x5b\x5a\x10\x59\xdd\xd0\xf0\x2b\x98\x21\xf0\x62\
+\xf1\x4e\x35\x9b\xae\xeb\x4e\xff\x1b\xa8\x56\x9f\x33\xbf\xae\xee\
+\xc7\x4c\x6d\xdf\xd5\x4e\xc7\x93\x54\x8e\xb7\x13\x55\xaa\x73\xd3\
+\xd1\x1e\x1f\x44\x22\xf3\x25\x3c\x9e\x12\xf2\x15\xf0\xb2\xcd\xfc\
+\x13\x7e\xfa\x0f\x82\x9d\x19\x9e\xaa\xa0\xc0\x38\xaf\xa6\xe6\xab\
+\x6d\x78\x62\x4a\x54\x5b\xf1\x84\xb6\x96\xa4\x75\xf8\xf7\xdb\x52\
+\x10\xf0\x98\xcf\x7b\xb7\xb4\x74\x69\x2a\xfd\x4f\x2d\x16\xab\x5e\
+\x0c\x04\xc6\x66\x7a\xfb\x56\x37\x36\x1e\xa9\x90\xcb\x9b\xa8\x18\
+\x6f\x95\x32\x59\xd3\x86\xc6\xc6\x03\xe9\x6a\x8f\x39\x55\x55\x7b\
+\x9d\x22\x51\x18\xf2\x15\xf0\xb2\x84\x47\x4c\xfd\x9f\xf0\x24\x41\
+\x12\x08\x76\x7a\x79\x3a\x91\x50\xf6\x79\x59\xd9\x5a\x30\x43\xe0\
+\x25\xca\xb3\xe6\xe7\xbb\x92\xe9\x7b\xfd\x8a\x8b\xcf\x58\x5c\x5b\
+\xfb\x5f\xaa\xb6\x6f\x4e\x75\xf5\x17\x32\x1e\xaf\x30\x93\xe3\xcd\
+\x29\x91\x78\x97\xd7\xd5\xfd\x94\xee\xf6\x58\x53\x5f\xff\x7b\x6f\
+\xa5\xf2\x14\xc8\x57\xc0\xcb\x02\xf3\xcf\x4d\xa8\x00\x20\xbd\x4f\
+\x58\x0a\xc1\x4e\x1f\x4f\xa3\x51\xe7\xbf\x11\x0e\x4f\x05\x33\x04\
+\x5e\x32\xbc\x9b\x6c\xb6\x47\x13\xe9\x77\x12\x1e\xaf\xf8\x05\xbf\
+\xff\x13\x3a\xf6\xf7\x39\x9f\xef\xe3\x4c\x8d\x37\x95\x58\xac\x9e\
+\x55\x5d\xbd\x27\x53\xed\xb1\xa5\xb9\xf9\xe8\xd5\x16\xcb\xbd\x3c\
+\xfc\x55\xcc\x90\xaf\x80\xc7\x41\xf3\x27\xde\xf7\x13\xbb\x00\xc0\
+\x7f\x2c\xc2\x8f\xfe\xa5\x10\xec\xf4\xf1\x1e\x74\xbb\x5f\x03\x33\
+\x04\x5e\xb2\xbc\x79\x35\x35\x5f\xf2\x62\xbc\x2c\x07\x2d\xe8\x28\
+\x16\x3b\xea\xff\x8e\xce\xfd\x45\x73\xf1\x67\x60\xbc\x89\xc7\x94\
+\x96\xae\xa0\xa2\x3d\x50\xf1\x24\x91\x88\x25\x90\xaf\x80\xc7\x31\
+\xf3\xe7\xe3\x6f\xfb\xcd\x8b\x39\xf5\x3f\xfe\x63\x01\x7e\xf4\x2f\
+\x21\xbd\x5b\x18\x82\xdd\x4d\xde\x15\x46\xe3\x9d\xdb\x9a\x9a\xda\
+\x93\xd1\x56\x4c\xeb\x30\xad\x25\x69\x1d\xfe\xfd\xb6\x14\x04\x3c\
+\xf6\xf2\x6a\xe4\xf2\x5e\x9d\xf5\x39\x29\x8f\x27\x7f\xd6\xe7\xfb\
+\x88\x09\xfb\xbb\xba\xae\xee\xb7\x8e\x53\x18\x77\x73\xbc\xe5\x8c\
+\x08\x04\xc6\x53\xd9\x1e\x1f\x95\x95\xad\x43\xef\x45\x00\xb3\x01\
+\x1e\x47\x78\x02\x5c\xc7\x0b\x80\x78\x95\x82\x90\x54\x00\x48\x20\
+\xd8\xdd\xe7\xf5\x53\xa9\xce\xde\xdc\xd4\x74\x74\x2b\x9e\x64\x12\
+\xd1\x16\x3c\xa1\xad\x21\x69\x2d\xfe\xfd\xd6\x14\x04\x3c\x76\xf3\
+\x9e\xc6\x4c\xbe\x63\x9f\x6b\x55\x28\x06\x2d\xac\xa9\xf9\x96\x49\
+\xfb\xfb\x49\x34\x8a\xde\xee\x97\x97\x8e\xf1\x76\xaf\xd3\x39\x9c\
+\x8e\xf6\x98\x59\x5b\xf3\x5d\x99\x56\xd3\x0b\xcc\x06\x78\x2c\xe7\
+\x89\x70\x3f\x27\x0a\x80\xdc\x78\xd7\x08\x04\xa4\x02\x40\x0c\xc1\
+\xee\x3e\x2f\x5a\x58\x58\xbf\xae\xbe\xfe\x2f\x30\x43\xe0\x75\x87\
+\xb7\xb6\xbe\x7e\x3f\x31\x07\x3f\x7a\x3f\xfd\x93\x1e\xcf\xfb\x4c\
+\xdd\xdf\x8e\xf7\x2c\xa4\x32\xde\x2e\x36\x18\x6e\xa1\xb3\x3d\x96\
+\x37\x34\x1c\x18\xa4\xd1\x5c\x04\xf9\x0f\x78\x2c\xe5\x11\x1e\x4e\
+\x14\x00\x79\xb1\x4e\xfd\xe7\xe2\x15\x02\x51\x00\x88\x20\xd8\xdd\
+\xe7\x59\x0a\x0a\xdc\xcb\x6a\x6b\x7f\x04\x33\x04\x5e\x3a\x78\x67\
+\xe8\xf5\x57\x34\x16\x17\x0f\x58\x50\x5d\xfd\x0d\x93\xf7\x77\x53\
+\x63\xe3\x11\xf4\xc8\x5e\xaa\xe3\xad\x8f\x52\x79\x7a\x57\x67\xcc\
+\xa8\xde\xdf\x3b\xec\xf6\x67\xd1\xa5\x08\xc8\x7f\xc0\x63\x11\x8f\
+\x38\x7b\x4f\x14\x00\xfc\x58\xe6\x9f\x83\x57\x07\xf9\xa4\xeb\x05\
+\x10\xec\x6e\xf2\xd0\x73\xd8\x33\xab\xaa\x76\x83\x79\x01\x2f\x5d\
+\xbc\x95\x75\x75\xbf\xb0\x65\x7f\xe7\x56\x57\x7f\x59\xcc\xe7\x17\
+\x27\x3b\xde\x22\x05\x05\x35\xe8\x6c\x07\x93\xda\xe3\x8d\x60\x70\
+\x6a\x22\x8f\x39\x42\xfe\x03\x1e\x43\x78\x52\x52\x01\x20\x88\x77\
+\xd3\x1f\xb9\x00\xe0\x27\x3c\x4b\x10\x04\x3b\x16\x4f\xf8\x71\x49\
+\xc9\xd2\x2d\x78\x32\x49\x44\x9b\xf1\x04\xb4\x9a\xa4\x35\xf8\xf7\
+\x5b\x52\x10\xf0\x80\x47\x37\xef\xa9\x40\xe0\xf3\x64\xc6\x9b\x3e\
+\x3f\xdf\xb9\xb8\xb6\xf6\x07\x26\xee\xef\x94\x8a\x8a\x6d\xb1\xe6\
+\x63\x80\xfc\x07\x3c\x06\xf1\x88\x02\x40\x14\xd3\xcf\xf1\x95\x72\
+\x48\xcf\x08\x82\xf9\x77\x9f\x77\xc2\x8b\x3e\xdf\xd8\x2d\x8d\x8d\
+\xed\x89\x6a\x33\xa6\x35\x98\x56\x93\xb4\x06\xff\x7e\x4b\x0a\x02\
+\x1e\xf0\x98\xc2\x3b\xc5\x6c\xba\x36\x91\xf1\x86\xa6\xe5\x9d\x56\
+\x51\xb1\x93\xc9\xfb\xbb\xac\xa6\xe6\xa7\xce\x9e\xc6\x80\xfc\x07\
+\x3c\x86\xf1\xa4\x09\xdd\xc3\x47\x2a\x00\x72\xc1\xfc\xd3\xc3\xbb\
+\xd3\x6e\x7f\x0e\xcc\x01\x78\xc0\xfb\x5b\x0b\xeb\x6a\xff\xf0\xab\
+\x55\xd1\x38\xe3\x4d\xf8\x51\x24\xb2\x84\x0d\xfb\xbb\xb1\xbe\xfe\
+\xf0\xf9\x7a\xfd\x8d\x90\xff\x80\xc7\x60\x5e\x62\x4f\xef\x91\x0a\
+\x00\x30\xff\x34\xf0\x06\xeb\xf5\xd7\x81\x39\x00\x0f\x78\xff\xcb\
+\xfb\x34\x1a\x5d\xc9\xeb\xfa\xed\x7b\x27\xbc\xe0\xf3\x8d\x61\xdb\
+\xfe\x3e\xec\x72\xbd\x65\x90\x48\x2c\x90\xff\x80\xc7\x5a\x5e\xaa\
+\xc6\x0f\xc1\xfe\x37\x0f\x3d\x93\xbd\xb1\xb1\xf1\x08\x98\x03\xf0\
+\x80\xf7\x6f\xde\x2d\x56\xeb\x93\x9d\x8d\xab\x3b\x6c\xb6\x67\xd9\
+\xba\xbf\xaf\x44\xc2\x73\x21\xff\x01\x8f\x0b\x3c\x08\x4e\x37\x78\
+\x7e\xa9\xb4\x7c\x75\x7d\xfd\x1f\x9b\xf1\xc4\x10\x4f\x9b\xf0\x04\
+\xb4\x8a\xa4\xd5\xf8\xf7\x9b\x53\x10\xf0\x80\xc7\x74\x1e\x56\x1c\
+\x1f\xad\x96\xc9\x5a\xc9\xe3\xea\x5c\x83\xe1\x5a\x36\xef\xef\xa7\
+\xe5\xe5\x5b\x21\xff\x01\x0f\xcc\x3f\x8b\x83\xad\xe6\xf3\x2d\xf3\
+\xab\xab\xff\x03\xe6\x00\x3c\xe0\xc5\xe6\xcd\xa9\xaa\xfa\xba\x80\
+\xc7\x53\xa0\x71\xd3\xa4\x50\x0c\xda\xd0\xd8\x78\x84\xcd\xfb\x3b\
+\xa7\xae\xf6\xff\xc0\x6c\x80\x07\xe6\x9f\xa5\xc1\x46\x33\xb2\x4d\
+\x2c\x2b\xdb\xb2\xb9\xa1\xa1\x3d\x11\x6d\xc2\xb4\x1a\xd3\x2a\x92\
+\x56\xe3\xdf\x6f\x4e\x41\xc0\x03\x1e\xdb\x78\xc3\xfc\xfe\xcf\x42\
+\x05\x05\x95\xab\xeb\xea\xfe\x64\xfb\xfe\xae\x68\x68\x38\x1a\x89\
+\x04\x15\x60\x36\xc0\x03\xf3\xcf\x3e\x5e\xfe\x3b\xa1\xd0\x5c\x30\
+\x07\xe0\x01\x2f\x39\x1e\x17\xcc\x9f\xe0\x19\x25\x12\x33\xe4\x53\
+\xe0\x81\xf9\x67\x17\xaf\x07\x9a\x8b\x1d\x92\x39\xf0\x80\x97\xdd\
+\x3c\x74\xff\x0f\xe4\x53\xe0\x81\xf9\x67\x11\xef\x3a\x8b\xe5\xe1\
+\x4d\x78\x02\x88\xa7\x8d\x78\xc2\x58\x49\xd2\x2a\xfc\xfb\x4d\x29\
+\x08\x78\xc0\x03\x1e\x73\x78\x0d\x0a\xc5\x40\xc8\xa7\xc0\x63\x93\
+\xf9\x27\xfc\xf4\x1f\x04\xfb\xdf\xbc\x53\xd4\xea\x4b\x20\x59\x02\
+\x0f\x78\xc0\x43\x3a\x55\xab\xbd\x0c\xcc\x06\x78\x2c\xe1\x11\x53\
+\xff\x27\x3c\x49\x90\x04\x82\xfd\x0f\xaf\x56\x2e\x6f\x5b\x5f\x57\
+\x77\x08\x92\x25\xf0\x80\x07\x3c\xa4\x21\x26\xd3\x3d\x60\x36\xc0\
+\x63\x89\xf9\xe7\x26\x54\x00\x90\xde\x27\x2c\x85\x60\xff\xbd\xb8\
+\xc4\xe2\xd0\xb2\x9a\x9a\x5f\x36\xd5\xd7\xb7\xc7\xd3\x46\x4c\xab\
+\x30\xad\x24\x69\x15\xfe\xfd\xa6\x14\x04\x3c\x6e\xf2\x36\xd4\xd7\
+\x1f\x85\xf8\xb1\x9b\x37\xd4\x6e\x1f\x0e\x66\x03\x3c\x16\x98\x3f\
+\xf1\xbe\x9f\xd8\x05\x00\xfe\x63\x11\x7e\xf4\x2f\x85\x60\xf3\x78\
+\x0a\xa1\x50\x37\xab\xbc\xfc\x4b\x48\x96\xc0\x4b\x27\xef\x05\x9f\
+\xef\x33\x88\x1f\xbb\x79\xcf\x7b\xbd\x63\xc0\x6c\x80\xc7\x70\xf3\
+\xe7\xe3\x6f\xfb\xcd\x8b\x39\xf5\x3f\xfe\x63\x01\x7e\xf4\x2f\x21\
+\xbd\x5b\x38\x7b\xcd\x9f\xc7\x2b\x18\x1d\x8d\xae\xd9\x88\x27\x81\
+\x58\xda\x80\x27\x8c\x15\x24\xad\xc4\xbf\xdf\x98\x82\x80\xc7\x5d\
+\xde\xdc\xca\xca\x6f\x6b\xe5\xf2\xfe\x10\x3f\x76\xf3\xde\x0f\x85\
+\x16\x81\xd9\x00\x8f\xc1\x3c\x01\xae\xe3\x05\x40\xbc\x4a\x41\x48\
+\x2a\x00\x24\x59\x1e\xec\xdc\x97\x03\x81\x29\x90\x2c\x81\x97\x6e\
+\xde\x20\x8d\xe6\xa2\x36\x83\xfe\x3c\x88\x1f\xbb\x79\x53\xca\xcb\
+\x77\x81\xd9\x00\x8f\xa1\x3c\x11\xee\xe7\x44\x01\x90\x1b\xef\x1a\
+\x81\x80\x54\x00\x88\xb3\x3d\xd8\xf7\x39\x1c\xaf\x41\xb2\x04\x5e\
+\xba\x79\x1f\x46\x22\xcb\x9a\x9a\xea\x64\x67\x98\x4d\xb7\x42\xfc\
+\xd8\xcd\x5b\x5e\x5d\xfd\x3b\x98\x0d\xf0\x18\xc8\x23\x3c\x9c\x28\
+\x00\xf2\x62\x9d\xfa\xcf\xc5\x2b\x04\xa2\x00\x10\x65\x7b\xb0\x2f\
+\x31\x1a\xef\x80\x64\x09\xbc\x74\xf3\xd6\xd5\xd7\x1f\x2d\xd5\x6a\
+\x9b\x51\xbf\xbb\xd2\xe1\x78\x0a\xe2\xc7\x7e\x1e\xba\x4c\x08\xe6\
+\x05\x3c\x06\xf1\x88\xb3\xf7\x44\x01\xc0\x8f\x65\xfe\x39\x78\x75\
+\x90\x4f\xba\x5e\x90\xd5\xc1\xee\x53\x5c\x7c\xd6\xfa\xba\xba\xa3\
+\x1b\xeb\xea\xda\x63\x69\x03\xa6\x95\x98\x56\x90\xb4\x12\xff\x7e\
+\x63\x0a\x02\x1e\xf7\x79\xf7\xba\x5d\xef\x13\x7d\xef\x6e\x8f\x67\
+\x24\xc4\x8f\xfd\x3c\x7d\x7e\xbe\x13\xcc\x0b\x78\x0c\xe2\x49\x49\
+\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\x2c\x41\x1c\x0d\x76\
+\x54\x22\xa9\x5f\x59\x5d\xfd\xd7\x06\x3c\x11\x74\xa5\xf5\x78\xc2\
+\x58\x4e\xd2\x0a\xfc\xfb\x0d\x29\x08\x78\xdc\xe7\xcd\xa9\xa9\xfe\
+\xd5\x6b\xd0\x3b\x89\xfe\xf7\x6c\x20\x30\x15\xe2\xc7\x7e\x1e\xca\
+\x19\x60\x5e\xc0\x63\x10\x8f\x28\x00\x44\x31\xfd\x1c\x5f\x29\x87\
+\xf4\x8c\x60\x56\x9b\xbf\x35\x3f\xdf\xb5\xa0\xb2\xf2\x47\x48\x6e\
+\xc0\xcb\x04\xef\x3c\xb3\xf9\x6e\x72\xff\xfb\x28\x1c\x5e\x09\xf1\
+\x63\x3f\xaf\x4d\xa5\x3a\x03\xcc\x0b\x78\x0c\xe2\x49\x13\xba\x87\
+\x8f\x54\x00\xe4\x66\xbb\xf9\x4b\x78\x3c\xe5\x94\xd2\xd2\x5d\x90\
+\xdc\x80\x97\x09\xde\xa7\xa5\xa5\xdb\xcb\xca\x4a\x8a\xc9\xfd\x6f\
+\x56\x79\xf9\x57\x10\x3f\xf6\xf3\xce\x30\x99\x6e\x05\xf3\x02\x1e\
+\x83\x78\x92\x64\xa6\xfb\xcd\xc9\x76\xf3\xc7\x16\xe1\x07\xe1\xf0\
+\x12\x48\x6e\xc0\xcb\x14\xaf\x5e\xa7\x3b\xb5\x43\xff\xeb\xb1\xa6\
+\xb6\xf6\x20\xc4\x8f\xfd\xbc\x2b\x6d\xb6\xa7\xc1\xbc\x80\xc7\x3a\
+\x5e\xaa\xc6\xcf\xb1\xe0\x9c\xf0\xac\xd7\x3b\x06\x92\x1b\xf0\x32\
+\xc5\x7b\xc6\xef\x9f\xda\xb1\xff\x49\x78\xbc\x62\x88\x1f\x37\x78\
+\x43\x3d\x9e\xf7\xc0\x6c\x80\xc7\x66\x5e\xd6\x06\xe7\x16\x8b\xe5\
+\xd9\xf5\xb5\xb5\xed\xb1\xb4\x0e\xd3\x72\x4c\xcb\xea\xfe\xd1\x72\
+\xfc\xfb\xf5\x29\x08\x78\xd9\xc3\x5b\x50\x5d\xf5\x97\x57\xa7\x8d\
+\x74\xec\x7f\x36\xb1\x38\x00\xf1\xe3\x06\xef\xd9\x40\x60\x1a\x98\
+\x0d\xf0\xc0\xfc\x59\xc6\x3b\x4b\xab\xbd\x16\x92\x1b\xf0\x32\xc9\
+\xbb\xca\x66\x7b\xb6\xb3\xfe\x57\x2e\x93\xb5\x42\xfc\xb8\xc1\x1b\
+\x19\x89\xac\x05\xb3\x01\x1e\x98\x3f\x8b\x78\xcd\x0a\xc5\x89\x6b\
+\x6a\x6b\x8f\x40\x72\x03\x5e\xa6\x78\x93\x2a\x2a\xbe\x75\x3a\xed\
+\xda\xce\xfa\x5f\x7f\x85\xe2\x5c\x88\x1f\x37\x78\x33\xcb\xcb\xbf\
+\x06\xb3\x01\x1e\x98\x3f\x4b\x78\x01\xa1\xb0\x6c\x59\x75\xf5\x1f\
+\x90\xdc\x80\x97\x49\x5e\x5f\x83\xe1\xb2\xae\xfa\xf3\x05\x3a\xdd\
+\xcd\x10\x3f\x6e\xf0\xd6\x54\x57\x1f\x42\x37\x75\x82\xd9\x00\x0f\
+\xcc\x9f\xe1\x3c\x35\x9f\x6f\x99\x5d\x5e\xfe\x9f\x75\xf8\xe0\xed\
+\x4c\x6b\xd1\x40\xef\x30\xd8\x97\xe1\xdf\xaf\x4b\x41\xc0\xcb\x3e\
+\xde\x6b\x91\xf0\xd2\x58\xfd\xf9\x46\xb3\xf9\x29\x88\x1f\x77\x78\
+\xe8\x31\x62\x30\x1b\xe0\x81\xf9\x33\x98\x27\xe3\xf1\x0a\x3f\x8b\
+\x46\x37\x43\x72\x03\x5e\x26\x79\x4b\x6a\x6b\x8f\x78\x25\x92\xf2\
+\x58\xfd\xf6\x51\xa7\xf3\x3d\x88\x1f\x77\x78\x6e\xb1\x38\x08\x66\
+\x03\x3c\x36\x98\x7f\xc2\x4f\xff\x71\x2c\x38\xf9\x6f\xfa\x7c\x73\
+\xd6\xd5\xd4\xb4\x77\xa5\xb5\x98\x96\xd5\xfe\x5b\x6b\x63\xac\x03\
+\x3c\xe0\x75\xd4\x3d\x36\xdb\x1b\xf1\xfa\xee\x2b\x7e\xff\x0c\x88\
+\x1f\x77\x78\x95\x52\x69\x1b\x98\x0d\xf0\x18\xce\x23\xa6\xfe\x4f\
+\x78\x92\x20\x09\x57\x82\x73\xec\x88\x0b\x92\x1b\xf0\x32\xcc\x5b\
+\x50\x51\xf1\x53\x22\xa7\x83\x47\x47\x22\x1b\x20\x7e\xdc\xe1\x0d\
+\x50\x2a\xcf\x03\xb3\x01\x1e\xc3\xcd\x3f\x37\xa1\x02\x80\xf4\x3e\
+\x61\x29\x17\x82\x73\xb5\xd1\xf8\x20\x24\x37\xe0\x51\xc1\x3b\x57\
+\xab\xbd\x21\x91\x3e\x3c\xaf\xbc\xfc\xbf\x10\x3f\xee\xf0\x2e\xd0\
+\xe9\x6e\x05\xb3\x01\x1e\x83\xcd\x9f\x78\xdf\x4f\xec\x02\x00\xff\
+\xb1\x08\x3f\xfa\x97\xb2\x3d\x38\x27\x2a\x95\x17\xad\xc5\x07\x75\
+\x67\x5a\x83\x69\x69\xed\xbf\xb5\x26\xc6\x3a\xc0\x03\x5e\x67\xbc\
+\x31\xe1\xf0\x26\xac\xcb\xe5\x26\xd0\x8d\x73\x57\xd7\xd4\x1c\x85\
+\xf8\x71\x87\x77\xb3\xc5\xf2\x0c\x98\x0d\xf0\x18\x6a\xfe\x7c\xfc\
+\x6d\xbf\x79\x31\xa7\xfe\xc7\x7f\x2c\xc0\x8f\xfe\x25\xa4\x77\x0b\
+\xb3\x32\x38\xe8\xba\xdc\xaa\xaa\xaa\x43\x90\xdc\x80\x47\x05\xaf\
+\x4a\x26\xeb\x99\x48\x3f\x96\x0b\x85\x7a\x88\x1f\xb7\x78\x8f\xba\
+\x5c\x1f\x80\xd9\x00\x8f\x81\x3c\x01\xae\xe3\x05\x40\xbc\x4a\x41\
+\x48\x2a\x00\x24\x6c\x0d\x0e\xba\x2b\x77\x61\x65\xe5\x2f\x90\xdc\
+\x80\x47\x05\xef\x59\xb7\xfb\xb3\x44\xfb\xb2\x47\x24\x2a\x85\xf8\
+\x71\x8b\xf7\xaa\xdf\x3f\x13\xcc\x06\x78\x0c\xe3\x89\x70\x3f\x27\
+\x0a\x80\xdc\x78\xd7\x08\x04\xa4\x02\x40\xcc\xd6\xe0\x28\x84\x42\
+\xdd\x94\x68\xf4\x8b\xb5\xd5\xd5\xed\x9d\x69\x0d\xa6\xa5\x35\xff\
+\xd6\x9a\x2e\x7e\x1f\x4f\xc0\xcb\x6e\xde\xb2\x8a\x8a\xfd\x6a\x3e\
+\xdf\x9a\x68\x7f\xae\x2f\x2a\x1a\x08\xf1\xe3\x16\x6f\x4c\x28\xb4\
+\x11\xcc\x0b\x78\x0c\xe2\x11\x1e\x4e\x14\x00\x79\xb1\x4e\xfd\xe7\
+\xe2\x15\x02\x51\x00\x88\x58\x1c\x1c\xc9\xa8\x60\x70\x0d\x24\x37\
+\xe0\x51\xc5\xbb\xda\x68\x7c\x28\x99\xfe\x7c\xa2\xd1\x70\x35\xc4\
+\x8f\x5b\xbc\xb9\x65\x65\x3f\x80\x79\x01\x8f\x21\x3c\xe2\xec\x3d\
+\x51\x00\xf0\x63\x99\x7f\x0e\x5e\x1d\xe4\x93\xae\x17\xb0\x35\x38\
+\x39\x2f\x79\x3c\x93\xd7\xe0\x03\xbb\xa3\x56\xe3\x83\x7d\x09\x49\
+\xe8\xf3\xea\x2e\x7e\x1f\x4f\xc0\x03\xde\xd4\x68\xf4\x4b\xac\xdf\
+\x89\x92\xe9\xcf\x17\x98\x4d\x0f\x41\xfc\xb8\xc5\x5b\x55\x5d\x7d\
+\x54\xa9\x54\xf0\xc1\xbc\x80\xc7\x00\x9e\x94\x54\x00\x08\xe2\xdd\
+\xf4\x47\x2e\x00\xf8\x09\xcf\x12\xc4\xc0\xe0\xdc\x65\xb5\xbe\x02\
+\xc9\x08\x78\x54\xf2\xfa\x14\x17\x9f\x95\x6c\x7f\xbe\xc9\x61\x7f\
+\x15\xe2\xc7\x3d\x9e\xd7\x68\xf0\x80\x79\x01\x8f\x01\x3c\xa2\x00\
+\x10\xc5\xf4\x73\x7c\xa5\x1c\xd2\x33\x82\xac\x35\xff\x0b\xf5\xfa\
+\xdb\x21\x19\x01\x8f\x4a\xde\x9b\x7e\xff\xbc\x54\xfa\xf3\xc3\x1e\
+\xf7\xe7\x10\x3f\xee\xf1\xa2\x6a\x55\x13\x98\x17\xf0\x18\xc0\x93\
+\x26\x74\x0f\x1f\xa9\x00\xc8\x65\xb3\xf9\xf7\x2a\x2a\x3a\x13\x9d\
+\x82\x83\x64\x04\x3c\xaa\x78\xab\x2a\x2b\x0f\x3b\x45\xa2\x70\x2a\
+\xfd\xf9\x95\x50\x70\x31\xb4\x07\xf7\x78\xf5\x5a\xcd\x99\x60\x5e\
+\xc0\x63\x00\x4f\x92\xcc\x74\xbf\x39\x6c\x36\xff\x52\x89\xa4\x6e\
+\x69\x45\xc5\x5f\x6b\xaa\xaa\xda\x3b\x6a\x35\xa6\xa5\xd5\x55\xed\
+\x4b\x48\x42\x9f\x57\x77\xf2\xdb\x44\x04\x3c\xe0\x11\x1a\x6a\xb5\
+\xbe\x9c\x6a\x7f\x1e\x17\x0e\xef\x80\xf6\xe0\x1e\x6f\x90\xc1\x70\
+\x2d\x98\x17\xf0\x58\xc3\x4b\xd5\xf8\x99\xb2\x33\xd6\xfc\x7c\xd7\
+\x9c\xd2\xd2\x1f\x56\xe3\x83\x91\xac\x55\x55\x7f\x0f\xca\xc5\x24\
+\xa1\xcf\xab\x3a\xf9\x6d\x22\x02\x1e\xf0\x08\xcd\x29\x2b\xfb\xbf\
+\x02\x1e\x4f\x91\x6a\x7f\x5e\x58\x5e\xfe\x1b\xb4\x07\xf7\x78\x17\
+\x99\x4c\x0f\x81\xd9\x00\x8f\x8d\x3c\xd6\xed\x0c\x7a\xe1\xca\xf8\
+\x70\x78\x27\x24\x23\xe0\x51\xcd\x3b\x53\xad\xbe\xa6\x1b\xfd\x59\
+\x0c\xed\xc1\x4d\xde\xed\x56\xcb\x70\x30\x1b\xe0\x81\xf9\x67\x9e\
+\x27\x78\xd7\xef\x5f\x02\xc9\x08\x78\x54\xf3\x3e\x0e\x85\xd6\x63\
+\xfd\x2f\x27\xd5\xfe\x6c\xe4\xf3\xed\xd0\x1e\xdc\xe4\x3d\xe5\x72\
+\x8d\x06\xb3\x01\x1e\x98\x7f\x66\x79\x27\xa0\x81\x06\xc9\x08\x78\
+\x74\xf0\x4a\x65\xb2\xa6\xee\xf4\xe7\xb0\x44\x52\x0b\xed\xc1\x4d\
+\xde\x9b\x3e\xdf\x42\x30\x1b\xe0\x81\xf9\x67\x90\x77\xa3\xc9\xf4\
+\x0c\x24\x23\xe0\xd1\xc1\x7b\xdc\xe9\xfc\xa4\xbb\xfd\xb9\x55\x2e\
+\x3f\x15\xda\x83\x9b\x3c\x74\x49\x12\xcc\x06\x78\x60\xfe\x19\xe2\
+\x9d\xae\x56\x5f\xbd\xaa\xb2\xb2\xbd\xa3\x56\x62\x5a\x5c\x55\xd9\
+\xbe\x08\x1b\x84\x84\xd0\xe7\x95\x9d\xfc\x36\x11\x01\x0f\x78\x1d\
+\xb5\xa8\xac\xec\x4f\xad\x40\x60\xea\x6e\x7f\xee\xaa\x0f\x43\x7b\
+\xb0\x9f\xb7\xb0\xbc\xfc\x77\x30\x1b\xe0\x81\xf9\x67\x80\xd7\x50\
+\x58\x38\x70\x45\x79\xf9\x61\x48\x46\xc0\xa3\x83\x77\xb9\x4e\x77\
+\x5f\x3a\xfa\xf3\x95\x7a\xfd\x43\xd0\x1e\xdc\xe5\xfd\x7d\x7f\x32\
+\x98\x0d\xf0\x98\x6b\xfe\x09\x3f\xfd\xc7\xa4\x9d\x29\xe2\xf1\x64\
+\x1e\x91\x28\xda\x22\x97\x9f\x76\x81\x56\x7b\x1b\x9a\xf6\xf7\x25\
+\xb7\x7b\xc6\x27\x91\xc8\xee\xb9\x95\x95\x07\x21\x19\x01\x2f\x53\
+\xbc\x09\xe1\xf0\x5e\xac\x0b\x0a\xd3\xd1\x9f\xef\xb1\x58\x5e\x87\
+\xf6\xe0\x2e\xcf\xc0\xe7\x3b\xc0\x6c\x80\xc7\x50\x1e\x31\xf5\x7f\
+\xc2\x93\x04\x49\xd8\x10\x9c\xea\xea\x0a\xb9\xcb\xa0\x0b\x56\x6b\
+\x35\x03\x06\xa9\xd5\x97\x5d\x63\x30\x3c\xfa\x98\xc3\x31\xea\xdd\
+\x40\x60\xf9\xec\x68\xf4\x07\x48\x6e\xc0\xeb\x0e\x0f\x15\x9d\xe9\
+\xea\xcf\xcf\xb9\x5c\x13\xa0\x3d\xb8\xcb\x0b\x49\x24\x75\x60\x36\
+\xc0\x63\xa8\xf9\xe7\x26\x54\x00\x90\xde\x27\x2c\xe5\x42\x70\xe4\
+\x3c\x9e\xd4\x29\x12\x45\x9a\xe5\xf2\x53\x06\x6b\x34\xb7\xdc\x61\
+\xb1\x8c\x78\xc9\xe3\x99\xfa\x59\x28\xb4\x7d\x49\x45\xc5\x01\x48\
+\x6e\xc0\xeb\x4a\xaf\x78\x3c\xb3\xd2\xd9\x9f\xdf\xf3\xfb\x57\x40\
+\x7b\x40\xb1\x08\xe6\x05\x3c\x8a\xcd\x9f\x78\xdf\x4f\xec\x02\x00\
+\xff\xb1\x08\x3f\xfa\x97\x72\x3d\x38\x76\xbb\x35\xd7\x6d\xd0\xf9\
+\x2a\x74\xda\xfe\x03\xf5\xba\xab\x2f\xb7\x98\x9f\xba\xdf\xe5\xfc\
+\xf4\x2d\xbf\x7f\xe9\x8c\x68\xf4\xfb\x95\x78\x42\x48\x54\x2b\xd0\
+\x0d\x63\x58\xb2\x58\x88\x25\x0d\x42\xe8\xf3\x8a\x24\x39\xc0\xa3\
+\x9f\xb7\xbc\xbc\xfc\xb0\x4d\x2c\x0e\xa4\xb3\xff\x4d\x2a\x29\xf9\
+\x12\xda\x83\xbb\xbc\xd3\x3a\x4c\x12\x05\xe6\x05\x3c\x06\x98\x3f\
+\x1f\x7f\xdb\x6f\x5e\xcc\xa9\xff\xf1\x1f\x0b\xf0\xa3\x7f\x09\xe9\
+\xdd\xc2\xd9\x1c\x6c\x89\x4b\x2c\x0e\x35\xca\x64\x27\x9d\xab\xd1\
+\xdc\x74\xab\xd9\xfc\xd2\x0b\x6e\xf7\xe4\x31\xa1\xd0\xd6\xc5\xe5\
+\xe5\x7f\xad\xac\xa8\x68\x27\xb4\x02\xd3\x22\x74\x37\x30\x49\xe8\
+\xf3\x0a\xd2\x6f\x92\x11\xf0\xe8\xe5\x61\x6d\x3d\x2c\xcd\xfd\xaf\
+\x07\xd6\x67\x0e\x40\x7b\x70\x97\x77\x95\x5e\xff\x30\x98\x17\xf0\
+\x18\xc4\x13\xe0\x3a\x5e\x00\xc4\xab\x14\x84\xa4\x02\x40\x02\xc1\
+\x8e\x9d\xd0\xe5\x42\xa1\xa1\x44\x22\x69\x18\xa8\x52\x5d\x74\x89\
+\xd9\xfc\xc4\xdd\x2e\xd7\xc7\x2f\x07\x02\xcb\xc6\x45\xa3\xdf\x41\
+\xb2\x64\x2f\x6f\x46\x49\xc9\x7f\x65\xc7\xee\x3d\x4d\x5f\x7f\x41\
+\xef\x0f\x80\xf6\xe0\x36\xef\x6e\x8b\xe5\x0d\x30\x2f\xe0\x31\x84\
+\x27\xc2\xfd\x9c\x28\x00\x72\xe3\x5d\x23\x10\x90\x0a\x00\x31\x04\
+\xbb\x7b\x3c\x89\x44\x2c\x41\xa7\x90\x1b\x0a\x0b\x4f\x3c\x57\xab\
+\xbd\x01\x3b\xa2\x7c\xf1\x79\x97\x6b\xe2\xa7\xc1\xe0\xe6\x85\xa5\
+\xa5\x7f\x42\xf2\x65\x2e\xef\x64\x95\xea\x8a\x74\xf7\x17\xbb\x58\
+\xec\x87\xf6\xe0\x36\x0f\x8d\x6f\xc8\x7f\xc0\x63\x00\x8f\xf0\x70\
+\xa2\x00\xc8\x8b\x75\xea\x3f\x17\xaf\x10\x88\x02\x40\x04\xc1\xce\
+\x38\xaf\x87\x42\x28\xd4\xa1\xbb\x86\xfb\xcb\xe5\xe7\x5f\xa6\xd7\
+\xdf\xff\x80\xcd\x36\xf2\x75\xaf\x77\xe1\x67\x25\x25\xdf\xce\xab\
+\xac\x3c\xba\x00\x4b\x42\x0b\xf0\x84\xb4\x1c\x4f\x54\xc9\x0a\xad\
+\xb7\x10\xe7\x00\x2f\x31\x7d\x10\x08\xac\xc1\xda\xe7\x84\x74\xf7\
+\x97\x72\x99\xac\x15\xda\x83\xdb\xbc\x77\x7d\xbe\x95\x90\xff\x80\
+\x47\x33\x8f\x38\x7b\x4f\x14\x00\xfc\x58\xe6\x9f\x83\x57\x07\xf9\
+\xa4\xeb\x05\x10\x6c\x9a\x79\x81\x80\x57\xe3\x55\xab\x2a\xeb\x35\
+\x9a\x33\xce\xd2\x68\xae\xbf\xd9\x64\x7a\xfe\x19\x97\x6b\xfc\xa8\
+\x40\x60\xe3\xfc\xb2\xb2\x3f\x20\xf9\x66\x8e\x47\x3c\xca\x95\xee\
+\xf6\xed\x53\x5c\x3c\x18\xda\x83\xdb\x3c\xac\x78\xff\x1a\xf2\x1f\
+\xf0\x68\xe6\x49\x49\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\
+\x2c\x41\x10\x6c\x5a\x79\x4a\x1e\x4f\xe3\x2f\x28\xa8\xe9\xa3\x50\
+\x0c\xbe\x44\xa7\xbb\xf7\x3e\xab\xf5\x9d\x57\x3d\x9e\xf9\x93\x23\
+\x91\xaf\x96\x55\x54\x1c\x85\x64\x9e\x1a\xef\x61\xbb\xfd\xc3\x4c\
+\xb5\xef\x59\x46\xe3\x5d\xd0\x1e\xdc\xe6\xcd\xae\xa8\x38\xd8\xd4\
+\x54\x57\x08\xf9\x0a\x78\x34\xf2\x88\x02\x40\x14\xd3\xcf\xf1\x95\
+\x72\x48\xcf\x08\x82\xf9\x73\x80\x57\x58\x28\x13\x7a\x55\xca\xb2\
+\x3a\xb5\xea\xf4\xb7\x02\x81\x0d\x0b\x50\x72\x42\x49\xaa\xbc\xbc\
+\x7d\x45\x0a\x42\xeb\xa1\xf5\x17\x90\xc4\x45\xde\xfc\xd2\xd2\xdf\
+\xd1\x4d\x9d\x99\x6a\xdf\xab\xac\xd6\x17\xb8\x1c\x3f\xe0\xfd\x2d\
+\xa7\xd9\x68\x83\x7c\x05\x3c\x1a\x79\xd2\x84\xee\xe1\x23\x15\x00\
+\xb9\x60\xfe\xdc\xe4\x35\x6a\x35\x67\x43\x32\x4f\x4c\x17\xe9\x74\
+\x43\x33\xd9\x1e\x43\x9d\xce\x51\x60\xae\xdc\xe7\xf9\xb4\x9a\x4a\
+\xc8\x57\xc0\xa3\x91\x97\xd8\xd3\x7b\xa4\x02\x00\xcc\x9f\xc3\xbc\
+\x91\x7e\xff\x9a\xe5\x78\xb2\x4a\x46\xcb\x30\xa1\x84\x36\x9f\x24\
+\xf4\x79\x59\x0a\x2c\xa6\xf3\x3e\x0b\x85\x76\xa1\x33\x27\x99\x6c\
+\x8f\xa7\x3d\x9e\xd9\x5c\x8d\x1f\xf0\xfe\xe1\x95\x15\x15\xf5\x82\
+\x7c\x05\x3c\xc6\xf3\x52\x35\x7e\x08\x36\xbb\x78\xcd\x32\xd9\x29\
+\x90\xcc\x63\xab\xb1\xb0\xf0\xe4\x4c\xb7\xc7\x3b\xc1\xc0\x46\x30\
+\x57\xee\xf3\xd0\x7d\x39\x90\xaf\x80\xc7\x26\x1e\x04\x87\xdb\xbc\
+\x1e\x1f\xfa\xfd\xeb\x21\x99\x77\xae\x17\x5d\xae\xe9\x54\xb4\xc7\
+\xd4\x92\x92\xef\xc1\x5c\xb9\xcf\x43\xef\x1b\x81\x7c\x05\x3c\x30\
+\x7f\xe0\x31\x86\xd7\x22\x97\x9f\xbe\xbc\xac\xac\x3d\x9e\x96\x61\
+\x5a\x80\x6e\x88\x23\x09\x7d\x5e\x96\xc0\xba\x6c\xe4\x2d\x8e\x46\
+\x0f\xa1\x1b\x26\x33\xdd\x1e\x1a\x8d\x3a\x7f\x49\x59\xd9\x11\xae\
+\xc5\x0f\x78\xff\xe6\x5d\x6f\x34\x3e\x0d\xf9\x0a\x78\x60\xfe\xc0\
+\x63\x12\xaf\xc7\x28\xbf\x7f\x23\x24\xf3\xff\xe5\x5d\x63\x31\x0f\
+\xa7\xa2\x3d\xd0\xa4\x4f\x60\xae\xd9\xc1\x7b\xd0\x62\x79\x1f\xf2\
+\x15\xf0\xc0\xfc\x81\xc7\x28\x5e\x5b\x51\xd1\x59\xcb\xf0\x44\xd6\
+\x51\x4b\x31\xa1\x84\x36\x8f\x24\xf4\x79\x69\x17\xbf\x8f\x27\x36\
+\xf0\x3e\x2f\x29\xf9\x3e\x60\xb7\x1a\xa9\x68\x0f\x8f\x48\x14\xe5\
+\x5a\xfc\x80\xd7\x39\x6f\x98\xd3\x39\x13\xf2\x15\xf0\xc0\xfc\x81\
+\xc7\x34\xde\x09\xa3\xfc\xfe\x2d\x90\xcc\xff\xe6\x0d\xd0\xeb\xae\
+\xa1\xaa\x3d\x6a\x0a\x0a\xfa\x81\xb9\x66\x07\xef\x23\xbf\x7f\x23\
+\xe4\x2b\xe0\x81\xf9\x03\x8f\x71\x3c\x74\x87\x32\x24\xf3\xf2\xf6\
+\x57\xfc\xfe\xd5\xf5\xf5\xd5\x45\x54\xb5\xc7\x89\xc5\xc5\x17\x83\
+\xb9\x66\x07\x6f\x5a\x38\xfc\x03\xe4\x2b\xe0\x31\xd5\xfc\x13\x7e\
+\xfa\x0f\x82\xcd\x49\x5e\xce\xa7\x81\xc0\xf6\x6c\x4e\xe6\x73\xca\
+\xcb\x8f\x96\x68\x35\x6d\x54\xb6\xc7\x85\x5a\xed\x5d\x60\xae\xd9\
+\xc1\x5b\x52\x56\x76\x14\x6b\xf2\x3c\xc8\x57\xc0\x63\x18\x8f\x98\
+\xfa\x3f\xe1\x49\x82\x24\x10\x6c\xee\xf1\xfa\xcb\xe5\x17\x64\x73\
+\x32\xbf\xcb\xe1\x18\x45\x75\x7b\xdc\x6c\x34\xbe\x08\xe6\x9a\x3d\
+\x3c\x34\xa5\x34\xe4\x2b\xe0\x31\xcc\xfc\x73\x13\x2a\x00\x48\xef\
+\x13\x96\x42\xb0\xb9\xc7\x43\x8f\xa4\x7d\x18\x0c\xee\x9e\x8b\x25\
+\x2a\x42\xf3\xd0\x91\x4b\x69\x69\xfb\xd2\x14\x84\xd6\x9b\x47\x62\
+\x31\x99\x37\x39\x1a\xfd\xdd\xad\xd7\xba\xa9\x6e\x8f\x47\x6c\xb6\
+\x4f\xb8\x10\x3f\xe0\x25\x26\xb7\x50\x58\x06\xf9\x0a\x78\x0c\x32\
+\x7f\xe2\x7d\x3f\xb1\x0b\x00\xfc\xc7\x22\xfc\xe8\x5f\x0a\xc1\xe6\
+\x26\x0f\xdd\x00\x97\x8d\xc9\xfc\x6c\x83\xe1\x3e\x3a\xda\xe3\x15\
+\xb7\x7b\x3e\x98\x6b\xf6\xf0\xea\x0a\x0a\x06\x40\xbe\x02\x1e\x43\
+\xcc\x9f\x8f\xbf\xed\x37\x2f\xe6\xd4\xff\xf8\x8f\x05\xf8\xd1\xbf\
+\x84\xf4\x6e\x61\x08\x36\xc7\x78\x65\x65\x25\xc5\x1f\x85\x82\xfb\
+\xb2\x29\x99\xbf\x17\x0a\xee\x8c\x44\x82\x0a\x3a\xda\xe3\x93\x40\
+\x60\x3b\x98\x6b\xf6\xf0\x06\x15\x17\x5f\x0a\xf9\x0a\x78\x0c\xe0\
+\x09\x70\x1d\x2f\x00\xe2\x55\x0a\x42\x52\x01\x20\x81\x60\x73\x97\
+\x37\x48\xa7\xbd\x2e\x9b\x92\x79\xbd\x4a\x79\x06\x5d\xed\x31\x2b\
+\x12\xf9\x15\xcc\x35\x7b\x78\x17\x6a\xb5\xf7\x40\xbe\x02\x1e\xcd\
+\x3c\x11\xee\xe7\x44\x01\x90\x1b\xef\x1a\x81\x80\x54\x00\x88\x21\
+\xd8\xdc\xe6\xc9\xe5\x45\x82\xcf\x82\xc1\x7d\xd9\x90\xcc\x9f\x70\
+\xb9\xa6\xd1\xd8\x1e\x22\x30\xd7\xec\xe2\x5d\x6f\xb1\xbc\x06\xf9\
+\x0a\x78\x34\xf2\x08\x0f\x27\x0a\x80\xbc\x58\xa7\xfe\x73\xf1\x0a\
+\x81\x28\x00\x44\x10\xec\xec\xe0\x9d\xa2\x54\x5e\xb9\x24\x1a\x6d\
+\x4f\x54\x8b\x31\xcd\xc5\x12\xdc\x1c\x92\xd0\xe7\xc5\x49\x30\xa8\
+\xe6\xcd\x28\x29\x39\x68\x10\x0a\x3d\x74\xb5\x87\x89\xcf\xb7\xb1\
+\x39\x7e\xc0\x4b\x9e\x77\x9f\xd3\x39\x0e\xf2\x0b\xf0\x68\xe2\x11\
+\x67\xef\x89\x02\x80\x1f\xcb\xfc\x73\xf0\xea\x20\x9f\x74\xbd\x00\
+\x82\x9d\x3d\xbc\xfc\x71\x81\xc0\x97\x5c\x4e\xe6\x57\x1b\x0c\x4f\
+\xd3\xd9\x1e\xfe\x82\x82\x1a\x30\xd7\xec\xe2\xbd\xe8\x76\x2f\x85\
+\xfc\x02\x3c\x9a\x78\x52\x52\x01\x20\x88\x77\xd3\x1f\xb9\x00\xe0\
+\x27\x3c\x4b\x10\x04\x9b\x33\xbc\xd3\x94\xca\x6b\xb9\x9a\xcc\xc7\
+\x07\x83\xdf\x2a\x78\xbc\x02\x3a\xdb\xa3\x59\x26\x3b\x05\xcc\x35\
+\xbb\x78\xef\x07\x02\xbb\x21\xbf\x00\x8f\x26\x1e\x51\x00\x88\x62\
+\xfa\x39\xbe\x52\x0e\xe9\x19\x41\x30\xff\xec\xe4\x09\x30\xa3\xfc\
+\x86\x8b\xc9\x1c\x4d\x7a\x44\x77\x7b\x9c\xa6\x52\x5d\x0d\xe6\x9a\
+\x5d\xbc\x29\x25\x25\x7f\x40\x7e\x01\x1e\x4d\x3c\x69\x42\xf7\xf0\
+\x91\x0a\x80\x5c\x30\xff\xec\xe6\x9d\xa1\x52\xdd\xc0\xb5\x64\xfe\
+\xba\xdb\xbd\x14\xdb\xb5\x1e\x74\xb7\xc7\x45\x46\xe3\x13\x60\xae\
+\xd9\xc7\xc3\x9a\x5f\x02\xf9\x05\x78\x34\xf0\x12\x7b\x7a\x8f\x54\
+\x00\x80\xf9\x03\x4f\x38\x31\x18\xfc\xcf\x62\x3c\xa9\x21\x2d\xc2\
+\x84\x12\xda\x6c\x92\xd0\xe7\x45\xa4\xdf\x24\x23\x2a\x79\x0b\xa3\
+\xd1\xa3\x7e\xa1\xb0\x9c\x09\xed\x71\x93\xd5\xf2\x0e\xdb\xe2\x07\
+\xbc\xee\xf3\x0c\x7c\xbe\x03\xf2\x0b\xf0\x18\xcb\x4b\xd5\xf8\x21\
+\xd8\xdc\xe4\x9d\xa3\x54\xde\xb2\xb8\xa4\xa4\x1d\x69\x11\xa6\x39\
+\x58\x12\x9b\x4d\x12\xfa\xbc\x08\xff\xf7\x64\x45\x35\xef\x2e\x93\
+\xe9\x2d\xa6\xb4\xc7\x43\x4e\xe7\x14\xb6\xc5\x0f\x78\xdd\xe7\x85\
+\x24\x92\x3a\xc8\x2f\xc0\x63\x03\x0f\x82\x03\x3c\xb4\x88\x26\x05\
+\x02\xdf\xb3\x3d\xf9\xce\x08\x06\x7f\x51\xf1\x78\x6a\xa6\xb4\xc7\
+\x70\xaf\x77\x15\x98\x6b\xf6\xf1\x1a\x65\xb2\xd3\x20\xbf\x00\x0f\
+\xcc\x1f\x78\xac\xe1\x0d\xd6\x68\xee\x60\x7b\xf2\x3d\x4b\xa5\xba\
+\x99\x49\xed\x31\x2a\xe0\xff\x0a\xcc\x35\xfb\x78\xa7\x29\x95\xd7\
+\x40\x7e\x01\x1e\x98\x3f\xf0\x58\xc3\x73\x3a\xed\xda\xcf\x42\xa1\
+\x1f\xd9\x9a\x7c\x3f\xf2\x7a\xb7\xf0\x3a\x79\x17\x3b\x9d\xed\x31\
+\x27\x12\x39\x00\xe6\x9a\x7d\xbc\xcb\xb5\xda\x87\x21\xbf\x00\x0f\
+\xcc\x1f\x78\xac\xe2\x0d\x36\x1a\x1f\x9a\x85\x27\xb8\x85\x78\xe2\
+\x4b\x56\x68\x3d\xb4\xfe\x2c\x92\xa8\xe0\x95\x4b\xa5\xbd\x99\xd4\
+\x1e\xc5\x7c\x7e\x31\x9b\xe2\x07\xbc\xf4\xf1\xee\x30\x1a\xdf\x80\
+\xfc\x02\x3c\x30\x7f\xe0\xb1\x8a\x17\x74\xda\x0c\xe3\xc2\xe1\x9f\
+\xd8\x96\x7c\x9f\xb0\xd9\xc6\x33\xad\x3d\x4c\xf9\xf9\x3e\x30\xc3\
+\xec\xe4\x3d\x69\xb7\x4f\x84\xfc\x02\x3c\x30\x7f\xe0\xb1\x8e\x77\
+\x91\x56\x7b\xdf\xa2\x48\xa4\x3d\x59\x2d\xc4\x34\x1b\x4b\x7e\xb3\
+\x48\x42\x9f\x17\xa6\xc0\x4a\x86\x37\x37\x1c\xfe\xcb\xc8\xe7\xdb\
+\x99\xd6\x1e\x51\xb1\xb8\x85\x0d\xf1\x03\x5e\xfa\x79\x6f\x3a\x9d\
+\x2b\x21\xbf\x00\x8f\x69\xe6\x9f\xf0\xd3\x7f\x10\xec\xec\xe5\x15\
+\xf1\x78\xb2\xa9\x81\xc0\xcf\x6c\x49\xbe\x43\x34\x9a\x47\x99\xd8\
+\x1e\xbd\x0a\x0b\xcf\x01\x33\xcc\x4e\xde\x67\x3e\xdf\x57\x90\x5f\
+\x80\xc7\x20\x1e\x31\xf5\x7f\xc2\x93\x04\x49\x20\xd8\xd9\xcb\xbb\
+\x58\xad\x7e\x60\x21\x9e\x08\xe3\x69\x01\x26\x94\x20\x67\x92\x84\
+\x3e\x2f\x48\x70\xfd\xee\xf0\xc6\xfe\x9d\x68\xc5\x4c\x6c\x8f\x33\
+\x8b\x8b\x6f\x62\x7a\xfc\x80\x97\x19\xde\x9c\x50\xe8\x20\x0f\x9f\
+\x89\x12\xf2\x0b\xf0\x18\x60\xfe\xb9\x09\x15\x00\xa4\xf7\x09\x4b\
+\x21\xd8\xd9\xcb\x93\xf1\x78\x45\xd3\x82\xc1\x5f\x98\x9e\x7c\xb1\
+\xa3\xec\x73\x99\xda\x1e\x43\x74\xba\x27\xc0\x0c\xb3\x97\x27\xe1\
+\xf1\x8a\x21\xbf\x00\x8f\x01\xe6\x4f\xbc\xef\x27\x76\x01\x80\xff\
+\x58\x84\x1f\xfd\x4b\x21\xd8\xd9\xcd\xbb\x4c\xa3\x79\x84\xc9\xc9\
+\x77\x84\xc3\xb1\x90\xc9\xf1\x1b\x6a\x32\xbd\x0b\x66\x98\xbd\x3c\
+\x87\x40\x10\x82\xfc\x02\x3c\x9a\xcd\x9f\x8f\xbf\xed\x37\x2f\xe6\
+\xd4\xff\xf8\x8f\x05\xf8\xd1\xbf\x84\xf4\x6e\x61\x08\x76\x96\xf2\
+\x0a\x78\x3c\xc5\xf4\x50\xe8\x37\x26\x26\xdf\x79\x91\xc8\x11\xbb\
+\x48\x14\x65\x72\xfc\x9e\xb1\xd9\xa6\x81\x19\x66\x2f\xaf\x52\x59\
+\x7c\x22\xe4\x17\xe0\xd1\xc8\x13\xe0\x3a\x5e\x00\xc4\xab\x14\x84\
+\xa4\x02\x40\x02\xc1\x06\xde\x95\x1a\xcd\xe3\x0b\xc3\xe1\x76\xb2\
+\x16\x60\x9a\x85\x25\xb8\x99\x24\xa1\xcf\x0b\x3a\xfc\x2e\x51\xa5\
+\xc2\xbb\xd5\x60\x78\x8d\xe9\xf1\x7b\xd7\xed\x5e\x97\xae\xfd\x4d\
+\x77\xfc\x80\x97\x79\x5e\x6f\x8d\xfa\x72\xc8\x2f\xc0\xa3\x89\x27\
+\xc2\xfd\x9c\x28\x00\x72\xe3\x5d\x23\x10\x90\x0a\x00\x31\x04\x1b\
+\x78\x68\x91\xf0\x78\xca\x19\x81\xc0\xef\x0b\xf0\xc4\x36\x1f\x13\
+\x4a\x90\x33\x48\x42\x9f\xe7\xe3\xff\x9e\xac\x52\xe1\x4d\xf1\xfb\
+\x7f\x42\xd7\x57\x99\x1e\xbf\xf1\x3e\xdf\x77\xe9\xd8\xdf\x74\xc7\
+\x0f\x78\xd4\xf0\x4e\xd7\xeb\xee\x86\xfc\x02\x3c\x1a\x78\x84\x87\
+\x13\x05\x40\x5e\xac\x53\xff\xb9\x78\x85\x40\x14\x00\x22\x08\x36\
+\xf0\xc8\xcb\xd5\x5a\xed\xd3\x4c\x4a\xbe\xa7\x15\x17\x5f\xcf\x82\
+\xf8\xe5\xcc\x0d\x87\x8f\x80\x19\x66\x2f\xef\x0a\xa3\x71\x18\xe4\
+\x17\xe0\x51\xcc\x23\xce\xde\x13\x05\x00\x3f\x96\xf9\xe7\xe0\xd5\
+\x41\x3e\xe9\x7a\x01\x04\x1b\x78\xff\xb3\xa0\xb7\xeb\x4d\x0f\x04\
+\xfe\x64\x42\xf2\x1d\xe9\x72\x6d\xc4\x36\x29\x97\xe9\xf1\x2b\xe6\
+\xf1\xb4\x60\x86\xd9\xcd\xbb\xdd\x62\xf9\x18\xf2\x0b\xf0\x28\xe6\
+\x49\x49\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\x2c\x41\x10\
+\xec\xac\xe3\x0d\x31\x19\x87\x33\x21\xf9\x96\x88\xc5\xad\x6c\x88\
+\x9f\x43\x24\x2a\x01\x33\xcc\x6e\xde\xb3\x76\xfb\x2c\xc8\x2f\xc0\
+\xa3\x98\x47\x14\x00\xa2\x98\x7e\x8e\xaf\x94\x43\x7a\x46\x10\xcc\
+\x1f\x78\x5d\xf2\xdc\x7a\xad\x7b\x52\x38\xbc\x9f\xce\xe4\xfb\x88\
+\xc5\x32\x96\x2d\xf1\xab\x2c\x28\xe8\x0b\x66\x98\xdd\x3c\xfc\x6c\
+\x15\xe4\x17\xe0\x51\xc9\x93\x26\x74\x0f\x1f\xa9\x00\xc8\x05\xf3\
+\x07\x5e\x22\xbc\xab\x4d\xa6\x57\x67\x60\x89\x6d\x5e\x28\xd4\x3e\
+\x3f\x05\xa1\xf5\xd0\xfa\xd3\x49\x4a\x94\x37\x23\x18\xdc\xaf\xe6\
+\xf3\x2d\x6c\x89\x5f\x5f\x99\xec\xa2\xee\xec\x6f\xba\xe3\x07\x3c\
+\xea\x79\x13\x7c\xbe\xff\x42\x7e\x01\x1e\xc5\xbc\xc4\x9e\xde\x23\
+\x15\x00\x60\xfe\xc0\x4b\x88\x67\x37\x19\xdc\x33\x83\xc1\xbf\xe8\
+\x48\xbe\x17\xab\xd5\x0f\xb2\x29\x7e\x83\x55\xaa\xbb\xc0\x0c\xb3\
+\x9b\x37\x37\x14\x3a\xda\xd9\xfd\x2a\x90\x5f\x80\x47\x3b\x2f\x55\
+\xe3\x87\x60\x67\x37\xef\x46\xbd\x7e\x04\xd5\xc9\xf7\x53\x8f\xe7\
+\x0b\x6c\xb3\x44\x6c\x8a\xdf\x55\x26\xe3\xcb\x60\x86\xc0\x93\x0b\
+\x85\x7a\xc8\x2f\xc0\x83\x57\x04\x03\x8f\x13\x3c\x9d\x40\x60\x9c\
+\x15\x0c\x1e\xa0\x32\xf9\xb6\x4a\xa5\x67\xb2\x2d\x7e\x43\xad\xd6\
+\xb1\x60\x86\xc0\xf3\x88\x44\xa5\x90\x5f\x80\x07\xe6\x0f\x3c\xce\
+\xf0\x6e\x31\x18\x5e\xa5\x2a\xf9\xbe\x68\xb3\xcd\x65\x63\xfc\x9e\
+\x76\x3a\x16\x81\x19\x02\xaf\xba\xa0\xa0\x3f\xe4\x17\xe0\x81\xf9\
+\x03\x8f\x33\x3c\x8d\x40\x60\x9e\x19\x08\x1c\x9c\x87\x27\xc5\xce\
+\x34\x17\xd3\xf4\x0e\xc9\x12\x69\x6e\x8c\x75\x3a\x6a\x4e\x20\x70\
+\xd8\x99\x97\x17\x66\x63\xfc\xde\xf4\x78\x76\x24\xbb\xbf\xe9\x8e\
+\x1f\xf0\xe8\xe7\xf5\x93\xcb\x2f\x81\xfc\x02\x3c\x30\x7f\xe0\x71\
+\x8a\x77\x9b\x5e\xff\xe6\xbc\x60\xb0\xbd\x33\xcd\xc5\x34\x1d\x25\
+\xcc\x0e\x9a\xdb\xc5\xef\xbb\xd2\x8d\x5a\xed\x08\xb6\xc6\xef\xb3\
+\x40\xe0\xd7\x64\xf7\x37\xdd\xf1\x03\x1e\xfd\xbc\xf3\x54\xaa\xa1\
+\x90\x5f\x80\x07\xe6\x0f\x3c\x4e\xf1\x54\x7c\xbe\x6d\x96\xdf\x7f\
+\x28\x53\xc9\x77\xbc\xd7\xfb\xa3\x94\xc7\x93\xb3\x31\x7e\x3e\x9f\
+\x5b\x05\x66\x08\x3c\xa4\xeb\x75\xba\x61\x90\x5f\x80\x07\xe6\x0f\
+\x3c\xce\xf1\xee\x34\x18\xde\xc9\x54\xf2\x3d\x59\xa1\xb8\x9a\xad\
+\xf1\xd3\x89\xc5\x76\x30\x43\xe0\x21\x3d\x60\x34\x7e\x0a\xf9\x05\
+\x78\x4c\x31\xff\x84\x9f\xfe\x83\x60\x03\x2f\xde\xa2\xcf\xcf\x77\
+\xce\xf1\xfb\x0f\xa7\x3b\xf9\xbe\xe9\x70\xac\x33\x1a\xf5\x79\x6c\
+\x8d\x5f\x40\x20\xa8\x06\x33\x04\x1e\xd2\x30\xab\x75\x01\xe4\x17\
+\xe0\x31\x80\x47\x4c\xfd\x9f\xf0\x24\x41\x12\x08\x36\xf0\xe2\x2d\
+\x43\x8d\xc6\xf7\xe6\x60\x89\x6e\x1a\x96\x1c\x3b\x6a\x0e\x9e\x48\
+\x93\x55\x89\x54\xda\xcc\xe6\xf8\xd5\x4a\x24\x27\x27\xb3\xbf\xe9\
+\x8e\x1f\xf0\x98\xc3\xfb\xc0\xe5\xda\x01\xf9\x05\x78\x0c\x30\xff\
+\xdc\x84\x0a\x00\xd2\xfb\x84\xa5\x10\x6c\xe0\xc5\x5b\x0c\x42\xa1\
+\x77\x4a\x28\x74\x24\x5d\xc9\xf7\x5e\x93\xe9\x53\xb6\xc7\xef\x24\
+\x85\xe2\x4a\x30\x43\xe0\x21\x4d\xf2\xfb\x7f\x83\xfc\x02\x3c\x9a\
+\xcd\x9f\x78\xdf\x4f\xec\x02\x00\xff\xb1\x08\x3f\xfa\x97\x42\xb0\
+\x81\x97\x08\xef\x4e\x8b\x65\xf4\x34\x94\x38\x71\xcd\x09\x04\xda\
+\xe7\xa6\xa0\x29\x5e\xef\x1f\x6e\x83\xce\xc7\xf6\xf8\x5d\xa8\x54\
+\x3e\x90\xc8\xfe\xa2\x38\x91\xe3\xd6\xdd\xf8\x01\x8f\x99\x3c\xaf\
+\xd7\xa5\x83\xfc\x02\x3c\x9a\xcc\x9f\x8f\xbf\xed\x37\x2f\xe6\xd4\
+\xff\xf8\x8f\x05\xf8\xd1\xbf\x84\xf4\x6e\x61\x08\x36\xf0\x62\xf2\
+\xbc\x6a\x55\xe5\x94\x60\xf0\x48\x77\x93\xe5\x60\xbd\xee\x11\x2e\
+\xc4\xef\x26\x9d\xee\xd5\x6c\x37\xc3\xcf\x3d\x9e\xef\x1f\x33\x9b\
+\x27\xcc\x0e\x04\x8e\x66\x7b\x31\xe1\xd5\x69\x23\x90\x5f\x80\x47\
+\x03\x4f\x80\xeb\x78\x01\x10\xaf\x52\x10\x92\x0a\x00\x09\x04\x1b\
+\x78\x89\xf2\xee\x32\x9b\x3f\xeb\x4e\xb2\x1c\xe9\x76\xef\x0b\x85\
+\xfc\x6a\x2e\xc4\xef\x61\xb3\x79\x5c\xb6\x9a\xff\x64\xaf\xf7\xb7\
+\x0b\x94\xca\xfb\x15\x3c\x5e\x01\x8a\x85\x43\x24\x2a\x79\xc2\x6c\
+\x9e\x96\xcd\x67\x12\x22\xca\xe2\xde\x90\x5f\x80\x47\x31\x4f\x84\
+\xfb\x39\x51\x00\xe4\xc6\xbb\x46\x20\x20\x15\x00\x62\x08\x36\xf0\
+\x92\xe1\xd9\x05\x82\xf0\x2c\xec\x68\x6f\x0e\x9e\x00\x13\x15\x76\
+\x84\xd8\x3e\x15\x4b\x92\x0d\x1a\xf5\x79\x5c\x89\xdf\x08\xbb\x7d\
+\x59\xbc\xfd\xed\xa8\xd9\x49\xc6\x8d\x69\xbc\x19\x7e\xff\x81\x6b\
+\x35\x9a\x17\x24\x3c\x9e\xb2\xb3\xf8\x95\xaa\x54\x03\x9e\x77\x3a\
+\x57\x72\x65\x7f\x93\xe1\x35\xa8\x55\x83\x21\xbf\x00\x8f\x42\x1e\
+\xe1\xe1\x44\x01\x90\x17\xeb\xd4\x7f\x2e\x5e\x21\x10\x05\x80\x08\
+\x82\x0d\xbc\x54\x78\x0f\x1a\x8d\x63\x53\x49\x96\x8f\xd9\xed\xf3\
+\xb8\x14\xbf\x51\x2e\xd7\xbe\x6c\x31\x7f\xac\xe8\x3b\x72\xa7\xc1\
+\x30\x52\xcd\xe7\x5b\x12\x89\x5f\xa3\x5a\x75\xee\x3b\x0e\xc7\x96\
+\x6c\x31\x7f\xa4\x93\x8b\x8b\xaf\x81\xfc\x02\x3c\x8a\x78\xc4\xd9\
+\x7b\xa2\x00\xe0\xc7\x32\xff\x1c\xbc\x3a\xc8\x27\x5d\x2f\x80\x60\
+\x03\x2f\x25\x9e\x53\x24\x8a\x24\x7a\x16\x80\x48\x96\x13\x03\xfe\
+\x43\x7e\x95\xb2\x9a\x4b\xf1\x9b\xee\xf7\xff\x95\x0d\xe6\xff\xa8\
+\xd9\x3c\xd1\x9a\x97\x17\x4c\x21\x7e\x39\x7d\x8b\x8a\x2e\xfd\xd8\
+\xed\xfe\x92\xeb\xe6\x8f\xbe\xbf\x48\xad\x7e\x08\xf2\x0b\xf0\x28\
+\xe2\x49\x49\x05\x80\x20\xde\x4d\x7f\xe4\x02\x80\x9f\xf0\x2c\x41\
+\x10\x6c\xe0\x75\xc1\x7b\xd8\x64\x1a\x37\xc7\xef\x6f\x8f\xa5\xd9\
+\x98\xa6\x1e\x4b\x98\x81\xf6\x21\x06\xc3\xab\x5c\x8a\x9f\x8c\xc7\
+\x2b\x8a\xb5\xbf\xc7\x85\x4c\x23\x4e\x9c\x12\x89\x1f\x1d\xbc\x61\
+\x16\xcb\xc2\x90\x40\x50\x97\x86\xf8\x09\xce\x54\x28\x6e\x1d\xe7\
+\x76\xff\xc8\xe4\xfd\xed\x2e\xef\x16\x9d\xee\x75\xc8\x2f\xc0\xa3\
+\x88\x47\x14\x00\xa2\x98\x7e\x8e\xaf\x94\x43\x7a\x46\x10\xcc\x1f\
+\x78\xdd\xe6\xa1\xf7\x9f\xcf\xc6\x93\x62\x67\x9a\x85\x69\x0a\x7a\
+\xe4\x0f\x4b\x92\x1f\xf9\x7c\x3f\xf8\x6c\x16\x13\x97\xe2\x67\xce\
+\xcf\xf7\x76\xb5\xbf\xc7\x85\x4e\x9d\xc7\x88\x51\xa2\xf1\xa3\x9a\
+\xf7\xa6\xc3\xb1\xa1\xaa\xa0\x60\x60\xba\xe3\x57\x84\xd5\x4d\x97\
+\xaa\x54\x8f\x4e\xf1\x78\xfe\x60\xd2\xfe\xa6\x8b\xf7\xb0\xd1\x38\
+\x01\xf2\x0b\xf0\x28\xe2\x49\x13\xba\x87\x8f\x54\x00\xe4\x82\xf9\
+\x03\x2f\x9d\xbc\x47\x8d\xc6\x89\x89\x24\xcb\xfe\x1a\xf5\x0d\x5c\
+\x8b\x5f\x58\x2c\x6e\xe6\x9a\xf9\x7f\xe0\x74\xee\xe9\x55\x58\x78\
+\x3e\xb6\x7b\x27\x64\x32\x7e\xc5\x3c\x9e\xf6\x46\xad\xf6\xe5\xe9\
+\x5e\xef\x21\x2e\xc5\x6f\x84\xd5\xba\x02\xf2\x0b\xf0\x28\xe2\x25\
+\xf6\xf4\x1e\xa9\x00\x00\xf3\x07\x5e\x5a\x79\x4e\xa1\xb0\x22\x5e\
+\xb2\x1c\xe6\x70\xac\xab\xab\xab\x2a\xe4\x5a\xfc\x9a\xa5\xd2\xb3\
+\xb9\x62\x5e\x63\xdd\xee\xef\x4f\x91\xcb\xaf\xc7\x76\x2b\x9f\xca\
+\xf6\x30\xf0\xf9\x8e\xa1\x06\xfd\xc7\x93\x03\x81\xa3\x5c\x38\x73\
+\x32\xca\xe5\xfa\x12\xf2\x0b\xf0\x18\xc5\x4b\xd5\xf8\x21\xd8\xc0\
+\x4b\x64\x79\xc2\x64\x9a\x1a\x2b\x59\x46\x8b\x15\xbd\xb8\x18\xbf\
+\x53\xe5\xf2\x1b\xd9\x6e\xfe\x13\xbd\xde\x5f\xc9\xcf\xf2\xd3\xd5\
+\x1e\x7e\x8d\xba\xe1\x21\xab\x75\x16\x9b\xcd\x1f\x69\xba\xd7\x7b\
+\x00\xdb\xad\x1e\x90\x5f\x80\x07\xaf\x08\x06\x5e\x56\xf0\x3c\x02\
+\x41\x75\x57\xc9\xf2\x8e\xbf\xe7\xfb\xe7\x64\xfc\x2e\x53\xab\x9f\
+\x64\xab\xf9\x23\xa3\xba\xba\x93\x67\xf9\xe9\x6e\x8f\x72\xa5\x72\
+\xc0\x4b\x36\xdb\x32\x36\x9a\x3f\x21\xac\x92\x52\x40\x7e\x01\x1e\
+\x98\x3f\xf0\xb2\x86\xf7\xa4\xd9\x3c\x73\x0a\x96\xfc\x26\x07\xfe\
+\xd1\x58\xaf\xe7\x0f\x55\x5e\x9e\x91\xab\xf1\xbb\xc9\x68\xfc\x90\
+\xbc\xbf\x68\xff\x67\xfa\x7c\xed\xb3\x52\x10\x5a\xaf\x63\xfc\x32\
+\xc1\x9b\xe1\xf3\x1d\xb9\x5d\xa7\xeb\xf4\x59\x7e\x26\xb5\x47\xbd\
+\x4c\x76\xea\x3b\x76\xfb\x56\xa6\xc5\x2f\x11\x9e\x31\x2f\xcf\x0f\
+\xf9\x05\x78\x60\xfe\xc0\xcb\x1a\x5e\xb8\x58\xd1\xab\x63\xb2\x3c\
+\x5b\xa9\x1c\xca\xe5\xf8\x3d\x68\xb5\xce\x64\x93\xf9\x3f\x64\x30\
+\x74\xf9\x2c\x3f\x43\xdb\x23\xa7\x7f\x51\xd1\x65\xa3\x5c\xae\xaf\
+\xd8\x62\xfe\x48\x25\x62\x71\x2b\xe4\x17\xe0\x81\xf9\x03\x2f\xab\
+\x78\x8f\x59\xad\x0b\x88\x64\x39\xd2\x6e\xdf\x89\xfd\x33\x9f\xcb\
+\xfb\xfb\x92\xc3\xb1\x81\x0d\xe6\xff\x94\xcd\xb6\xc4\x2f\x12\x35\
+\xb0\xb8\xff\x09\xcf\x50\x28\x6e\xfb\xcc\xe5\xfa\x3f\xa6\x9b\x3f\
+\x52\xab\x54\x7a\x2e\xe4\x03\xe0\x81\xf9\x03\x2f\xab\x78\xa5\x6a\
+\x55\x7f\x22\x59\xd6\x48\x24\x83\xb8\xbe\xbf\xef\x7b\x3c\xdf\x31\
+\xd9\xfc\x87\x3b\x9d\x9b\x6b\x54\xca\x33\xb9\xd2\xff\x64\x3c\x5e\
+\xe1\xa5\x2a\xd5\x63\x93\xdd\xee\x3f\x98\x6a\xfe\x48\xa7\x29\x14\
+\x37\x43\x3e\x00\x1e\x98\x3f\xf0\xb2\x8e\xf7\x8c\xd9\x3c\xff\x31\
+\x93\x69\x2a\xd7\xf7\xb7\xba\xba\x42\x3e\xd9\xef\x3f\xc2\x44\xf3\
+\x7f\xd3\xe5\xda\xd7\x4b\xa3\x1e\xc2\xc5\x47\x2f\xd1\x82\xe6\x10\
+\xb8\x5e\xa7\x7b\x75\x82\xcf\x7b\x88\x89\xf7\x60\x5c\xae\x56\x3f\
+\x05\xf9\x00\x78\x74\x9b\x7f\xc2\x4f\xff\x41\xb0\x81\x97\x2e\x5e\
+\x58\x2c\x6e\x32\xe7\xe7\x7b\xb8\xbe\xbf\x6e\x9d\xc6\x31\x03\x37\
+\x8e\x64\x85\xd6\xc3\x8a\x87\xf6\x49\x81\x7f\x34\xf9\xef\x1b\xf4\
+\xba\xc5\xfb\xd0\xeb\xfd\xef\x49\x1a\xcd\x1d\xd1\x68\x58\x99\x0d\
+\xfd\xcf\xa3\xd5\x44\xef\xb0\x98\xc7\x4e\x08\xf8\x8f\xa6\x23\x7e\
+\xe9\x6a\x8f\xdb\xf5\xfa\xf7\x21\x1f\x00\x8f\x46\x1e\x31\xf5\x7f\
+\xc2\x93\x04\x49\x20\xd8\xc0\x03\x5e\xe2\x3c\x57\x81\x24\x3a\xd3\
+\xeb\x6d\x4f\x56\x33\x30\x4d\xc6\x4c\x62\x92\xff\x1f\xa1\xcf\x33\
+\x52\x60\x11\xbc\xd1\x1e\xf7\x6f\xe7\x6a\xb5\x8f\xb9\xdd\x4e\x7d\
+\x36\xb6\x47\x48\xad\x6a\x78\xdc\x64\x9a\xc1\x94\xf6\x78\xd8\x62\
+\x99\x03\xe3\x0d\x78\x34\x9a\x7f\x6e\x42\x05\x00\xe9\x7d\xc2\x52\
+\x08\x36\xf0\x80\x97\x38\xaf\x5c\x24\xea\x43\xb7\xd9\x4c\x71\xbb\
+\x0f\x5c\xa1\xd7\x8f\x70\x98\x0c\x76\x68\x5f\x1e\x0f\xdd\x7d\x3f\
+\xdc\x62\x59\x41\xa7\xf9\xa3\xf5\x87\x3b\xec\x9b\xa1\x3d\x80\x47\
+\x93\xf9\x13\xef\xfb\x89\x5d\x00\xe0\x3f\x16\xe1\x47\xff\x52\x08\
+\x36\xf0\x80\x97\x38\xaf\xb7\x4c\x76\x21\x5d\xe6\x8f\xad\x73\xe4\
+\x56\x9d\x6e\xa4\x4b\xaf\xf3\x43\x7b\xfc\x9b\x57\x27\x93\x9d\xf6\
+\xb6\xdd\xbe\x8d\x0e\xf3\x47\x9c\x0f\xdc\xee\x1f\xa0\x3d\x80\x47\
+\x83\xf9\xf3\xf1\xb7\xfd\xe6\xc5\x9c\xfa\x1f\xff\xb1\x00\x3f\xfa\
+\x97\x90\xde\x2d\x0c\xc1\x06\x1e\xf0\x12\xe0\x9d\xa9\x50\xdc\x41\
+\x87\xf9\x3f\x68\x30\x8c\x77\x08\x04\x21\x68\x8f\xb8\xbc\xdc\xbe\
+\x32\xd9\x65\x1f\x3b\x1c\x5f\x53\x69\xfe\x48\x13\xfc\xbe\xa3\xe5\
+\xe5\xd1\x22\x68\x0f\xe0\x51\xc8\x13\xe0\x3a\x5e\x00\xc4\xab\x14\
+\x84\xa4\x02\x40\x02\xc1\x06\x1e\xf0\x12\xe7\x5d\xa5\x56\x3f\x3f\
+\x03\x4f\xfe\xb1\x34\x1d\xd3\x24\xcc\x1c\x26\xfa\xff\x11\xfa\x3c\
+\x3d\x81\x75\xc9\x7a\xde\x62\x59\xe8\x13\x08\xea\xa0\x3d\x92\xe6\
+\x09\xb1\x62\xed\xf6\xb1\x4e\xe7\xff\xa5\xb3\x3d\xe2\xb5\x2f\x97\
+\x67\xc0\x04\x1e\xe3\x78\x22\xdc\xcf\x89\x02\x20\x37\xde\x35\x02\
+\x01\xa9\x00\x10\x43\xb0\x81\x07\xbc\xe4\x78\x77\xe9\xf5\xa3\xa8\
+\x30\xff\xd7\xac\xd6\xf5\xd5\x05\x05\x03\xa0\x3d\xba\xc7\x93\xf1\
+\x78\x45\x97\xa8\xd5\x4f\x8c\xf5\x79\xff\xcc\xb4\xf9\xa3\xef\xed\
+\x22\x51\x29\xb4\x07\xf0\x28\xe0\x11\x1e\x4e\x14\x00\x79\xb1\x4e\
+\xfd\xe7\xe2\x15\x02\x51\x00\x88\x20\xd8\xc0\x03\x5e\xf2\xbc\xa7\
+\x4d\xa6\xb9\x33\x3c\x9e\xf6\xae\x34\x1d\xd3\x24\x9f\xb7\x7d\x22\
+\x49\xe8\xf3\xf4\x18\xeb\x90\xf5\x9e\xdd\xbe\xa7\x67\x41\xc1\x79\
+\xd8\x9f\x3a\x01\xda\x23\x7d\x3c\x97\x4e\xeb\xb9\x5a\xaf\x7f\xfb\
+\x73\x8f\xe7\x50\x32\xed\x91\x6c\xfb\x56\x14\x14\xf4\x87\xf6\x00\
+\x5e\x86\x79\xc4\xd9\x7b\xa2\x00\xe0\xc7\x32\xff\x1c\xbc\x3a\xc8\
+\x27\x5d\x2f\x80\x60\x03\x0f\x78\x29\xf0\xde\xb2\xd9\xb6\x66\xc2\
+\xfc\x47\x3b\x9d\xdf\x9f\x5c\x58\x78\x1d\xf6\x27\xf2\xa1\x3d\x32\
+\xc7\x73\xa9\x55\xd1\xa1\x06\xfd\xa7\xd3\x3c\x9e\xa3\xe9\x36\x7f\
+\xa4\x3e\x32\xd9\x25\xd0\x1e\xc0\xcb\x30\x4f\x4a\x2a\x00\x04\xf1\
+\x6e\xfa\x23\x17\x00\xfc\x84\x67\x09\x82\x60\x03\x0f\x78\xff\x5a\
+\x3e\x77\xb9\x7e\x4e\xa7\xf9\x8f\x73\x3a\x7f\x1d\x5c\x5c\x7c\x1f\
+\x86\x96\x40\x7b\x50\xc7\x73\x0b\x85\x65\x4f\x1a\x0c\x33\xd3\x69\
+\xfe\x48\x67\xcb\xe5\x43\xa1\x3d\x80\x97\x61\x1e\x51\x00\x88\x62\
+\xfa\x39\xbe\x52\x0e\xe9\x19\x41\x30\x7f\xe0\x01\x2f\x75\x9e\x60\
+\x3a\x6e\x06\x64\x61\x47\x93\xc7\x0c\x61\x02\x49\xe8\xf3\xb4\x4e\
+\x7e\x4b\x68\xb2\xcb\x75\x60\x88\x5a\xfd\x3c\xe6\xfa\x4a\x68\x0f\
+\xfa\x78\x11\xb1\xb8\xe7\x4b\x16\xcb\xca\xe9\x31\xda\x2a\x99\xf6\
+\xbd\x5a\xa5\x1a\x06\xed\x01\xbc\x0c\xf3\xa4\x09\xdd\xc3\x47\x2a\
+\x00\x72\xc1\xfc\x81\x07\xbc\xee\xf1\xd4\x7c\xbe\xa5\xbb\xe6\x8f\
+\x7d\x7f\xe4\x56\xad\xf6\x5d\x8d\x40\x60\x86\xf6\x60\x0c\xaf\x47\
+\xa3\x44\x72\xfa\x9b\x36\xdb\xb6\xee\xb6\xef\x3d\x3a\xdd\xa7\xd0\
+\x1e\xc0\xcb\x30\x2f\xb1\xa7\xf7\x48\x05\x00\x98\x3f\xf0\x80\xd7\
+\x4d\x9e\x47\x28\xac\xea\x8e\x39\xa0\x67\xf9\x6d\x79\x79\x01\x68\
+\x0f\xc6\xf2\x72\xfb\xcb\x64\x97\x7f\x64\xb7\x7f\x9d\xea\x99\x9d\
+\x67\xcd\xe6\x05\xd0\x1e\xc0\x63\x04\x2f\x55\xe3\x87\x60\x03\x0f\
+\x78\xff\x5e\xaa\x25\x92\x93\xa6\xbb\xdd\xed\x48\xd3\x30\x4d\xf4\
+\x7a\xda\x27\x78\xbd\xc7\x85\x3e\x4f\xc3\xff\x9d\xac\x67\x4d\xa6\
+\x05\x7e\x81\xa0\x16\xda\x83\x35\x3c\xe1\x99\x72\xf9\x9d\x1f\xb9\
+\xdd\x3f\x27\xd2\xbe\x64\xbd\x6d\xb1\xec\x80\xf8\x01\x0f\x5e\x11\
+\x0c\x3c\xe0\x71\x8c\x37\xa0\xa8\x68\x48\x32\xe6\xff\x8a\xd9\xbc\
+\xbe\x1c\x7f\x96\x1f\xe2\xc7\x3e\x9e\xd7\x6a\x36\x5f\xa4\xd5\x3e\
+\x3f\xda\xe3\xd9\x9f\x88\xf9\x23\x8d\x73\x38\x7e\x83\xf8\x01\x0f\
+\xcc\x1f\x78\xc0\xe3\x18\xef\x3c\x85\xe2\xfe\xa9\x58\x92\x9f\x80\
+\x99\xc1\x78\xcc\xf4\x09\xa1\xcf\x53\xf1\xc2\x00\x69\xa4\xcd\xb6\
+\xa7\x99\xf4\x2c\x3f\xc4\x8f\xdd\x3c\xbb\x46\xe5\xbe\x5e\xa3\x79\
+\x7d\x92\xd3\x79\x68\x1a\xa9\x9d\xbb\x12\x86\x11\x43\xfc\x80\xc7\
+\x56\xf3\x27\xbf\x23\x40\x9a\x86\xe9\x82\x81\x07\x3c\x4e\xf0\xae\
+\xd5\x68\x5e\x9d\xe0\xf1\x1c\x33\xfc\xe3\xf2\xfc\x63\xfe\xa3\x1c\
+\x8e\xef\x3a\x3e\xcb\x0f\xf1\xe3\x0e\x4f\x97\x9f\xef\x1e\xaa\xd5\
+\x7e\x1a\xaf\x00\x50\xf2\xf9\x76\x88\x1f\xf0\xe8\xe6\xa5\xf2\xc7\
+\xc9\xef\x08\x90\xa4\x61\xba\x60\xe0\x01\x8f\x33\xbc\xa1\x26\xe3\
+\x84\x63\x05\x00\xae\x89\xb8\xf9\x7f\xe6\x70\xfc\x3a\x58\x2e\xff\
+\xd7\xb3\xfc\x10\x3f\x6e\xf2\xd0\x1c\x02\x8f\x19\x0c\x33\xbb\x2a\
+\x00\xd0\xfd\x1e\x10\x3f\xe0\xd1\xc9\x4b\xe5\x8f\x8b\x48\xf3\x0b\
+\x8b\xd3\x30\x5d\x30\xf0\x80\xc7\x29\xde\x33\x16\xcb\xf2\x89\xb8\
+\xf1\x4f\x42\xff\x75\x3a\x0f\x0c\x51\x2a\x3b\x7d\x96\x1f\xe2\xc7\
+\x7d\x5e\xa9\x58\xdc\x36\xdc\x6c\x5e\xd5\xb1\x00\xa8\x2f\x28\x38\
+\x0d\xe2\x07\x3c\xba\x78\xc9\xfe\xf1\x1e\xa4\x77\x04\x08\x49\x2f\
+\x17\xe8\x01\x3c\xe0\x01\xef\x1f\xde\x9b\x36\xdb\xbe\x89\xe8\x1e\
+\x00\xb7\xfb\xc8\xcd\x6a\x75\x97\xcf\xf2\x43\xfc\xb2\x8a\xd7\xa3\
+\x41\x2c\x3e\xe3\x0d\xb3\x79\xfb\x54\x97\xab\x7d\x0a\xa6\xfe\xc5\
+\xc5\x37\x40\xfc\x80\x47\x07\x8f\x60\x26\xf3\xc7\xf9\xa4\x77\x04\
+\x08\xba\x39\x5d\x30\xf0\x80\xc7\x59\xde\x58\x97\x6b\xff\x3d\x46\
+\xc3\x44\x3b\x3f\x3f\x08\xf1\x03\x5e\x87\x25\xb7\x9f\x4c\x36\xe4\
+\x3d\xbb\xfd\xdb\xf3\x54\xaa\xc7\x20\x7e\xc0\xa3\x89\x97\x93\xe8\
+\x24\x41\x3d\x48\xef\x08\x20\x94\xd7\xcd\x3f\x0e\x3c\xe0\x71\x92\
+\xe7\x72\x39\xc4\xc1\x82\x82\x46\x88\x1f\xf0\x62\xf1\x74\x3a\x6d\
+\xa1\x57\x26\xab\x81\xf8\x01\x8f\x06\x5e\x6e\x42\x05\x00\xe9\xc7\
+\x79\x24\xe5\xa6\xe1\x8f\x03\x0f\x78\xc0\x03\x1e\xf0\x80\x07\x3c\
+\x7a\x78\x09\x15\x00\x39\x1d\xc5\xeb\xc6\x02\x3c\xe0\x01\x0f\x78\
+\xc0\x03\x1e\xf0\x18\xc1\xeb\x11\xaf\x5a\x38\x81\xa4\x1e\xdd\xfc\
+\xe3\xc0\x03\x1e\xf0\x80\x07\x3c\xe0\x01\x8f\x21\xbc\xff\x07\x58\
+\x8c\x20\x00\x50\x0c\xdc\x81\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x0b\x94\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\
+\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x0b\x34\x49\x44\x41\x54\x78\
+\xda\xed\x9c\x7f\x4c\x54\x57\x16\xc7\xbf\xf7\xcd\x9b\x01\x06\x10\
+\x66\x14\x6c\x14\xb7\xce\x16\x8d\xdd\x0d\x90\xd6\x5d\xac\x48\x6c\
+\x48\x19\xd2\x25\xa9\x2e\x76\x5b\x33\x92\x25\x0b\x46\xfb\xc3\xba\
+\xd4\x1a\xb7\xc5\x74\xb5\xb8\x35\x81\x62\xb6\x75\xa3\x35\xd6\xba\
+\x0a\x5d\x2d\x4d\x68\x75\x6b\xe2\x82\xd2\x4d\x6a\x87\x1a\xa9\xc4\
+\x76\x05\x64\x57\x2c\x3b\x80\x85\xaa\x45\x28\x85\x75\xe6\xfd\x38\
+\xfb\xc7\x76\xde\xce\x8f\x37\xfc\x9c\x1f\xc4\xbe\x93\x4c\x32\xf3\
+\xde\x9b\x7b\xee\xfb\xcc\xb9\xe7\x9c\x7b\xdf\xb9\xc3\x30\x03\x85\
+\x88\x0e\x02\xf8\xb1\xef\x71\xc6\x98\x75\xa6\xf5\x95\xcd\x30\x70\
+\x34\xa1\x4e\x33\xc6\x34\x80\x53\x00\x37\x13\x41\x72\x11\x84\xf6\
+\x22\x7d\x2f\x9e\xc7\x65\x59\x86\x2c\xcb\x00\x80\x9b\x37\x6f\x62\
+\x70\x70\xd0\xef\xb8\x27\x78\xb5\x36\xee\x6a\xa1\x00\x22\xcb\x32\
+\xb9\x5c\x2e\x22\x22\xba\x73\xe7\x0e\x15\x15\x15\x91\xd9\x6c\x26\
+\x93\xc9\x44\x5b\xb6\x6c\x51\xce\xb9\x5c\x2e\x92\x65\x39\x50\x33\
+\x07\x7f\x70\xf0\x3c\x61\xb4\xb5\xb5\x51\x49\x49\x09\x01\x20\x00\
+\xa4\xd7\xeb\x49\xaf\xd7\x2b\x9f\x9f\x7e\xfa\x69\xea\xe8\xe8\xf0\
+\xfa\xae\x0a\xcc\x83\x3f\x08\x78\x4e\xa7\x53\x79\xff\xde\x7b\xef\
+\xd1\xca\x95\x2b\x15\x50\x3a\x9d\x8e\x18\x63\xca\x67\xc6\x18\x71\
+\x1c\xa7\x7c\x7e\xf8\xe1\x87\xe9\xdd\x77\xdf\x55\x6d\xeb\xae\x85\
+\xa8\x66\x79\xb5\xb5\xb5\x34\x7b\xf6\x6c\x02\xe0\x05\x68\xbc\x97\
+\xfb\xda\xb8\xb8\x38\xaa\xa9\xa9\x21\x41\x10\xfc\x2c\xf1\xae\x05\
+\xe8\xb6\x96\xa8\xa8\x28\xc5\xda\x74\x3a\xdd\x84\xe1\x79\x5a\x29\
+\xcf\xf3\x04\x80\xcc\x66\x33\xb5\xb5\xb5\x45\x04\x20\x17\x01\x90\
+\x00\x00\xa7\xd3\x09\x8e\xe3\x20\x49\x12\x24\x49\x9a\x74\x3b\x92\
+\x24\x41\x14\x45\xf0\x3c\x8f\xc1\xc1\x41\xf4\xf5\xf5\xfd\xb0\xd2\
+\x18\x4f\x98\xd3\x6d\x83\xe3\x38\x44\x2a\x25\xe4\xa0\x89\x06\x50\
+\x03\xa8\x01\xd4\x00\x6a\xa2\x01\xd4\x00\x6a\x00\x35\x80\x9a\x68\
+\x00\x35\x80\x1a\x40\x0d\xa0\x26\x1a\x40\x0d\xa0\x06\x50\x03\xa8\
+\x89\x06\x50\x03\xa8\x01\xd4\x00\x6a\xa2\x01\x0c\xad\xf0\xa1\x56\
+\x10\xa9\x2a\x01\xb7\xde\x50\x97\xc0\xf1\x77\x0b\xb8\x40\x9c\x42\
+\x0d\x92\x0b\x32\xb4\xa5\xe3\xd5\xfc\x09\x82\x80\x63\xc7\x8e\x05\
+\x53\x27\x24\x49\x42\x63\x63\x23\x86\x86\x86\xfc\xf4\x79\x82\xfc\
+\x5e\x4c\x33\xd2\x17\x8c\x57\xf3\x47\x44\xb4\x6d\xdb\x36\x9a\x33\
+\x67\x8e\x52\x71\x85\x49\xd6\xc3\x8c\xf7\x9a\x3d\x7b\x36\x6d\xd8\
+\xb0\x81\x44\x51\x1c\xb7\x96\x70\xa6\x40\x3b\x38\x5e\xcd\x5f\x67\
+\x67\x27\xbd\xf0\xc2\x0b\xca\x4d\x1a\x0c\x86\x90\xc0\x63\x8c\x79\
+\xd5\x12\x3e\xf5\xd4\x53\x7e\xb5\x84\x81\x64\x46\x5a\xdb\x87\x1f\
+\x7e\x48\x79\x79\x79\x0a\x2c\x8e\xe3\x42\x02\x4e\x0d\xa4\x67\xb5\
+\x57\x4e\x4e\x0e\xbd\xff\xfe\xfb\x5e\xb5\x84\x11\xb5\x4a\x1a\x43\
+\x04\x41\xa0\xd3\xa7\x4f\x53\x52\x52\x52\x58\xa1\x61\x02\xb5\x84\
+\xc9\xc9\xc9\x54\x57\x57\x47\xe3\x49\xd8\xe0\x89\xa2\xa8\xfc\x92\
+\xdd\xdd\xdd\x54\x55\x55\x45\xf7\xde\x7b\x2f\x01\x20\x9e\xe7\xa7\
+\x54\xf3\x17\xaa\x97\x67\x2d\x61\x4a\x4a\x0a\x55\x56\x56\x52\x4f\
+\x4f\x8f\xd7\xbd\x84\x1c\xa2\xa7\x02\x49\x92\x88\x88\xe8\xd6\xad\
+\x5b\xb4\x7a\xf5\x6a\x32\x9b\xcd\x4a\x4d\xb3\xbb\xc3\x93\xa9\x38\
+\x0d\x07\x40\xdf\x1f\x34\x3e\x3e\x9e\x1e\x7f\xfc\x71\xea\xeb\xeb\
+\xf3\xba\xa7\x90\x41\xf4\x6c\xdc\xe1\x70\xd0\xea\xd5\xab\x95\xa0\
+\x90\x99\x99\x49\xb5\xb5\xb5\x4a\xa1\xf8\xaa\x55\xab\x94\xd2\xdd\
+\x50\x45\xdc\x89\xf8\x42\xf7\xfb\xc4\xc4\x44\x7a\xf2\xc9\x27\xa9\
+\xb3\xb3\x93\x88\x88\x8e\x1e\x3d\x4a\x4b\x97\x2e\x55\xce\xe7\xe7\
+\xe7\x53\x7f\x7f\x7f\xf8\x00\xbe\xf1\xc6\x1b\x4a\x69\x6d\x53\x53\
+\x93\x72\xdc\x33\x88\x38\x1c\x0e\xda\xbb\x77\x2f\xc5\xc7\xc7\x2b\
+\xa0\xc3\x61\x95\x3a\x9d\x8e\x0c\x06\x03\x01\x20\x93\xc9\x44\xfb\
+\xf7\xef\xa7\xee\xee\x6e\xd5\x3e\xda\xed\x76\x32\x1a\x8d\x04\x80\
+\x8e\x1e\x3d\x1a\x3e\x80\x07\x0e\x1c\xf0\xea\xf4\xe6\xcd\x9b\xe9\
+\x9b\x6f\xbe\x21\xdf\x61\xee\xf6\x2d\x35\x35\x35\x94\x94\x94\x14\
+\x16\xbf\xc8\x18\xa3\x79\xf3\xe6\x29\x55\xfc\xb2\x2c\x7b\x0d\x4f\
+\x49\x92\x68\x60\x60\x80\x4a\x4b\x4b\xbd\x82\xcc\xf1\xe3\xc7\xc3\
+\x0f\xd0\x37\xf7\x5a\xb7\x6e\x1d\x9d\x3d\x7b\xd6\x2b\x65\xf0\xdc\
+\x82\x70\xe2\xc4\x09\x7a\xec\xb1\xc7\xbc\xf6\x81\x4c\x67\x78\xfb\
+\xea\x5f\xb3\x66\x0d\x9d\x3a\x75\x2a\xa0\xfe\xfa\xfa\x7a\x5a\xbb\
+\x76\xad\xaa\xfe\x88\x01\x54\x4b\x1b\x2c\x16\x0b\x9d\x3c\x79\xd2\
+\xcf\x12\xdd\x11\xef\xab\xaf\xbe\x22\x9b\xcd\xa6\x5c\x3f\xd5\x2a\
+\x7d\xb7\xfe\x67\x9e\x79\x46\x09\x06\x9e\x7a\x7c\xa3\xeb\x64\x24\
+\x62\x00\x3d\x7d\xd0\xdc\xb9\x73\xa9\xba\xba\xda\xcb\x41\x0b\x82\
+\xa0\x58\xc5\xc0\xc0\x00\x6d\xda\xb4\x89\xe6\xcd\x9b\xe7\x97\xb7\
+\x8d\x95\xd3\x01\x20\x8b\xc5\x42\x5b\xb6\x6c\x51\x20\x39\x9d\x4e\
+\x12\x04\x41\xd1\x73\xfd\xfa\x75\x0a\x86\x84\x1d\xa0\xda\xcd\x26\
+\x27\x27\x53\x49\x49\x89\x72\x53\xbe\x37\x7b\xeb\xd6\x2d\xaa\xad\
+\xad\x55\xf2\x48\xbd\x5e\xef\x65\x95\x3c\xcf\x2b\x79\x5c\x46\x46\
+\x06\x9d\x3c\x79\x92\x86\x86\x86\xbc\x02\x83\x3b\x38\x78\x06\x8c\
+\x60\x4a\xd8\x01\x06\xca\xc3\x56\xad\x5a\x45\x0e\x87\xc3\x0b\xa0\
+\xe7\x30\x3f\x73\xe6\x0c\x2d\x59\xb2\xc4\xeb\x07\x88\x8a\x8a\xa2\
+\x8c\x8c\x0c\xba\x74\xe9\x92\x62\xc5\x9e\x81\x41\x14\x45\x6a\x6f\
+\x6f\x27\xab\xd5\x4a\xa1\x94\x88\x01\x74\x5f\xeb\xb6\x22\x00\x64\
+\xb5\x5a\xbd\x1c\xb7\xaf\xc3\xff\xe4\x93\x4f\xa8\xb8\xb8\x98\x36\
+\x6e\xdc\x48\x2d\x2d\x2d\x5e\xd7\x79\xa6\x22\x87\x0e\x1d\xa2\xec\
+\xec\x6c\x1a\x67\x96\x49\x8d\x03\x17\x68\xab\xe3\x75\xca\x6a\x2f\
+\xa6\xe4\xcf\xad\x64\x68\x79\x88\x0c\x2d\x0f\x51\xf2\xe7\x56\xca\
+\x6a\x2f\xa6\xad\x8e\xd7\xa9\x71\xe0\x42\xe8\x20\x4e\x17\x60\xa0\
+\x61\x6e\x32\x99\x68\xdf\xbe\x7d\x8a\x25\xfa\xce\x08\x7c\x03\x83\
+\x5b\x5e\x7d\xf5\x55\x8a\x8b\x8b\x53\xda\x0a\x24\x87\xfa\x3f\xa0\
+\x45\x97\x0b\x08\x17\x97\x4e\xe8\x95\x7a\xf9\x97\xf4\x56\xff\x07\
+\x13\x86\x18\xf1\x67\x22\x23\x23\x23\x78\xee\xb9\xe7\x60\x30\x18\
+\x50\x55\x55\x85\xee\xee\x6e\xe5\x9c\x28\x8a\x10\x45\x51\xf9\xdc\
+\xd9\xd9\x89\xb2\xb2\x32\x30\xc6\xf0\xf2\xcb\x2f\x43\x10\x04\x70\
+\x1c\xa7\xba\xe3\xa9\x7d\xf8\x1a\x32\xaf\x14\x61\x43\xef\x6e\x5c\
+\x75\x76\x4f\xb8\x3f\x9d\xce\x1e\x6c\xec\xdd\x8d\xcc\x2b\x45\xb8\
+\xfa\x9d\x63\xdc\xd5\xf6\x88\x02\x94\x65\x19\x2e\x97\x0b\x8c\x31\
+\xc8\xb2\x8c\x97\x5e\x7a\x09\x16\x8b\x05\xbb\x76\xed\xc2\xe8\xe8\
+\x28\x78\x9e\x07\xcf\xf3\x68\x68\x68\xc0\xce\x9d\x3b\xb1\x64\xc9\
+\x12\x54\x54\x54\x80\xe7\x79\x30\xc6\xe0\x74\x3a\x55\xf7\xd9\x9d\
+\x1e\xb0\xe3\xa1\xce\x62\x7c\x36\xda\x3e\xe5\xbe\x7d\x36\xda\x8e\
+\x15\xd7\xd6\xe3\xef\xb7\x3f\x8b\xec\x43\xa5\x89\x2e\xcb\x47\x47\
+\x47\x23\x2b\x2b\x0b\x87\x0f\x1f\x86\xd1\x68\x44\x6c\x6c\x2c\x56\
+\xae\x5c\x89\xe1\xe1\x61\x5c\xba\x74\x09\x44\x84\x82\x82\x02\x6c\
+\xda\xb4\x09\x2d\x2d\x2d\x10\x45\x51\xd5\xf2\x3e\xba\xdd\x8c\x82\
+\x7f\x6f\x83\x8b\x84\x69\xf7\xeb\xa6\x38\x88\x5f\x74\xfd\x16\xa7\
+\xe8\x8f\xc8\x33\x2f\xf7\xb3\x42\xc6\x18\x8b\x08\x40\x9d\x4e\x07\
+\xc6\x18\x44\x51\x84\xc9\x64\xc2\xf3\xcf\x3f\x8f\x75\xeb\xd6\x21\
+\x35\x35\x15\xa2\x28\xa2\xa7\xa7\x07\x09\x09\x09\x38\x77\xee\x1c\
+\x74\x3a\x9d\xf2\xbd\x8c\x8c\x0c\x34\x35\x35\xe1\xf2\xe5\xcb\xa8\
+\xa9\xa9\xf1\x6b\xf7\xea\x77\x0e\xd8\xba\xb7\x07\x05\x9e\x5b\x5c\
+\x24\xe0\x57\x8e\x17\x71\x5e\xff\x67\xfc\x34\x3e\x35\xf2\x16\xe8\
+\xde\xe2\x3a\x6b\xd6\x2c\xbc\xf9\xe6\x9b\xb0\xd9\x6c\xe0\x38\xce\
+\xcb\x1a\x75\x3a\x1d\x04\x41\x00\x63\x4c\xb9\xde\x7d\x8e\x31\x86\
+\xb4\xb4\x34\x54\x55\x55\xf9\xb5\x5d\xd8\xf3\x7b\xdc\x14\x07\x83\
+\xde\xe7\x61\x79\x14\xc5\xbd\xbb\xd0\x7c\x7f\x8d\x9f\x15\x86\xdc\
+\x07\x32\xc6\xbc\xac\x68\xc5\x8a\x15\x38\x7e\xfc\x38\x86\x86\x86\
+\x50\x58\x58\x18\x70\x28\x4e\x56\x0e\x7d\x7d\x62\x5a\x3e\x6f\x22\
+\x3e\xf1\xad\xaf\x3f\x08\xed\x63\x4d\x5f\x31\x18\x0c\xca\x63\xc7\
+\xa2\xa2\x22\xb4\xb7\xb7\xe3\xdc\xb9\x73\xb0\xd9\x6c\x20\x22\x10\
+\x11\x0c\x06\x43\x50\xf6\xfa\x56\xde\xa8\x0e\xf9\xe8\x79\xed\x46\
+\x4d\xe8\x01\x7a\xc2\x88\x8e\x8e\xc6\xfa\xf5\xeb\xf1\xed\xb7\xdf\
+\xa2\xba\xba\x1a\xa9\xa9\xa9\x8a\xb5\x31\xc6\x82\xb6\x49\xba\xf1\
+\xf6\x05\x5c\x73\xf5\x86\x1c\xe0\x35\x57\x2f\x1a\x6f\x5f\x08\x0d\
+\x40\x9e\xe7\x95\x9c\x6c\xd1\xa2\x45\xd8\xbf\x7f\x3f\xba\xba\xba\
+\xf0\xf6\xdb\x6f\x23\x2e\x2e\x0e\x92\x24\x41\xaf\xd7\x87\x64\x67\
+\x79\xfd\xf0\xf9\xb0\xf9\x70\x5f\x5d\x7c\xb0\xac\x4e\x14\x45\x58\
+\x2c\x16\x1c\x3e\x7c\x18\x39\x39\x39\x63\xfa\xc1\x60\xcb\x85\xd1\
+\xcb\x61\x03\xd8\x3c\xda\x3a\x7d\x0b\x74\x5b\x11\x11\xc1\x68\x34\
+\xa2\xa0\xa0\x00\x8d\x8d\x8d\xf8\xf2\xcb\x2f\x91\x93\x93\x03\x41\
+\x10\x10\x4e\xf9\x97\xb3\x27\x6c\xba\xfe\xe9\x33\xab\x99\x92\x05\
+\x12\x11\x78\x9e\x47\x42\x42\x02\xce\x9e\x3d\x8b\x07\x1e\x78\x00\
+\xc0\xff\xfe\x49\x43\xa7\xd3\x41\xaf\xd7\x87\x15\xe0\x90\x34\x1c\
+\x36\x5d\x83\x3e\xba\xa6\x64\x81\xe9\xe9\xe9\x58\xb8\x70\x21\x06\
+\x06\x06\xf0\xe0\x83\x0f\xe2\x89\x27\x9e\x40\x6b\x6b\x2b\x66\xd0\
+\xbf\xd2\x85\x2f\xaf\x9d\xca\x97\xb2\xb2\xb2\x70\xf5\xea\x55\x34\
+\x36\x36\xa2\xb0\xb0\x10\x75\x75\x75\x48\x4b\x4b\x43\x6e\x6e\x2e\
+\x8e\x1c\x39\xf2\xff\x2c\xde\xe5\x42\x38\x2a\x26\x12\x74\xf1\x61\
+\x03\x96\xe8\xa3\x6b\xc2\x00\xd5\xea\xeb\x72\x72\x72\xf0\xce\x3b\
+\xef\x60\x68\x68\x08\x6b\xd7\xae\xc5\xc7\x1f\x7f\x8c\x92\x92\x12\
+\xc4\xc6\xc6\xe2\xc0\x81\x03\x21\x8b\xba\xbe\xb2\x38\x6a\x41\xd8\
+\x00\xfa\xea\x9a\x94\x05\xfa\x42\x74\xaf\xa2\xc4\xc4\xc4\xa0\xb6\
+\xb6\x16\xfd\xfd\xfd\xd8\xb1\x63\x07\x52\x52\x52\xf0\xec\xb3\xcf\
+\x22\x36\x36\x16\xe5\xe5\xe5\xe8\xe8\xe8\xf0\x5a\xa2\x0a\xb6\x2c\
+\x33\xa6\x85\x0d\xa0\xaf\xae\x49\x0f\x61\x5f\x88\xee\xa0\x41\x44\
+\x48\x4a\x4a\x42\x79\x79\x39\x2e\x5e\xbc\x88\x86\x86\x06\x98\xcd\
+\x66\xbc\xf2\xca\x2b\xc8\xcc\xcc\x84\xd5\x6a\x45\x6b\x6b\x2b\x78\
+\x9e\x87\x20\x08\x7e\x05\x90\xd3\x91\x47\xe3\x97\x87\x0d\xa0\xaf\
+\xae\xa9\xa6\x31\x4c\xcd\x1a\xdd\x12\x13\x13\x83\xdc\xdc\x5c\xf4\
+\xf6\xf6\xa2\xa1\xa1\x01\xe9\xe9\xe9\xb0\xdb\xed\x48\x4b\x4b\xc3\
+\xb2\x65\xcb\xd0\xdc\xdc\x8c\x3b\x77\xee\x78\x45\xf5\xe9\x48\xae\
+\x69\x19\xee\x33\xa4\x84\x1c\xde\x7d\x86\x14\xe4\x9a\x96\x05\x6f\
+\x26\xa2\x06\xd2\x3d\x2b\x61\x8c\x41\x92\x24\xe4\xe5\xe5\xc1\x6e\
+\xb7\xe3\xfc\xf9\xf3\xd8\xba\x75\x2b\x9a\x9b\x9b\x91\x9d\x9d\x8d\
+\xec\xec\x6c\x54\x56\x56\x2a\x2b\x2c\x2e\x97\x0b\xb2\x2c\x83\x31\
+\x36\x26\x50\x49\x92\xe0\x72\xb9\xfc\x8e\xff\x2e\xb9\x28\xe4\x00\
+\xd5\x74\x04\x65\x2a\xc7\x3c\x24\xd0\xec\x23\x3d\x3d\x1d\x7b\xf6\
+\xec\x81\x28\x8a\xd8\xbe\x7d\x3b\xae\x5c\xb9\x82\xb2\xb2\x32\x70\
+\x1c\x87\xb2\xb2\x32\x65\xc9\x6a\xac\x39\x32\xc7\x71\xd0\xe9\x74\
+\x18\x19\x19\xf1\x3b\xb7\x71\xee\x1a\xfc\xdc\xf8\x93\x90\xc1\xfb\
+\x59\xcc\xfd\xd8\x38\x77\x4d\x58\x16\x13\x54\xad\x92\xe3\x38\xa5\
+\xf8\x7b\xf7\xee\xdd\x18\x1d\x1d\xc5\xde\xbd\x7b\xb1\x7c\xf9\x72\
+\x54\x54\x54\xc0\x68\x34\x62\xf3\xe6\xcd\xb0\xdb\xed\x98\x35\x6b\
+\x96\xaa\x6b\xa8\xaf\xaf\x47\x51\x51\x11\xcc\x66\xb3\x2a\xe4\xbf\
+\x2c\xd8\x85\x24\x3e\x31\xe8\xf0\xe2\x39\x23\x8e\x2c\xd8\xa9\x7e\
+\xbf\xa1\x36\xfb\xf1\x1e\x05\x0a\x82\x80\x2f\xbe\xf8\x02\xa5\xa5\
+\xa5\xf8\xf4\xd3\x4f\x11\x1d\x1d\x8d\x7b\xee\xb9\x07\x7d\x7d\x7d\
+\x70\x3a\x9d\xca\x90\x4e\x4f\x4f\x47\x5b\x5b\x1b\x24\x49\x82\xd5\
+\x6a\xc5\xbe\x7d\xfb\xb0\x78\xf1\x62\xbf\xf6\x3e\xba\xdd\x8c\xfc\
+\xae\xd2\xa0\xad\x4a\x1b\x98\x1e\x7f\x5d\xb8\x07\x8f\x9a\x57\xa8\
+\x1a\x4b\x38\x16\x54\xd9\x58\x7b\x34\x18\x63\xca\x52\x7d\x47\x47\
+\x07\x72\x73\x73\x31\x32\x32\x02\xa7\xd3\xe9\x15\x60\x6e\xdc\xb8\
+\x01\x9b\xcd\x06\x87\xc3\x81\x33\x67\xce\xc0\x62\xb1\xa8\x3e\x50\
+\x7a\xc4\x94\x89\x13\x0b\xab\x10\xc7\xc5\x4c\xbb\xef\x73\xf8\x44\
+\xfc\xcd\xf2\x27\x55\x78\x61\xb3\xc0\x89\x5a\xa4\x2c\xcb\xca\xd2\
+\x7e\x57\x57\x17\xea\xea\xea\xb0\x63\xc7\x0e\x18\x8d\x46\x54\x54\
+\x54\x20\x3f\x3f\x1f\xf3\xe7\xcf\xf7\xbb\x36\x90\xb4\x0d\x77\xe2\
+\x37\x3d\xe5\xb8\xf8\x9f\x2b\x53\xf6\x79\xc7\x7e\xf4\x07\x2c\x8e\
+\x5b\x38\x66\x3a\x17\xd1\xc9\xeb\x58\x30\x3d\x83\x89\x7b\xf5\x3a\
+\x10\x34\xc6\x18\x0b\xd4\xd6\xa1\xaf\x4f\xe0\xb5\x9b\x35\xe8\x9c\
+\xe0\x8a\x4d\x6a\xd4\x02\x6c\x4b\xfa\xb5\x6a\xc0\x50\xcb\x85\x67\
+\xec\x5f\xc1\x13\x11\x44\x51\x54\xa2\xb9\x9a\x17\xf0\x75\x0d\x63\
+\xf9\xdb\x8f\x6e\x37\xe3\xf4\x70\x13\xce\x8f\xfc\x03\xd7\x5c\xd7\
+\x95\x55\x95\x44\x5d\x3c\xee\x33\xcc\xc7\xf2\xd8\x74\xe4\xc7\xaf\
+\xc0\x23\xa6\xcc\xc9\xcd\xc6\x66\xd2\xca\x06\x11\x2d\x05\x70\x71\
+\xb2\x73\xf2\xc9\x04\xad\xe9\xfa\xf3\x90\xa7\x31\xd3\xec\x60\xcb\
+\x18\x80\xde\x9a\xc8\x86\xc1\x50\x6d\x2a\x0c\xd4\xee\x7f\x01\x55\
+\xbc\x1a\xf3\xbc\xba\x35\x45\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x24\x6a\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x80\x00\x00\x00\x74\x08\x06\x00\x00\x00\x4d\x3d\x3a\xc4\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x02\x54\x00\x00\x02\x54\
+\x01\x37\xb6\x01\x52\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\
+\x41\x54\x78\x9c\xed\x9d\x79\x9c\x1d\x45\xd5\xf7\xbf\xa7\xee\x4c\
+\x67\x66\x32\x93\x0d\xc8\x42\x10\x42\x58\xc3\x4e\x08\x06\x6c\x7c\
+\xd8\x5c\x40\x45\x20\x04\x70\x43\x59\x83\x82\x28\x02\x12\x59\xbc\
+\x60\xcb\x26\x62\xd0\x47\x41\x09\x0f\x12\x44\x16\x81\x00\x2a\x8a\
+\xfa\x28\x7b\x43\xd8\x05\x11\x94\xb0\x84\x60\x4c\xc2\x12\xb2\x4c\
+\x32\x99\x9e\x99\xae\xf7\x8f\xaa\xbe\x73\xef\x9d\xae\xbb\xcd\xbd\
+\x13\x7c\x3e\xef\xef\x43\x33\x37\xbd\x54\x55\xd7\x39\x5d\x75\xea\
+\xd4\x59\x44\x6b\xcd\xfb\x01\x5e\x10\x6e\x0e\x7c\x0a\xd8\x1b\xd8\
+\x0b\xd8\xda\x71\xeb\x32\xe0\x75\xe0\x79\xe0\x76\xe0\x81\x28\xeb\
+\xf7\x0d\x49\x23\x8b\xe0\x05\x61\x06\xd8\x0f\x38\x12\xd8\x05\xd8\
+\x12\x18\xef\xb8\x7d\x21\xb0\xc0\x1e\xf7\x44\x59\x7f\xf1\x50\xb4\
+\xb1\x1c\x64\x43\x33\x80\x17\x84\xd3\x81\x33\x80\x23\x80\x4c\x0d\
+\x45\xbc\x05\xdc\x01\xdc\x0a\x3c\x12\x65\xfd\x86\xbe\x90\x17\x84\
+\x02\xec\x03\x7c\x06\x98\x09\x8c\xad\xa1\x98\x3e\x60\x3e\x30\x27\
+\xca\xfa\x8f\xd7\xb1\x79\x55\x63\x83\x31\x80\x17\x84\x13\x80\xeb\
+\x81\x8f\xd7\xb1\xd8\x25\x98\x51\xe1\x57\x51\xd6\x5f\x50\xc7\x72\
+\xf1\x82\x70\x2f\xe0\x68\xcc\xd7\x3e\xb1\x8e\x45\xff\x11\x38\x2e\
+\xca\xfa\x4b\xeb\x58\x66\xc5\xd8\x20\x0c\xe0\x05\xe1\x27\x80\x79\
+\xc0\x26\x0d\xac\xe6\x61\xe0\xf4\x28\xeb\x3f\x33\x98\x42\xbc\x20\
+\x9c\x0a\xfc\x10\xf8\x70\x5d\x5a\x95\x8e\xb7\x81\x63\xa3\xac\xff\
+\xfb\x06\xd6\x91\x8a\x21\x67\x00\x2f\x08\xcf\x06\x2e\x03\x64\x08\
+\xaa\x8b\x31\xa3\xcc\x79\x51\xd6\x5f\x5e\xcd\x83\x5e\x10\x8e\x03\
+\x2e\x06\x8e\x03\x54\x03\xda\x56\x0c\x0d\x7c\x2b\xca\xfa\x97\x0f\
+\x41\x5d\x39\x0c\x29\x03\x78\x41\x78\x22\x70\xed\x90\x55\xd8\x8f\
+\x35\xc0\x45\xc0\x0f\xa3\xac\x1f\x95\xba\xd1\x0b\x42\x0f\x38\x1d\
+\x38\x1f\xe8\x18\x82\xb6\x15\xe3\xa4\x28\xeb\xff\xcf\x50\x55\x36\
+\x64\x0c\xe0\x05\xe1\xe1\x98\xf9\xb9\x9c\xa0\xb7\x08\xb8\x17\x78\
+\x1c\x23\xe9\xaf\x01\xba\x80\x8d\x80\xc9\xc0\x56\xc0\x81\xc0\x47\
+\x81\xa6\x2a\x9b\xf1\x2a\x70\x56\x94\xf5\xef\x76\xb4\xf1\x30\xe0\
+\x0a\x5b\x47\x35\xe8\x05\xfe\x17\xf8\x8b\xad\xe3\x35\xe0\x5d\xa0\
+\x15\x18\x01\xec\x8a\x59\xdd\x1c\x0c\x6c\x56\xa6\xac\x3e\xe0\xc8\
+\x28\xeb\xdf\x55\x65\x1b\x6a\xc2\x90\x30\x80\x5d\xe2\xbd\x08\x0c\
+\x2f\x71\xdb\x4b\xc0\x05\xc0\x9d\x95\x2c\xeb\xbc\x20\xdc\x08\xb3\
+\x72\xf8\x0c\xb0\x2f\xd5\x0d\xd3\x7f\xc6\xcc\xb9\x4b\x6c\x59\x13\
+\x31\x32\xc9\x47\xaa\x28\x23\x06\x1e\xc4\xac\x3e\xe6\x47\x59\xff\
+\xdd\x0a\xda\xdc\x04\xcc\x00\x2e\x04\xa6\x94\xb8\x75\x2d\xb0\xc3\
+\x50\x2c\x15\x87\x8a\x01\x7e\x8b\x59\xe3\xbb\x70\x0d\xf0\x8d\x28\
+\xeb\x77\xd5\x58\xfe\x78\x8c\x74\x7e\x02\xe6\x6b\xab\x04\xcb\x81\
+\xa3\xec\xef\xdb\x80\x71\x15\x3e\xf7\x1c\x70\x1d\x70\x7b\x94\xf5\
+\x97\x55\xd3\xce\x04\x5e\x10\xb6\x02\x57\x02\x27\x97\xb8\xed\x9e\
+\x28\xeb\x1f\x52\x4b\xf9\xd5\xa0\xe1\x0c\xe0\x05\xe1\x4c\xcc\xd0\
+\xef\xc2\xb7\xa3\xac\x7f\x51\x9d\xea\x52\xc0\x97\x80\x4b\x70\x2b\
+\x64\xf2\x11\xdb\xbf\x95\x8c\x1e\xcb\x80\x73\x81\x1b\xa2\xac\x1f\
+\x97\xbb\xb9\x12\x78\x41\x78\x3e\xf0\xdd\x12\xb7\x1c\x19\x65\xfd\
+\x3b\xea\x51\x97\x0b\x0d\x65\x00\xab\x34\x59\x88\x7b\x4e\xfd\x69\
+\x94\xf5\x4f\x69\x40\xbd\x1d\x18\x62\x7d\x03\x18\x36\xc8\xe2\xba\
+\x31\x5f\xeb\x25\x51\xd6\x5f\x33\xd8\xb6\x15\xc3\x0b\xc2\xab\x81\
+\xaf\x38\x2e\xbf\x0a\x6c\xd3\x48\xe5\x56\xa3\x97\x37\x07\xe3\x26\
+\xfe\x4b\x18\x69\xbb\xee\x88\xb2\xfe\x9a\x28\xeb\x9f\x83\x99\x67\
+\xe7\x0f\xa2\xa8\xf9\xc0\x94\x28\xeb\x9f\xd3\x08\xe2\x5b\x9c\x8e\
+\xe9\x8b\x34\x6c\x85\xe9\xc3\x86\xa1\xd1\x0c\x70\x6a\x89\x6b\x5f\
+\x29\xb7\x24\x1b\x2c\xa2\xac\xff\x7a\x94\xf5\x67\x62\xf4\xf5\x2f\
+\x54\xf1\xe8\x0b\xc0\x7e\x51\xd6\x9f\x19\x65\xfd\xd7\x1b\xd2\x38\
+\x0b\xdb\x07\xae\x11\x00\x4a\xf7\xe1\xa0\xd1\x30\x06\xf0\x82\x70\
+\x12\x6e\xee\xfd\x5d\x94\xf5\x1f\x6c\x54\xdd\xc5\xb0\x75\xdd\x86\
+\x51\xb6\x94\x83\x06\x6e\xdb\x00\xed\xfb\x9d\xe3\xf2\xc1\xb6\x2f\
+\x1b\x82\x46\x8e\x00\xfb\xe3\xd6\xf6\x5d\xd5\xc0\x7a\x07\xc0\x0b\
+\xc2\x1f\x00\x01\x95\x69\x1f\x05\x08\xec\x33\x43\x09\x57\x9f\x08\
+\xa6\x2f\x1b\x82\x46\x32\xc0\x1e\x8e\xf3\xaf\x01\x7f\x68\x60\xbd\
+\x05\xf0\x82\xf0\x7b\x98\xdd\xc6\x6a\x71\x86\x7d\x76\xa8\xf0\x07\
+\x4c\xdf\xa4\xc1\xd5\x97\x83\x46\x23\x19\x60\x9a\xe3\xfc\x7d\x8d\
+\xde\xb2\x4d\xe0\x05\xe1\xc5\xc0\xd9\x83\x28\xe2\x6c\x5b\x46\xc3\
+\x61\xfb\xe4\x3e\xc7\x65\x57\x5f\x0e\x1a\x0d\x61\x00\xab\xf1\xda\
+\xc5\x71\x79\x50\xbb\x73\x55\xb4\xe1\x3b\x98\xa5\x60\x29\x5c\x6d\
+\x8f\x52\x38\xd7\x96\x35\x14\x70\xf5\xcd\x2e\xb6\x4f\xeb\x8e\x46\
+\x8d\x00\x3b\x60\xf4\xe0\x69\x78\xba\x41\x75\xe6\xe0\x05\xe1\xb7\
+\x81\x6c\x89\x5b\xfa\x80\xe3\xa3\xac\x7f\x6a\x94\xf5\x4f\x05\x8e\
+\xb7\xe7\x5c\xc8\xda\x32\x1b\x0d\x57\xdf\xb4\x62\xfa\xb4\xee\x68\
+\x14\x03\xb8\xe6\xac\x5e\xcc\x06\x4f\xc3\xe0\x05\xe1\x39\x18\x81\
+\xcf\x85\x18\xb3\x0f\x70\x7d\x72\xc2\xfe\x3e\x96\x7e\xcd\x60\x1a\
+\x02\x5b\x76\x23\xf1\x3c\xa6\x8f\xd2\xd0\x10\x39\x60\xa8\x19\xe0\
+\xc5\x28\xeb\xaf\x6f\x50\x9d\x78\x41\xf8\x4d\x8c\x1a\xd8\x85\x18\
+\x63\x7d\xf3\xcb\xe2\x0b\xf6\xdc\x71\x94\x66\x82\x4b\x6c\x1d\x0d\
+\x81\xed\x9b\x17\x1d\x97\xff\x4f\x30\x40\xc3\xe6\x7f\x2f\x08\xbf\
+\x01\x94\x32\xa6\x88\x81\x13\xa2\xac\xff\x0b\xd7\x0d\xf6\xda\x09\
+\x94\x66\x82\xcb\x6d\x5d\x8d\x82\xab\x8f\xfe\xa3\x18\x60\xb4\xe3\
+\xfc\xdf\x1b\x51\x99\x17\x84\x47\x00\x73\x4a\xdc\xa2\x31\x86\x16\
+\xf3\xca\x95\x65\xef\x39\x89\xd2\x4a\xa3\x39\xb6\xce\x46\xc0\xd5\
+\x47\xae\x3e\x1d\x14\x86\xc2\xd4\x29\x1f\xae\xf9\xad\x66\x78\x41\
+\xb8\x23\x66\x2f\xdf\x05\x0d\xcc\x8a\xb2\xfe\xcf\x2b\x2d\xd3\xde\
+\x3b\x8b\xd2\x4c\x30\xcf\xd6\x5d\x6f\xd4\xbd\x8f\x4a\x61\xa8\x19\
+\xa0\xae\xf0\x82\x70\x14\x70\x17\xd0\xee\xb8\x45\x03\x5f\xae\xc5\
+\xc4\xca\x3e\xf3\x65\xdc\x4c\xd0\x0e\xdc\x65\xdb\xf0\x1f\x8b\x86\
+\xac\x2d\x87\x02\x76\xef\xff\x26\x60\x9b\x12\xb7\x9d\x12\x65\xfd\
+\xb9\x53\x6f\x5e\xac\x44\x49\xbb\x12\x69\x57\x22\x1d\x22\xd2\x2e\
+\x22\xed\x88\xb5\xf9\xd3\xac\xd1\xe8\x4e\xad\x75\x67\x1c\xb3\x46\
+\xeb\xb8\x33\xd6\xba\x33\xca\xfa\x73\x6d\x3d\x3f\x75\x94\xbf\x0d\
+\x70\x93\x17\x84\x87\xd4\xcb\x46\x60\xa8\xf1\x1f\xcb\x00\x02\x17\
+\x69\xf8\x84\xeb\xfa\xe8\x91\xed\x2b\xc7\x6f\x34\xf2\x3b\xbb\xdf\
+\xbc\x78\x8e\x12\x69\x55\x22\x28\x11\x44\x09\x62\x7f\xe7\x17\x16\
+\xdb\xef\x5c\x44\x03\x82\x68\xd8\xfd\xe6\xc5\x5d\x53\x26\x4f\x5c\
+\xb3\xec\xdd\x55\x2b\xdf\x5b\xd5\xe9\xfa\xd2\x3f\x21\xc6\xe0\xb4\
+\x9c\xd2\xe9\x7d\x89\xf7\x35\x03\x8c\xbc\xfc\xc9\x8c\x12\xb5\xb9\
+\x52\xb2\xad\x52\xb2\x4d\x46\xa9\x6d\x95\xc8\x36\x51\x6f\xef\xee\
+\xba\x84\x09\xd7\xf0\xd6\x61\x8c\x1b\x33\x72\x94\x88\x20\x02\xe6\
+\x6f\x3f\xe1\xf3\xff\x0d\xa0\xb5\x46\x61\x44\x7f\x11\x33\xe6\x8b\
+\x68\x44\xd3\x2a\x22\xad\xe3\xc6\x8c\x20\x8a\x7a\x58\xdb\xd5\x9d\
+\x5a\x9f\x86\x73\x36\x9e\xf3\xcc\xf1\xcd\x4d\x4d\xcf\xc6\x71\xbc\
+\x30\xd6\xfa\xe5\x38\xd6\x0b\xe3\x38\x7e\x39\x8e\xf5\xe2\x55\xb3\
+\xf7\xdc\x20\xae\x6b\x95\xe0\x7d\xc5\x00\xc3\xbe\xfb\x68\xb3\x52\
+\xb2\x67\x46\xa9\x03\x94\xc8\x01\x4a\xc9\xde\x22\xb4\x08\x20\x76\
+\x23\xaf\x2f\x8e\x59\xb3\xd6\xad\x4a\x68\x6e\xca\x30\x61\x93\xd1\
+\x96\xf0\xe6\xb9\xdc\xef\x84\x09\x54\x11\x03\xc4\x96\xf8\x3a\x47\
+\x78\x34\x82\xfd\x01\xc0\xf8\x8d\x47\xb1\x78\xe9\x3b\xf4\xf4\xa6\
+\xd3\x72\xcd\xda\xf5\xe3\x46\xb6\xb7\x1d\x24\xc2\x41\x45\x52\xc3\
+\xfa\x8e\xcb\x1e\x7f\x2c\x8e\xf5\x7d\x71\xac\xef\xeb\x8b\xe3\x27\
+\xa3\xac\xdf\x33\xe8\xce\xaa\x13\x36\x28\x03\xd8\xf9\x75\x77\xcc\
+\x76\xe7\x01\x4a\xe4\xc3\x92\x08\x74\x92\xfb\x5f\xee\x8f\xd6\x9a\
+\xd5\x6b\xbb\x70\x99\xb1\x89\x08\x13\x36\x19\x4d\x46\x25\xb2\xad\
+\xe4\x6d\x00\x4b\x5e\x99\xc9\x68\xa0\xd0\x68\x74\x1e\xa1\x5d\xc8\
+\x28\xc5\x84\x4d\x46\xf3\xe6\xb2\x77\x53\xeb\xd7\x5a\xb3\x66\x6d\
+\x17\x1d\xc3\x5b\x8a\x2f\xb5\xa0\xd9\x5f\xc3\xfe\xda\xd8\xff\x75\
+\x7a\x41\xf8\x30\x66\xe3\xe7\x7e\xe0\xd9\x0d\x29\x3f\x0c\x35\x03\
+\x34\x79\x41\xb8\x35\xc6\x50\xe4\x00\x8c\x39\x77\xff\xfa\x36\x9f\
+\xe8\x05\x30\xc4\x5b\xdb\xd5\x4d\x5f\x9f\xbb\xaf\xc6\x8e\x19\xc1\
+\x30\xaf\x39\xa5\x08\x49\xfd\xa9\xd1\xa0\x8b\x88\xaf\xf3\x7e\xd8\
+\x4b\xc9\xd1\xdc\xd4\xc4\xc6\xa3\x3b\x78\x7b\xc5\xea\xd4\xfa\xfb\
+\xe2\x98\xb5\x5d\xdd\xb4\xb5\x0c\x03\xad\x0b\x9e\xcd\x2b\xbc\x1d\
+\xf3\xfe\x89\xb1\xcc\x7b\x5e\x10\x3e\x88\x61\x88\x7b\x19\x62\x9a\
+\x34\xaa\xb2\x15\x8e\xf3\x67\x00\xdf\xaf\xb8\x94\x3c\x62\x75\x47\
+\x3d\x44\x3d\xee\x25\xf2\xc8\xf6\x36\xda\xdb\x06\x7c\x7d\x16\x46\
+\xb0\x33\xd4\x30\xe4\xd0\x86\xf6\x7d\xa0\x89\xb5\xce\x68\xad\xd1\
+\x09\xd1\xf2\x88\x57\xc8\x20\x9a\xf6\xd6\x16\xba\xdb\x7b\x58\xdd\
+\x99\x6e\xc1\xde\xd3\xdb\x47\x77\x4f\x2f\x4d\x19\x95\x14\xd0\xdf\
+\x84\x74\x8c\x06\x0e\xb3\x07\x22\x52\xe0\x24\x9a\x37\xda\xb8\xfa\
+\x74\x50\x68\x14\x03\xbc\x8c\xf1\x84\x29\xc6\x84\x5a\x0a\x8b\x63\
+\xcd\xfa\x6e\xb7\xf9\xe0\x30\xaf\x39\x1a\x3d\xb2\xfd\x31\x53\xaf\
+\x5e\x86\x96\x4e\x84\x4e\xd0\x6b\x92\xdf\x5a\xeb\x4e\x2d\x74\x6a\
+\xcd\x9a\x18\x3a\x25\xa6\xf3\x91\xc3\xc7\xae\x07\xf0\xef\x5c\xd6\
+\x12\x6b\xdd\xae\xcd\xd1\xa1\x35\xc9\xef\x76\x0d\xed\x1a\x3a\xf2\
+\x7e\x8f\x1f\xd5\x31\x7c\xbb\xee\xa8\x67\xaf\xee\xa8\xd7\x4b\x6b\
+\x4f\x77\x14\xa1\x5a\xac\x31\x72\xd1\x88\x52\x0e\x22\x4c\x48\xe4\
+\x1d\x8d\x46\x44\x12\x9e\xfd\x67\x65\xbd\x55\x1d\xea\x6e\x16\x6e\
+\x15\x23\x37\x00\x9f\xae\xf6\x59\xa5\xcc\xbc\xac\x94\x14\xfc\x5e\
+\xdf\x1d\xd1\xeb\x1e\xfa\xff\x05\x4c\x8d\xb2\xfe\xdb\x83\x68\x76\
+\xd5\xf0\x82\x70\x13\x8c\xde\x3e\xd5\xd5\x2b\xa3\x14\x9e\xd7\x44\
+\x1c\x6b\xe2\x38\x26\xd6\x9a\x38\xd6\x4e\xf9\x25\x41\x53\x93\xf5\
+\x9c\xb3\xa3\x47\x32\x88\xc4\x5a\xff\x5a\x6b\x7d\x6c\x94\xf5\x57\
+\xd6\xf1\x35\xea\xc7\x00\x5e\x10\x6e\x8f\x59\x0b\xcf\xc4\x6d\x0b\
+\x50\x12\x79\x44\x8f\x94\x92\xc7\x95\xa8\xfb\xa3\x9e\x9e\xd6\xde\
+\xbe\xd8\xb5\x03\xd7\x0d\xec\x13\x65\xfd\xa7\x6a\x6c\xf6\xa0\xe0\
+\x05\xe1\x34\xe0\x11\x1c\xbe\x07\x19\xa5\xbe\xdf\xd4\xa4\xba\xe2\
+\x58\xef\x1f\xc7\x7a\x7a\x1c\xc7\x5e\xb9\xde\xf6\x9a\xcd\xa0\xac\
+\xf3\xa6\x0f\xad\x8d\x7c\xa1\xb5\xee\xc2\x38\xd9\x5c\x1a\x65\xfd\
+\x7f\xd4\xe3\x1d\x06\xcd\x00\xf6\x8b\xbf\x10\x63\xbe\x5c\xeb\x94\
+\xd2\x07\x3c\xa5\x44\xee\x57\x4a\xee\x53\x4a\x85\x9d\xe7\x4c\x5f\
+\x67\x5d\xb4\x5f\xc2\xbd\x11\x72\x6e\x94\xf5\x2f\xad\xb1\xce\xba\
+\xc0\xda\x08\xb8\xb6\xa0\xdf\xc3\xf8\x15\x2c\x6f\xb9\xe8\xd1\xb6\
+\x38\xd6\x3e\x46\xf8\xdd\x1f\x63\xe6\x55\xe0\x28\xab\x44\x68\x6e\
+\xee\xef\xc2\x7c\xda\xf4\xf4\xf6\x92\xf7\xcf\x5e\x8c\x11\xe9\x85\
+\x83\x1d\x11\x6a\x66\x00\x1b\x1f\x67\x16\xc6\xf8\x62\xe3\x1a\x8a\
+\x78\x11\x23\xf5\xde\x0f\x3c\x94\xe6\x78\xe1\x05\xe1\x1d\x18\x07\
+\xd0\x34\xfc\x15\xd8\x33\xca\xfa\x43\xba\x79\x52\x0c\x6b\xaa\xf5\
+\x24\xb0\x9b\xe3\x96\xf9\xd6\x37\xa1\xf8\xb9\x0e\xe0\xbf\x30\xcc\
+\x70\x30\xb0\x43\x26\xa3\xf0\x9a\xec\x08\x90\xf7\xf5\x83\x11\x82\
+\x53\xf0\x0e\xc6\xf2\x69\x6e\xad\x71\x92\x6a\x62\x00\x2f\x08\xf7\
+\x07\x7e\x04\xec\x5c\x43\x9d\x7f\xc5\x6c\xd0\x94\x8c\x8d\x63\xdd\
+\xc9\xef\x74\x5c\xee\x03\x3e\x58\x4d\xf4\x8f\xf6\x4b\x17\xfc\x0a\
+\x2d\x3b\x8b\x02\x10\xe5\x75\xeb\x0f\xbe\x7b\xc1\xf4\xd5\x1b\xfd\
+\xf8\xf1\x11\x7d\xeb\x33\x4f\x80\xc4\xe6\x12\x7f\x5b\x71\xd6\x1e\
+\x47\x57\x5a\xae\x6d\xeb\x54\xe0\x09\xdc\xae\xef\x33\xca\xb9\x7b\
+\x7b\x41\x38\x7d\x98\xd7\xf4\xb3\x4c\x26\xb3\xdb\xc0\xf9\x3f\xa6\
+\x3b\x2a\xc9\xe7\x7f\x03\xbe\x1e\x65\xfd\xfb\xab\x69\x37\x54\xb9\
+\x1b\xe8\x05\xe1\x64\x2f\x08\xef\xc4\xac\x59\x6b\x21\x3e\xc0\xc2\
+\x0a\x88\x3f\x8a\xd2\xbe\x03\x3f\xa8\x3a\xf4\x8b\xc8\x78\x51\x32\
+\x45\x90\x29\x22\xb2\x5d\x3c\x72\x98\x02\x88\xfb\x86\x29\x41\xb6\
+\x53\xc2\x14\x51\x32\x45\x44\x2a\x71\x2a\x2d\x80\x6d\x4b\x29\x3f\
+\x82\xab\xca\xed\x1a\x46\x59\xff\xf1\xe6\xa6\xa6\x85\x2a\x45\x6d\
+\x5d\x01\x76\x06\xee\xf3\x82\xf0\x4e\x2f\x08\x27\x57\xd1\xf4\xca\
+\x19\xc0\x0b\xc2\x2f\x60\xe6\xe3\xc3\x2b\x7c\x24\x5d\x71\x0e\xdb\
+\x57\xf0\xec\x0f\x70\x2f\x19\x17\x62\xe2\x08\x54\x85\x7e\x95\xb0\
+\xf9\x5b\x70\x2d\x39\x0f\x85\x9b\x44\xd5\xe1\x02\xdb\xb6\x34\x4c\
+\xa0\x34\x83\xd8\x76\xc8\xf6\x46\x55\x9d\xb7\x7f\xa1\x04\xad\x9d\
+\x7d\x59\x8c\xc3\x81\x97\x2c\xad\x2a\x42\x45\x0c\xe0\x05\xe1\xb9\
+\xc0\x8d\x40\xea\xba\xb7\x08\xcb\x31\x7e\xef\xe7\x39\xae\x6f\x6f\
+\xe7\x3f\x57\x5d\x07\x62\xac\x74\xd3\x90\x58\xf6\x54\x6d\x57\x68\
+\x08\xdc\xcf\x04\x03\xae\xa5\x9c\xaf\x06\xb6\x4d\xa5\x2c\x89\x8e\
+\xb7\xef\x96\x8a\x4d\xae\x7c\xb6\x43\x89\x6c\xaf\x8a\xbe\x7e\x25\
+\x42\x73\x53\xd3\x99\x98\x3e\xad\x24\xce\x91\x07\xdc\x68\x69\x56\
+\x16\x25\x19\xc0\x0b\xc2\x8c\x17\x84\xd7\x60\x82\x25\x95\x43\x84\
+\xb1\xc9\xdb\x26\xca\xfa\x73\x31\x51\xba\xd2\xd0\x8c\x09\xf1\x92\
+\x56\x5f\x0b\xa5\x63\x08\xcd\xad\xd5\x67\x4f\x15\xed\x0a\xe6\xa3\
+\x60\xb7\x70\x10\x26\x32\xb6\x6d\x73\x4b\xdc\x72\xad\x7d\xc7\x94\
+\xf6\xc9\x81\xa2\xa4\x39\x47\x7c\x95\x30\x01\x64\x94\x3c\x69\xfb\
+\x74\x1b\x4c\x1f\x57\xe2\x54\x7b\xb1\x17\x84\xd7\x58\x61\xdd\x09\
+\xe7\xeb\x7a\x41\x38\x1c\xf8\x0d\x46\xd2\x2f\x87\x5f\x63\x42\x9a\
+\xcc\xce\x93\xe6\x9f\xc2\xc4\xc9\x49\xc3\x41\x8e\xf3\xb3\x30\xd1\
+\x36\xd3\xb0\x84\x41\x78\xf9\x88\x2a\x1c\xea\x0b\xae\xe5\xed\x1a\
+\x0e\x62\x0a\x48\x70\x36\xa6\xad\x69\xd8\x12\x47\x7f\x8a\xc8\x41\
+\xc5\x5b\xd5\xf6\x78\x57\x44\x9e\x82\x9c\xdb\xfb\x6c\x8c\x8f\xc0\
+\xaf\x2b\x68\xcb\x2c\xe0\x37\x96\x96\xa9\x48\x5d\x05\xd8\xf5\xf7\
+\xef\x28\x6f\x89\xfa\x02\x26\x16\xdf\x5f\xd2\x2e\x7a\x41\x78\x2b\
+\x26\xb8\x62\x61\xa5\x22\x6f\x74\x7f\xfb\x43\x93\x8a\xee\x6d\xc3\
+\xf8\xc6\xb9\xf6\xf9\x0f\x8d\xb2\xfe\x6f\x5c\x0d\x69\xbf\xe4\xc9\
+\x03\xb5\xc4\x17\x8b\x24\x5c\x2d\xa0\xd0\x19\x54\xaf\x28\x94\xd6\
+\x6a\x8a\x88\x1e\x2d\x76\xc3\x49\xe0\xdf\xa2\x44\x04\xc9\x20\x8c\
+\x15\x72\xd3\xc4\x0a\xd0\x0f\xa8\x0c\xaf\xe8\x58\xbd\xaa\xa5\xf7\
+\x91\xa5\xa7\xed\xee\x32\xd5\x4e\x85\x17\x84\x9f\xc6\x4d\xa0\xe5\
+\xc0\xe4\x28\xeb\xaf\xcb\x3f\x39\xe1\xbf\x9f\x5f\x04\x6c\x01\x3a\
+\xb7\xf4\xb3\xca\xa0\x5b\x96\x9f\xbe\xdb\xe7\x1c\xf5\x1c\x88\x89\
+\x61\xb8\x53\x99\x26\x3d\x0d\x7c\x32\x2d\x54\xde\x80\x11\xc0\x0b\
+\xc2\x2d\x30\x11\xba\xca\x11\xff\x12\x60\x37\x17\xf1\x2d\xfe\x38\
+\xa0\x42\x33\xc4\x6d\xd1\x76\xf1\x82\x62\x61\xf0\x34\xdc\xc4\xbf\
+\xad\x14\xf1\x01\x94\xd6\x23\x94\xc8\x74\x25\x32\x5d\x44\x4d\x17\
+\x25\xd3\x15\x6a\x2f\x84\x7d\x40\x3e\x64\x88\x9f\xc8\x01\x82\x28\
+\xd9\x54\x90\x09\x22\x8c\xcd\x9d\x33\x43\xee\x18\x51\x6a\x06\x5a\
+\x9d\xad\x14\xd7\x64\x54\xf3\x5f\x3f\x70\xd5\x0b\x7f\xd8\xfc\xaa\
+\x17\x8e\xcf\xdf\x5c\x2e\x05\xdb\xd6\xdb\x1c\x97\xc7\xd9\x77\xcd\
+\x61\xe2\x4f\xfe\xb6\xbd\x12\xd9\xa2\x58\xfa\xb7\xc2\xa0\xd3\x91\
+\xd6\xf6\xfd\x6e\x94\xf6\x85\x00\x43\xcb\xc7\x2d\x6d\x0b\x50\xc0\
+\x00\x36\x46\xde\xed\xc0\x80\x1b\xf3\xd0\x8b\xb1\xb2\x3d\xaf\x02\
+\xe5\xc3\x9f\x92\x1f\x39\x43\x8c\x44\xcf\xaf\x24\x37\x0d\x78\x41\
+\x38\x02\xf7\xf0\xde\x0d\x9c\x55\xa6\x1e\xc8\x14\x1b\x80\x94\xf9\
+\x5b\xd1\x7d\x82\x88\x34\x8b\xc8\xc7\x45\xa9\xeb\x36\xbf\xfa\xef\
+\xf3\xe4\xf6\x8a\xe3\x19\x9f\x85\x7b\x25\x74\xb6\x7d\x67\x00\x94\
+\xc8\x41\xfd\x73\x7e\xfe\xfc\x2f\x5a\x44\xfe\xe4\x28\x03\x80\x28\
+\xeb\xf7\x45\x59\xff\x3c\xcc\x70\x5f\x4a\x59\xb0\x05\x70\xbb\xa5\
+\x71\x0e\xc5\x23\xc0\x95\xc0\x9e\x25\x0a\xe9\x04\x3e\x1d\x65\xfd\
+\x8a\x82\x3d\xda\x30\x6c\x2f\x24\x5c\xad\x44\xa1\x44\x25\x5c\x9e\
+\x2f\x07\x7c\x03\x18\xe3\x28\xe6\x9a\x28\xeb\xbf\x59\x49\x7d\x29\
+\xf3\x27\x05\x5f\x7d\xc1\x51\x21\x53\xd0\xff\x6f\xa5\xd4\x17\x27\
+\xad\xf8\xc7\x8d\x15\xbe\xfb\x9b\x98\xe8\x67\x69\x18\x63\xdf\x39\
+\x69\xf7\x41\xc5\xd2\xbf\x3d\x9e\x5b\xfa\xb5\x5d\x2a\x8a\x44\x66\
+\x69\xf2\x69\x0c\x8d\x5c\xd8\x13\x43\xe3\x1c\x72\x32\x80\x17\x84\
+\x47\x63\x62\xde\xb9\xb0\x14\x33\x8f\x3c\x5b\x49\x83\x12\xb4\x7c\
+\xf7\xd1\x2b\x44\xe4\xcc\x02\x0e\x37\x1d\xba\x5e\x89\x8c\x59\xdb\
+\xd5\xdd\x8a\x09\xff\x3e\x22\xe5\xf1\x75\x98\xf9\xb2\xec\xf2\x67\
+\xfc\x15\xcf\x8f\x8d\x74\x34\x0d\x40\x32\x32\x5c\xf7\x71\x80\x28\
+\xf9\x92\x08\xad\x62\x0d\x4a\x12\x02\x63\x08\x7b\xbd\x88\xac\x47\
+\xa4\x15\x74\xab\x42\xb5\x29\xa5\x27\x83\xec\x48\xd1\x72\x51\x0a\
+\x9f\xd5\x19\x9a\xa6\x2d\x9c\xb5\x75\x59\x45\x94\x95\xa5\x5e\x03\
+\xda\x52\x2e\xaf\x06\xb6\xdc\x74\x93\xd1\x5d\xda\xec\xf5\xb7\x14\
+\xce\xff\x1a\xe0\xb2\x37\x4f\xdd\xa9\x2a\x7f\x44\x2f\x08\x77\xc7\
+\xc8\x6f\xa5\xb6\xde\x3f\x13\x65\xfd\x5f\x81\x65\x00\x1b\xb7\x6e\
+\x21\xee\x28\xd8\x2f\x01\x07\xd5\x12\xb8\xb0\xf5\xe2\xc7\x3e\xaa\
+\x44\xfe\x94\x66\x90\x29\xc2\xc1\x9d\xeb\xd6\xef\x07\xcc\x76\x3c\
+\xfe\xbd\x28\xeb\x7f\xab\xda\x3a\x13\x6c\x34\xe7\xd9\x6b\x15\x9c\
+\x98\x47\xf4\xbc\x55\x40\xd3\x07\x97\x7e\x7d\xc7\x27\xf3\xef\x17\
+\x90\x0f\xfc\xf4\xa5\x43\x35\xf1\x2c\x85\x1c\x9c\xdc\x5f\xc0\x3c\
+\xe6\xef\x45\xaf\x9c\xb4\x5d\x45\xde\xc2\x5e\x10\x5e\x56\xea\xfd\
+\x26\x8e\x1b\xf3\x00\x9a\x7b\xad\xe6\xbf\xdf\x6c\xc0\x18\xa5\xec\
+\xbf\xf8\x94\x1d\x1f\xa8\xf6\xbd\x6d\x60\xce\x3f\xe0\x0e\x46\xb9\
+\x04\xb3\x5c\xef\x4a\xa6\x80\xd3\x70\x13\x7f\x25\xf0\xa9\x5a\xa3\
+\x56\x2a\x91\x87\x45\xa4\xab\x68\xfe\x47\x29\x01\x64\x06\x45\x02\
+\x51\x1e\x56\x53\xda\xd7\xaf\x2c\x9a\x90\x7f\xba\xa6\x01\x9a\x07\
+\xde\xaf\x41\x2f\xfe\xca\x94\xbb\xff\xf5\xd6\x8e\x9f\x42\xe9\x3f\
+\x95\x90\x0f\xaa\x09\xd9\x72\xb9\x7d\x97\x34\x9c\x86\x66\xc6\xc0\
+\xd1\x51\x10\x25\x6b\x44\x24\xac\xe1\xb5\xb1\xb4\xfa\x14\x86\x76\
+\x69\x98\x88\xed\x77\xe5\x05\xe1\x68\xa0\xd4\x57\x76\x6c\x94\xf5\
+\x5d\xa1\x4b\xca\x62\xed\xb9\x7b\xad\x57\x4a\x1e\x54\x52\xa8\xdd\
+\x52\x4a\x88\x7a\x7a\x3e\x47\xfa\xf0\x08\x26\x99\xc2\xa0\xcd\xa0\
+\x5c\x02\x5e\x29\xe8\x0b\x88\x95\x96\x3b\xfb\x99\x67\x40\x19\x15\
+\x6f\x7b\xdb\x77\x70\xf9\x2d\xb6\x89\xc8\xe7\x0a\xe6\x7f\x25\x89\
+\xd2\xea\xbe\x37\xbe\xb2\x43\xcd\xd6\xc3\x96\x66\x5f\xc2\xad\x99\
+\xfc\x96\x17\x84\xa3\x15\xf0\x79\xdc\xfb\xed\x73\xa3\xac\x5f\x89\
+\xc2\xa1\x24\x94\xa8\x7b\x45\x14\x4a\x25\x87\xb1\xcf\x8b\x7a\x7a\
+\x5d\x0a\x8a\x77\x29\x12\x56\x6a\xab\xd8\x2d\xe8\xa5\x0c\x00\xc5\
+\x8f\x76\x16\xad\x06\xf2\x96\x90\x6a\x6d\x95\x2d\xb9\x92\x14\xa5\
+\x58\x5b\xcb\x30\x32\x19\x35\x3c\x5f\xff\x9f\x37\x12\x0c\x58\x42\
+\x57\x0b\xbb\x1c\x1d\xe0\x0a\x6f\x31\x1a\xf8\xbc\xc2\x04\x2f\x4e\
+\x43\x17\x50\x97\xd0\x28\xa2\xe4\x16\xa5\x24\xea\x1f\x05\x94\x6b\
+\x7f\x3b\xc1\xe5\x51\xd6\x77\x0d\x9b\x15\x43\x41\x21\xe1\xf2\xa7\
+\x80\x32\xd0\x4a\xf6\xeb\x17\x02\x8b\x98\x48\xeb\x07\xaa\x69\x87\
+\x7d\x97\x01\xd3\x59\x47\x7b\x6b\x8a\x5c\x24\x88\x48\xa4\x44\xea\
+\x15\x22\xf6\x7c\xdc\xcb\xd1\x19\x0a\x63\x94\x90\x86\xeb\xa2\xac\
+\xff\xef\x7a\xb4\xe0\xbd\xb3\xf6\x78\x5b\x89\xdc\x2d\xb9\xb9\x1f\
+\xd6\xbb\x19\x60\x19\xf0\x93\x7a\xd4\x8b\x52\xce\x65\x9e\x0b\x93\
+\x7f\xf4\xc2\xb8\xad\xe6\xbe\x7c\xa1\x12\x75\x5c\xc1\x17\xd9\xbf\
+\x0a\x78\x5b\x0b\x37\xd5\xd0\x9a\x9f\x60\xde\x0d\x80\xa6\x4c\x86\
+\xf6\xd6\x61\x05\x5f\x7d\xa2\xfb\x57\x22\x77\xbd\x72\xd2\x76\x75\
+\xb1\x71\xb4\xf2\x80\xcb\x39\xf6\xbf\x9a\x70\x1b\x31\xdc\x52\x8f\
+\x06\x24\x10\x25\xd7\x2a\x91\xa3\x44\x84\xee\xa8\xe4\x5e\xc6\xc5\
+\xc5\x6a\xd2\x41\xd5\x5b\xb8\xf4\xeb\x9f\xd3\x61\xcf\x49\x3f\xfb\
+\xfb\xe6\xa2\x33\xed\x92\xd1\xed\xa2\xa4\x83\x58\xb6\x51\xc3\xbd\
+\x19\x68\x3d\x2a\x7f\xee\xcf\x5b\x05\xac\xd5\x99\xa6\xd3\xfe\xf9\
+\xc5\x49\x55\xcb\x44\x51\xd6\x5f\x67\x23\x8e\xfd\x18\x60\x44\x7b\
+\x6b\x8e\x31\xb5\xd6\x08\x92\x3f\x59\x97\xda\x50\xaa\x05\x37\x93\
+\x1e\x71\x34\xe3\x12\x66\xde\x02\x1e\xab\x67\x0b\x94\xc8\x5f\x94\
+\xc8\x6b\xa2\x64\xf2\xfa\x6e\xe7\xd7\xbf\x84\x3a\xbe\xbc\x52\x6e\
+\x06\x40\xe4\x2a\x33\x1a\x58\x9f\x81\x38\x59\xef\x6b\x73\x6f\xb1\
+\xde\x40\xe4\x2d\x44\x1d\xf3\xcf\x2f\x4e\x2a\xa9\x99\x2b\x83\xb9\
+\x18\x81\x7b\xe2\xa8\x8e\xb6\x7e\x3d\x03\x56\x52\x13\x10\xcd\x2b\
+\xda\x98\xc9\xd5\x13\x8f\x61\x68\x3a\x20\xc3\x99\x6b\x37\xf0\xb5\
+\x7a\xc7\xf2\x5b\xf6\xf5\x5d\xb5\x28\xf9\x9f\x9e\xde\x3e\xfa\x62\
+\xa7\x89\xf7\x35\x75\x8d\x1f\x9c\x3f\x05\xe0\x12\x08\x8b\x96\x60\
+\x69\x5a\x43\xb3\x79\x34\x56\x69\x7d\xcb\x8e\x37\xbc\x76\xe3\x2e\
+\xd7\x2f\xac\xca\xea\x26\x81\x7d\xb7\x6b\x86\xb7\xb5\xd0\xdc\xdc\
+\x54\xa0\xfa\x35\xed\x00\x11\xb9\xf6\x1f\xc7\x6f\x5d\xd7\xbe\xb7\
+\xb4\x4c\x1d\xb5\x5c\x0c\x50\x97\xb9\x7f\x40\x65\x22\xd7\xaf\x5f\
+\x1f\xb9\x5e\x4e\x03\x15\x47\xf1\xa8\x04\x19\x2a\x53\xf7\x52\xe1\
+\x7d\x22\x8c\x11\x91\x2f\xc4\x4d\x99\x07\x76\xfb\xc5\x1b\xd5\xe8\
+\x02\xf2\xf1\xf3\x31\x23\x86\xeb\xfe\x79\xbf\x5f\xfa\x07\x89\x45\
+\x64\x5e\x9d\x5e\xbf\x18\xa9\x34\x1d\x52\x3f\xb4\xe5\xef\xae\x72\
+\x5e\x6b\x6b\x1d\xc6\x84\x8d\xeb\x1d\x06\xc7\x8e\x00\x90\x32\xac\
+\xab\xbb\x44\xeb\x48\x84\x16\xa0\x45\x94\xb4\x88\xa6\x05\x61\xa2\
+\x88\x6c\x56\x34\x5d\xe4\x9e\x35\xff\xa9\x0f\x68\xa5\x6f\xdd\xe6\
+\xfa\x57\x76\x5a\x78\xdc\xd6\x55\x09\x6b\x3b\x6c\xd5\xef\x47\x62\
+\xe6\xff\xfe\x29\x60\xed\xba\x2e\xfe\xfd\xd6\x7b\x98\xd4\x48\x43\
+\x03\x17\x03\x6c\xda\xa0\xfa\x8e\xc3\xb1\xa5\x3a\xb2\xbd\x4d\x94\
+\x92\xe3\x29\x9d\x41\xa3\x6a\x14\x12\xae\xff\x6b\xd6\x70\xe9\x2b\
+\xb3\xb6\x7b\xb2\xf8\xfe\x9d\x6e\x7f\xd1\x8b\xd7\x0e\x9b\x81\xe8\
+\xcf\x0b\xf2\x31\x01\x6f\x20\x03\x80\x88\x8c\xed\x18\xd6\xfc\xe5\
+\x6a\xdb\xab\x94\x1c\x8f\xf1\x40\x2f\x82\x66\xd5\x9a\x75\x0a\x13\
+\xaf\xb0\x11\xa9\xe3\x52\x69\xea\x9a\x02\x26\xdb\x6c\x1f\x75\x83\
+\x2d\xef\xc4\xb4\x6b\x4d\x99\x0c\x1d\xc3\x5b\x50\x4a\x4e\xde\xe5\
+\xc6\x45\x95\xd8\x1d\x56\x04\x95\x51\xce\x6d\x5f\x97\x26\xe8\x85\
+\x23\x77\x88\x5e\x3c\x76\xab\x5b\x5f\xfc\xd2\xd6\x87\x28\x25\x27\
+\x8a\x48\xef\xc0\x29\x21\xd1\x25\xc8\x61\xd5\xb4\x67\x8f\x5b\xdf\
+\xf4\x44\xe4\xe4\x7c\xd5\x6f\xf2\xb7\xa7\xb7\x2f\x09\x40\x71\x52\
+\x83\xfa\x3e\x75\x58\x71\x31\xc0\x58\xd2\x9d\x3b\x07\x83\x03\x5d\
+\x8d\x18\xd9\xd1\x66\xb7\x8a\x65\xa2\x52\x52\x2a\x91\x52\x55\xc8\
+\x29\x82\x52\x84\xba\x4a\x66\xbf\xbf\x1d\xb3\xe5\x8d\x22\xfa\xfe\
+\x7c\xe6\xe9\x2f\x13\x44\x64\x64\x55\xed\x11\x39\x59\x89\x4c\x2c\
+\x9e\xff\x45\x60\xe5\x9a\xdc\xca\x77\x6b\xea\x1f\x1e\x7e\x6f\x1c\
+\x39\x8e\x15\xee\x18\xb9\x9f\xad\x73\x23\x4e\x72\x5d\x18\x3d\x62\
+\xb8\xdd\x20\x52\x28\x25\xe7\xee\x71\xcb\x9b\xae\xfd\x81\xea\x50\
+\x64\x07\x98\x4f\xc8\x4a\x85\x1f\x41\xfd\x7d\xe0\xca\xc0\x8c\x08\
+\x4a\x4a\xa6\xc1\x2b\xc0\x07\x6f\x5b\xd2\x26\x22\xe7\x26\x65\xe4\
+\xa2\x94\x98\x4d\x31\x56\xae\x2e\xd0\x2e\x57\x62\x87\x59\x0d\x52\
+\x4d\xca\x80\x3e\x05\x3c\xe4\xb8\x78\x82\x17\x84\x75\x91\x05\xac\
+\x27\x6d\xea\x70\xd9\xde\xd6\x82\x67\x97\x44\xd6\x68\x64\xbc\x28\
+\xf9\x6a\x3d\xea\x2d\xfa\x5a\x0b\x85\xc0\x0a\x21\x19\x99\x34\x70\
+\x0a\xb1\x53\x80\xa8\x8a\x37\x6b\x94\xc8\x57\x95\xc8\xf8\x01\xaa\
+\x5f\x4b\xfc\xde\xbe\x82\xef\xf0\x70\xdb\x67\x83\x86\xdd\x1a\x4e\
+\x9d\x7a\x81\x87\x14\x6e\xf7\xab\x56\x6a\x70\xc0\x70\xe0\xb3\x38\
+\x7c\x0a\x92\xaf\xbf\x60\xab\x58\x64\xf6\xf4\xdb\x97\xa4\x19\x88\
+\x54\x0d\xd7\xda\xbe\xdc\x66\x10\xc0\xee\xbf\x5c\xb4\x1f\x9a\x03\
+\x5d\x4b\x43\x25\x95\x29\x6c\xf6\xbe\x63\xe9\x08\x51\x32\x5b\x92\
+\xf7\xcc\x67\x26\x34\xcb\x57\x0c\x58\x1d\x79\x18\x2f\xeb\x7a\xe0\
+\x22\xdc\x99\xd3\xee\x4c\x62\xed\xbd\xe7\xb8\x61\x96\x17\x84\x87\
+\xd6\xa1\x11\xa9\xb9\x83\x32\x4a\xad\x1d\xd1\xde\x96\xf7\xf5\x27\
+\x5b\xc5\x6a\x8c\x12\x55\x4b\x96\x8f\x42\x48\x66\x84\x6b\x6d\x5f\
+\x0a\x3b\xdf\xf8\xda\xb4\xdd\x6f\x5e\xf4\x43\x94\xba\xdd\xc6\x15\
+\x1c\xa8\x18\x12\x41\x6b\x5d\x91\x56\x50\x94\x9c\xa1\x44\xc6\xa4\
+\x6d\xfc\xac\x58\xb5\x76\x6d\x4f\x7a\xe4\x93\x8f\xd7\xf0\xc6\x05\
+\xb0\xd6\xc9\x2e\x2f\xa1\xf7\x80\x9b\x12\x8b\xa0\xb3\x01\x57\x7a\
+\x94\x95\xc0\x1e\xb5\xda\x04\x58\x47\x88\x15\xa4\xc4\x0c\xc8\x28\
+\x75\xed\x0e\x5b\x6f\xf6\x79\x25\xd2\xa6\x54\x62\x2b\x08\x76\x8c\
+\x5e\x83\x66\xcb\x47\x8f\x18\x5f\x36\x25\x6b\x82\xad\xae\x5d\xf8\
+\x11\xb1\x56\x30\x4a\xb1\x19\xc2\x71\x02\x9b\x14\x2d\xdf\x2c\x33\
+\xa8\x8b\x44\x58\xad\xb5\x6e\xcf\x28\x19\x8e\x92\xe1\x20\xc3\x05\
+\x99\x04\xda\x17\x11\xe5\x5a\x42\x02\x88\xa8\x55\x5e\x4f\xcf\x96\
+\x0f\x7f\x6e\x73\xd7\xc7\x03\x80\x7f\xe7\xb2\x8d\x44\xe4\x75\xf2\
+\x12\x51\x9b\xa8\x33\x1a\xad\xf5\xba\x17\x16\xbe\x79\x53\x77\xd4\
+\x93\x26\x1f\xad\x01\x36\xaa\x35\xa2\x98\xf5\x11\x7c\x1a\x70\xf9\
+\x24\xce\x8e\xb2\xfe\xe5\x89\x2c\xf4\x63\xe0\x6b\xa4\x5b\x05\x8d\
+\x02\xee\xf1\x82\xb0\x26\x93\x30\xe0\xc3\x38\x02\x46\xf4\xc5\xf1\
+\x9d\x22\xb2\x42\x44\x66\xe7\x96\x44\x4a\x12\x45\x41\x87\x16\x66\
+\x53\x85\x33\x88\xe8\xf8\x54\x51\x72\x58\x8e\x70\x94\xda\x0b\xe0\
+\x7c\x04\x94\x28\xab\x89\x49\x08\x6c\x9f\x2a\xde\x0b\x28\x78\x56\
+\x22\x51\xfa\x8c\x72\xc4\x37\xb7\xca\x6c\x11\xe9\x48\xc6\x9c\x9c\
+\xe9\x97\x29\xe9\xc7\xdd\x51\xcf\x03\xa4\x0b\xc8\x1d\x80\x0f\x3c\
+\x50\xe9\xfb\x27\xb0\xf3\xfe\x3d\xb8\x89\xbf\x04\xbb\x29\xa5\x00\
+\x6c\xce\xde\x33\x4b\x94\x39\x05\x58\x60\x0d\x0e\xab\x85\x6b\x28\
+\x5b\x0f\x3c\xa8\x44\x2e\x57\x22\xab\x95\x24\x11\x42\xf2\x65\x01\
+\xbe\xea\xdf\xb9\xac\xe2\xb8\x42\xa2\x64\xab\xb4\xb9\x3a\x65\xf9\
+\xe6\x56\xf7\xc2\xc0\x3d\xfa\x3c\x19\x02\x91\x6e\xa5\xd4\xac\x05\
+\x47\x6c\x5a\x56\x6d\xbd\xcf\x5d\xcb\x27\x88\xc8\x57\x73\xfa\x7e\
+\x95\x5f\x26\xab\xc5\x28\x7c\x1e\xb4\x7d\x51\x4d\xdf\x39\x61\x69\
+\xb4\x80\xd2\xc9\xa9\xcf\x4c\xf2\x34\xe7\xf4\x00\xd6\x4a\xb4\x54\
+\xfe\x9c\x09\xc0\x43\x5e\x10\x56\x9b\xc9\xd2\xf5\x12\x0f\x46\x59\
+\xbf\xeb\x89\xa3\x26\xae\x10\x91\x39\xb9\xe5\x91\x59\x09\x24\x8a\
+\x96\x56\x10\x97\x93\x69\x01\x26\xce\x5d\xda\x26\x4a\xb6\x76\x11\
+\xae\x1c\xd1\x07\x30\xc7\x40\x66\xe9\x12\xe1\x37\x19\xc9\xcc\x7c\
+\xec\x88\xf1\x37\x54\xd2\x26\x11\x39\x4f\x84\xd6\x42\xbf\xc4\xdc\
+\xd2\x74\x4e\x38\x63\xfc\x0a\x4b\x88\x07\x1d\x45\xb8\x5c\xe8\x52\
+\x61\x69\xf3\x10\xa5\x2d\x82\xaf\x4e\x2c\x82\x61\xa0\x22\xe8\x1b\
+\x98\x68\x17\x2e\xb4\x63\x7c\xcd\x9c\x6b\xfa\xa2\x06\x4d\xc4\xed\
+\xb6\xd4\xef\xf1\x22\x5c\x09\xb2\x22\x21\x84\xea\x9f\x06\x00\x7d\
+\xd2\xde\x77\x2c\x2d\xe5\xa8\x02\xc0\x88\xa6\xb5\xbb\x0a\xc6\x04\
+\xdc\x4d\xc8\x8a\x8f\x5e\x11\xf9\x97\x42\x1e\x57\xc2\x7c\x25\xea\
+\x9c\xa6\x26\x99\xb2\x60\xe6\xa6\x87\x3e\x3a\x73\xdc\x3d\x95\xbc\
+\xfb\x3e\x77\x2d\xdf\x02\x38\xc9\xb2\x57\x3f\xa3\x99\x17\x5e\x41\
+\xa1\xc9\x9b\xcb\xfb\x67\x57\x9b\x19\xbd\x2c\x2c\x4d\x7e\x83\x3b\
+\x72\x3a\x18\xda\x16\x24\xbb\x28\xd0\x87\x44\x59\x3f\xf2\x82\xf0\
+\x48\x0c\x47\xba\x3a\xbd\x09\x98\x6b\xb3\x59\x66\xcb\x78\x07\x7d\
+\xac\xc4\xb5\xdc\x4b\x3f\x76\xc4\x84\xd5\xfe\x5d\xcb\x2f\x07\x2e\
+\xd3\x68\xe2\xd8\x04\x6f\xb6\xf1\xfa\x3c\x8d\x9e\x83\x3b\x54\x0c\
+\x00\xef\xae\x1f\xf9\xb7\x31\xed\x9d\xbb\x16\x2f\xef\x74\x2c\x2a\
+\x93\x11\x85\xee\xbb\x5a\x8b\x9a\x9e\x63\x32\x1d\x1f\xaa\x33\x99\
+\x2e\xa5\x55\x07\xa2\xe7\x27\x82\x9e\x12\x16\xc4\x71\xe6\x90\xa7\
+\x3e\x3b\xe1\x9d\x52\xf5\x55\x80\x39\x80\x67\x66\x7b\x6b\xef\x9f\
+\x8b\x14\xa7\x2f\x7f\xf8\xb0\xb1\xf9\x26\x6f\x7f\x20\xdd\x06\x52\
+\x30\x7d\xe8\xcc\x72\x62\xbd\x7f\x03\xca\x07\xab\x7e\x03\x93\x8d\
+\xbc\x60\xbb\x7d\x83\x38\x87\x02\x6f\x44\x59\x7f\x52\xfe\x09\xff\
+\xce\x65\x6d\x4a\xc9\x6b\x22\x32\x0e\x8c\xa4\x9c\x1f\x5e\x2d\x8e\
+\xe3\x23\x9e\x3c\x7a\x33\x97\xce\xa2\x2c\x76\xbd\x69\xd1\x83\x68\
+\xf9\xaf\x9c\xa0\x17\xe9\xd1\xcf\x1e\x3b\x69\xe5\xee\xf3\x16\x8d\
+\xca\xb4\x64\xde\xeb\x17\xf4\xd4\x43\x4f\x1c\xb5\xe9\xbe\xb5\xd6\
+\x03\xf0\xa1\xf9\xcb\x66\x88\x30\x3f\x99\xd2\x92\xb2\x01\x34\x7a\
+\xb9\xd6\x4c\x7e\xf8\xb0\xb1\x05\x56\x4f\x5e\x10\x2e\x22\xfd\xa3\
+\xbb\x25\xca\xfa\x43\xe7\x1c\x0a\x60\x6f\xdc\x17\xf8\x7d\x99\x82\
+\x77\x02\xfe\xec\x05\xe1\xdd\x5e\x10\x16\x64\x09\xb7\x71\x80\x3f\
+\xe2\x78\x6e\xc0\x90\x17\xce\x18\xbf\x2e\x8e\xb9\xc4\x10\xdb\xc4\
+\xd6\xcb\x45\xef\xd4\x1a\xad\xf9\xc9\xd4\x5b\x16\xd7\x9c\x9c\x41\
+\x91\x6f\x1c\x52\xa8\x07\x28\x94\x17\x6a\xad\xc1\x60\xaf\x3b\xfe\
+\x3d\x4a\xa3\x7f\x92\x7c\x56\xb1\x6d\x7f\x6c\x0f\xad\xb9\xa4\x98\
+\xf8\x16\xae\x69\xe0\x63\xb6\x2f\x73\xf0\x82\x70\x2b\x2f\x08\xef\
+\x06\xfe\x4c\x79\xe2\xff\x1e\xd8\xd7\xe5\x5d\xe5\x8c\x0f\x10\x65\
+\xfd\xb5\x18\x5f\xb3\x4a\x4c\xb4\x0e\x05\x5e\xf4\x82\xf0\x7b\x79\
+\xd1\x3f\xa6\x01\x1b\x39\xee\x4f\x7d\x59\xad\xf5\x35\x26\xcc\x7a\
+\x7f\x87\x25\xc1\x15\xb5\xd6\x13\xb4\xe6\x8a\x0a\xda\x92\x8a\x62\
+\x41\x2f\x87\x51\x03\x05\xc2\xc1\x40\x6b\xae\x40\x33\x81\x7e\x82\
+\xe7\x33\xf2\xc2\x58\x6b\x97\xbf\xa0\x8b\x01\x36\xc2\x66\x0e\xf5\
+\x82\xb0\xc3\xa6\xb3\x7d\x11\xd3\xe7\xe5\x30\x17\xe3\xcb\xe9\x34\
+\x63\x2f\x19\x0f\xc3\x7a\x9e\x96\x0a\xf7\x92\x0f\x0f\xb3\x66\x5f\
+\xe8\x05\xe1\x2c\xcc\xfa\x3f\x0d\x3d\x40\xea\x94\xf1\xe8\x11\xe3\
+\xbb\x63\xad\x4f\x8a\x75\xac\x93\x91\x20\xff\xeb\x89\xb5\x3e\x61\
+\x97\x1b\x5f\x3f\xa0\x82\xb6\x0c\x44\x91\x24\x9e\xbf\x42\xce\x17\
+\x16\x1d\xe6\x0a\x15\x61\xcf\xdb\x96\x1c\x80\xd6\x27\xe8\x22\xc2\
+\xdb\x51\x40\xc7\x5a\x9f\x14\x1e\x3e\xce\x65\xa2\xfd\x17\x4c\xdf\
+\xa4\x16\x6d\xfb\x74\x21\xa6\x8f\x2b\xd9\x32\x3f\x2f\xca\xfa\x27\
+\x97\xf3\xe0\xae\x28\x20\x4a\x94\xf5\x2f\x01\x8e\xc1\xbd\x5e\xcd\
+\xc7\x38\x8c\x57\xac\x2b\xac\xcc\x3f\xd2\x62\x02\x26\x58\x30\x73\
+\xc2\x83\x71\xac\xaf\xcd\x1f\x05\x74\x32\x0a\x98\x73\x73\x77\x98\
+\xf7\x6a\xd5\x91\x48\x0b\x97\x81\x45\x53\x40\xde\x72\x31\x31\x5b\
+\xaf\x16\xd3\x6e\x7d\xb3\x15\xad\xe7\x26\xc1\xa6\xe3\xfc\xe9\xcb\
+\xb4\xfb\xda\x47\x67\x8c\x77\x2d\xf7\xb0\x7d\xe2\x8a\xfe\xf9\x03\
+\x4c\x9f\x3a\x93\x64\xe4\x61\x3d\x70\x8c\xa5\x59\x59\x54\x1c\x11\
+\xc7\x26\x56\xdc\x11\x93\xa4\xa9\x12\xb8\x36\x20\xca\x86\x38\x8d\
+\x63\x7d\xb6\xd6\xf1\x12\x9d\xc8\x03\x05\x1d\xca\x56\x5a\xeb\xef\
+\x54\xda\xee\x04\x92\x89\x2f\x04\x99\xa1\x61\x06\xc2\xcc\x51\xa3\
+\x26\x75\x02\x8c\x1a\x35\xa9\x13\x25\x33\x95\x62\x46\x93\x64\x66\
+\x28\xe1\xc2\x6a\xcb\x06\xd0\x9a\xef\x68\xad\xb7\x32\xc4\x1f\xf0\
+\xf5\x2f\xd1\xb1\xae\x44\xa3\xe9\xea\x1b\x57\x5f\x16\xe3\x2e\x60\
+\xc7\xb4\xc4\x98\x2e\x6c\xa8\x40\x91\x5f\x89\xb2\xfe\x82\x52\x37\
+\x4d\xbd\x79\xf1\xa7\x35\xfa\xd7\xb1\x26\x37\x02\xe4\x56\x04\x3a\
+\xee\xd3\xb1\x9e\xfe\xf2\x89\xdb\x36\x3c\x0f\x71\x25\xd8\xfd\xe6\
+\x37\xf6\x10\xe4\x71\x11\xc9\x50\x20\x4b\xe4\x84\xca\x43\x9f\x38\
+\x6a\x62\xc9\x08\x27\x5e\x10\xee\x85\x49\x4e\xe5\x8a\x38\x5a\x0a\
+\x35\x07\x8a\xdc\x90\xa1\x62\xff\x81\x89\x20\x72\x1f\x46\x2b\x38\
+\xc0\x93\x75\xd7\x5f\x2e\xfa\x95\xd6\xfa\xa8\xe4\x8b\x8a\x73\xc3\
+\x69\x4c\x1c\xeb\xe7\x62\xad\xa7\xbd\x36\x6b\xfb\x0d\x1a\x2a\x76\
+\xd7\x5f\x2e\x6a\x12\x91\xa7\x04\x76\x45\xf2\x14\x3e\x89\x3c\x21\
+\xdc\xf6\xd4\xd1\x9b\x0d\x58\x0a\xdb\xc0\x91\xfb\x62\x62\x07\x7f\
+\x8c\xca\xe2\x27\x16\x63\xc3\x84\x8a\xcd\x47\x9d\x82\x45\xc7\x98\
+\xd0\xeb\xf7\xd9\xe3\x91\x28\xeb\xaf\xdd\xf9\x17\xaf\x8f\xd5\xe8\
+\x97\x74\xac\xc7\x24\x12\xb5\x25\xbe\x61\x88\x58\x9f\xfb\xfa\x97\
+\xa7\x6c\xd0\x60\xd1\xbb\xdc\xf8\xfa\x39\x22\x72\x49\xca\x66\x13\
+\x62\x34\x7e\x53\x9e\xfe\xec\x07\xde\xb2\x91\xba\xf6\xc1\x10\xfc\
+\x00\x60\x2a\xb5\xe7\x6d\xdc\xf0\xc1\xa2\x8b\x51\x8f\x70\xf1\x79\
+\xe8\xc1\x04\xaa\xba\x6f\xc2\x26\xa3\x87\xb5\xb5\x78\xb3\xe3\x42\
+\x81\x2a\x99\x0e\xba\xb5\xd6\xfb\x2c\x3e\x65\xc7\x0d\x12\x2e\x7e\
+\xa7\x1b\x5e\x9b\x26\x22\x8f\x20\x0c\xcb\xdf\x53\xc0\xfe\x5d\xb3\
+\xb6\xeb\x7b\x8b\x97\xbe\xd3\x8d\x21\xf8\x74\xca\x3b\x25\x97\xc3\
+\xfb\x2f\x5c\x7c\x31\x06\x93\x30\xc2\x85\xf1\x1b\x8f\xa2\xc5\x6b\
+\x2e\x50\xaa\xe4\x18\x21\xd6\xff\x8a\xb5\x9e\xfa\xef\xd3\x76\x1e\
+\xd2\x84\x11\x3b\xcc\x7b\x75\x13\x81\x67\x44\x64\x33\xf2\x56\x18\
+\x89\xdd\xc1\xda\xae\x6e\x16\x2f\x1d\xac\x36\xb9\x00\xbf\xc6\xc4\
+\x6a\x78\x7f\x26\x8c\xc8\x87\x17\x84\xf3\x30\xc1\x09\xea\x82\xa6\
+\x4c\x86\x09\x9b\x98\x85\x7b\x5c\x34\x0a\xe8\x58\x13\xf5\xf4\x46\
+\xab\x3a\xd7\xd9\x94\x31\x2c\xc3\x04\x4a\xea\xc4\x18\x55\x74\x16\
+\x1d\xb9\x73\x49\xc8\x59\x6b\xb4\xd2\x6e\x8f\x8e\xbc\xdf\x69\xe7\
+\xc6\x0b\x6c\xb7\xe9\xd8\x31\x7b\xb5\xb6\x78\x1e\x14\x2e\x31\x11\
+\x23\xaf\xbc\xfe\xaf\xb7\x9c\x29\xe6\x6a\xc4\xbc\x28\xeb\x1f\x57\
+\xcf\x02\xa1\x71\x9e\x41\xdb\x3a\xce\x2f\xa5\x86\xbc\x41\xbd\x7d\
+\x7d\xbc\xb7\x7a\x2d\x23\xdb\xdb\x52\x47\x01\xa5\x94\xd7\x3a\xcc\
+\xdb\xb7\xab\x3b\xaa\x4a\x87\xef\x05\x61\x42\xa1\x4a\x43\xbf\x01\
+\x30\x7a\x64\x3b\xc3\xbc\x26\x62\xad\x0b\xd4\x46\x89\x99\xc7\x5b\
+\x2b\x56\x0f\x86\xf8\xae\x3e\xda\xae\xd6\x02\x4b\xa1\x51\xc9\xa3\
+\x5d\x21\xdf\xe6\x60\xe2\xdd\x7e\x0d\xb8\x1b\xb7\x2d\xe2\x00\x74\
+\xae\x5b\xcf\xba\xf5\xdd\x03\x88\x9f\xac\xb7\x9b\x9b\x33\x34\x65\
+\xaa\xa2\x23\x18\xc2\x57\xf5\xd0\xf0\xd6\x61\x8c\x18\xde\x9a\xb7\
+\xd6\xb7\x2b\x94\xd8\xe6\x35\xec\xec\x2a\x36\xf1\x2e\x87\xf7\x30\
+\x7d\xf1\x35\x4c\xdf\xb8\xc2\xc9\xb8\xfa\x74\x50\x18\xea\xbc\x81\
+\xbd\x51\xd6\x7f\x05\x63\x8e\xf4\xe3\xe2\xc4\x91\x18\xf5\xb1\x73\
+\x3f\x7b\xe5\x9a\x75\x8c\x1e\xd1\x4e\x46\x49\xff\x48\x10\xf7\x33\
+\xc4\x30\xaf\x89\x78\xbd\xd9\x41\x6c\x04\x9a\x9b\x32\x8c\x19\xd9\
+\xee\xfc\xf2\xa3\x9e\x5e\xde\x2a\xe1\xff\x68\xd1\x89\x09\xa4\x9d\
+\x9a\x38\xd2\x0b\xc2\x21\x5d\xd6\x6e\xd0\xcc\xa1\xf6\xc5\x9f\xb6\
+\xc7\x15\x5e\x10\x36\x63\x82\x19\x26\x4b\xa5\xbd\x81\x5c\x74\x6d\
+\xad\x35\xab\x3a\xcd\x54\xa0\xf3\x75\x03\x3a\xce\xc9\x04\xcd\x4d\
+\x19\xba\x4b\xe4\x17\xac\x15\x4a\x84\x8d\x46\x75\xe4\xa5\x02\x4c\
+\xc8\x6e\x98\x2d\x8e\x35\xcb\xde\x59\x99\xc6\x7c\xeb\x31\xfe\xf9\
+\xc9\x12\xf7\xff\xa7\x8e\x75\xc1\x76\xcc\xa3\xf6\xb8\xc8\x2a\x9b\
+\x36\xc7\xc8\x14\xdb\x00\xdb\xf6\xf5\xc5\xdb\x74\xae\x5b\xbf\x5b\
+\x5b\x8b\x37\x3e\x6d\x14\xd0\x40\x26\xa3\x4a\x66\x18\xad\x05\xc6\
+\x7f\x41\x0d\x48\xfb\x66\xd8\x40\x78\x7b\xe5\xea\x65\x3d\xbd\x7d\
+\x7f\xc5\x6c\xd8\xbc\x9c\xf7\x77\x71\xad\x4a\x9a\xa1\xc0\xfb\x8a\
+\x01\x8a\x61\x3b\xee\x75\x7b\x14\x44\xcd\xda\x78\xce\x33\x41\x1c\
+\xeb\x6f\xe7\x6f\xbc\x24\xa3\x80\x09\xf2\x21\x2b\xb5\xd6\x11\x46\
+\x82\xaf\x55\x2f\xd1\x05\xac\xe9\x18\xde\xea\x79\xcd\x4d\xa3\xd2\
+\x89\x0f\x5a\xf3\xdd\x95\xdf\x9c\x96\xad\xb1\x8e\x0d\x8a\x46\x09\
+\x81\x0d\x47\x1c\xeb\x0b\xb4\xd6\xbf\xcd\x09\x84\x85\x3b\x86\x68\
+\xad\x47\x01\x17\x44\x59\xbf\x0d\x23\xe8\x8d\xc4\x98\xbd\x6f\x8f\
+\xd9\x5f\xdf\x0f\x38\xc4\x1e\xfb\xd9\x73\xdb\xdb\x7b\x46\x02\x99\
+\x28\xeb\xb7\x8d\x1d\x33\xe2\x82\xb6\x16\x6f\xd4\x00\xa1\xaf\x7f\
+\xc3\xe7\xb7\x1a\x5d\x2f\x0f\xaa\x21\xc7\xfb\x7a\x04\x28\x85\x15\
+\x67\xed\xa1\x47\x7e\xef\xc9\x63\xb4\xd6\x4f\xc4\x5a\x6f\x5b\x30\
+\x15\xf4\x7f\xa9\x57\x7b\x41\x18\xdb\x6c\x1b\xab\x71\x47\xec\x4c\
+\xc5\xa6\xff\xfd\xfc\x2c\x11\xb9\xda\xa4\x70\xa5\x20\x0d\xb0\x98\
+\x1f\x2f\x6b\x91\x63\x16\x9d\xbc\x7d\x63\xa4\xce\x21\xc0\x7f\xec\
+\x08\x00\xb0\x6a\xf6\x9e\xab\x62\xad\x0f\x8f\xb5\xee\x4c\x21\x3e\
+\x18\x3a\xfd\xcc\x0b\x42\x97\x73\xa4\x13\xe3\x7f\xf4\xdc\x89\x5a\
+\xeb\x9f\xc5\x5a\x4b\x9e\x59\x5a\xbe\x75\x4f\x67\xac\xf5\xe1\x8b\
+\xbe\x3c\xa5\xac\xd8\xff\x7e\xc6\x50\x33\x40\xdd\x47\x9c\xce\x73\
+\xa6\xbf\xa8\x63\x7d\x6c\x0a\xf1\x13\x08\xc6\x8a\xd9\x95\x88\x6a\
+\x00\xc6\x5e\xf9\xec\xf1\x5a\xeb\xb9\xb1\xd5\x94\x0e\xb4\xee\x01\
+\xad\xf5\xb1\x8b\x4f\xd9\xb1\xaa\x4c\x22\x15\x62\x48\x47\xe5\x46\
+\x31\x80\x2b\xc6\xef\x8e\x8d\xa8\xac\xeb\xfc\xbd\xe7\x6b\xad\x4b\
+\x39\x93\x0a\x26\x61\xd3\xb1\xe5\xca\xda\xf8\x07\xcf\x1c\xab\x35\
+\xd7\x16\x7e\xf9\x05\xc6\x1d\x68\xad\xcf\x78\xf3\xd4\x9d\xe6\xd7\
+\xed\x05\x0a\xe1\xea\xa3\x86\xa4\x8f\x6f\x14\x03\xb8\x0c\x35\xa6\
+\x36\xa8\x3e\xa2\xac\x7f\x25\xa5\xfd\x08\x15\x70\x9d\x17\x84\x5f\
+\x74\xdd\x30\xe6\x8a\xa7\xbf\xa8\xd1\xd7\xc5\x5a\x2b\xed\x14\xfa\
+\x38\x7b\xc9\x69\x3b\x0f\x3e\x8e\xb1\x1b\xae\x3e\x6a\x88\xf1\xcb\
+\x50\x33\xc0\x0e\xae\xb4\x69\xf5\x40\x94\xf5\xbf\x4f\x69\x07\x09\
+\x05\x5c\x9f\x96\x58\x71\xd4\xe5\x4f\x7e\x41\x6b\x7d\x7d\xac\x51\
+\xa9\x5f\xbe\x59\x65\x9c\xbb\xf4\x6b\xbb\x7c\xbf\x51\xed\xb7\x7d\
+\xb3\x83\xe3\xf2\xff\x09\x06\x68\x02\x76\x69\x50\x9d\x00\xd8\x6c\
+\xe2\xa5\xd6\xe4\x0a\x98\xe7\x05\x61\x6e\x67\x6d\xc4\x65\x4f\x1c\
+\xa7\x35\xf3\xb4\xd6\x45\xc4\x27\x7f\xce\xcf\x2e\x3f\x7d\xb7\x46\
+\x1b\x9f\xe5\x27\xf4\x85\x00\x00\x03\xdb\x49\x44\x41\x54\xec\x82\
+\x5b\x06\x68\x08\x03\x34\x4a\xe0\x78\x11\xa3\x44\x49\x53\xc0\xec\
+\x81\x49\xb4\xdc\x30\x44\x59\xff\xbb\x36\xab\xb7\x8b\x11\x32\xc0\
+\xcf\xbd\x20\x9c\xe6\x35\x37\x21\xc2\x29\xc6\x6d\xcb\x44\xec\xc8\
+\x57\xf1\x5a\x04\xef\x9c\x39\xb5\xae\xe1\xeb\x1c\x70\x79\x62\x75\
+\x61\xfa\xb4\xee\x68\xc8\x08\x60\x35\x78\xcf\x39\x2e\x37\x4c\x0e\
+\x28\x6a\xc3\x05\x94\x49\xa7\x96\x51\xea\x14\xad\xf5\x29\xfd\x5f\
+\x7a\xaa\xd0\x77\xc9\xbb\x67\x4e\x1d\x2a\x45\x8f\xab\x6f\x9e\x6b\
+\x94\x3a\xb9\x91\xcb\x40\xd7\x90\x75\x40\xbd\xe3\xe0\xb9\x60\xd3\
+\xa9\xa5\x06\x5d\x4c\x32\x84\x16\x2d\xed\x8a\x85\xbe\xcb\xdf\xfb\
+\xe6\xb4\x8a\xdc\xd3\x07\x0b\xdb\x27\x2e\xa7\x97\x86\x59\x3f\x6f\
+\x08\x06\x98\x4c\x95\x7e\xef\x83\x81\x4d\xb5\x3a\x60\x8f\xdd\x86\
+\x68\x29\xb0\xe1\x2f\xfa\xf2\xe7\xac\x3a\x7b\x4f\x57\xb2\xa7\x46\
+\xe0\x20\xdc\x31\x62\xff\x23\x19\xe0\x7e\xdc\xf9\x6a\xd2\x62\xd7\
+\x37\x0c\x51\xd6\x3f\x13\x23\x0f\xe4\xda\x53\x62\xd8\xd7\x5a\xeb\
+\xec\x9a\x6f\x7d\xb0\x54\xc4\x94\x46\xc0\xd5\x27\x9a\xfa\x87\x8f\
+\xcf\xa1\x61\x0c\x10\x65\xfd\x45\xc0\xbd\x8e\xcb\x9f\xf4\x82\xd0\
+\xe5\x3b\x58\x77\x78\x41\xb8\x2f\x70\x14\x29\x8e\x7f\xba\x68\xf8\
+\xef\xeb\x8b\xa5\xa7\xb7\xef\x28\xfb\xcc\x50\xb5\xef\xc3\xc0\x27\
+\x1d\x97\xef\xb5\x7d\xd9\x10\x34\x5a\x15\x7c\x55\xa9\x6b\x56\x52\
+\x6f\x18\xbc\x20\xdc\xd2\x0b\xc2\x3b\x30\x81\x96\x9c\x6e\xd4\xc9\
+\xd7\xdf\x67\x5d\xd2\xed\xbd\x0f\x78\x41\x78\x87\x17\x84\x5b\x36\
+\xb8\x8d\x4d\x94\xe9\xa7\x46\xd6\xdf\x68\x06\xb8\x17\x78\xd5\x71\
+\x6d\x67\xa8\xdd\xdd\xbb\x14\xac\x1b\xf5\xa5\x98\x84\x97\x25\x23\
+\x8b\x24\x70\xcc\x55\x47\x00\x2f\x79\x41\x78\x69\x9e\xdb\x7b\xbd\
+\x71\x05\x6e\x17\xbb\x57\x71\x8f\xa2\x75\x41\x43\xcc\xc2\xf3\xe1\
+\x05\xe1\x4c\x8c\x33\x83\x0b\xe7\x47\x59\xdf\xe5\x49\x5c\x6d\x5d\
+\x0a\x63\x8e\x7e\x09\x50\x49\x6c\x9d\xc4\x6c\xa8\x92\x0f\x61\x19\
+\x46\xcb\x78\x43\xbe\x0d\xdf\x60\xe0\x05\xe1\x79\x98\x48\x9e\x2e\
+\x1c\x19\x65\xfd\x7a\x65\x0f\x4b\x45\xc3\x19\x00\xc0\x0b\xc2\xdf\
+\x62\x32\x59\xba\x70\x15\xf0\xcd\x24\x74\x59\x0d\xe5\x8f\x07\x8e\
+\x04\x4e\x00\x76\xad\xf0\xb1\xe5\x18\xb9\x00\x4c\xaa\xf7\x4a\x5c\
+\xaf\xc1\xe8\x37\xae\x03\x6e\x8f\xb2\x7e\x45\x89\x9d\x8b\x61\x53\
+\xf5\x7e\x9f\xd2\xc2\xf0\x3d\x51\xd6\x3f\xa4\x96\xf2\xab\xc1\x50\
+\x31\xc0\xe6\x18\x4d\x56\xa9\xe8\xda\x2f\x61\x62\x13\xdf\x59\x89\
+\xd2\xc3\x0b\xc2\x8d\x30\x43\xf4\x67\x30\x4e\x96\xd5\x4c\x67\x7f\
+\xc6\x78\xd9\x2c\xb1\x65\x4d\x04\xe6\xe1\x0e\x69\x93\x86\x18\x13\
+\x4c\xeb\x56\x60\x7e\x94\xf5\xcb\x46\x34\xb5\xf3\xfd\x0c\x8c\x2f\
+\x65\xa9\x38\x7e\x6b\x81\x1d\x6a\x4d\xd7\x5b\x0d\x86\x84\x01\x00\
+\xbc\x20\x3c\x1c\x33\x15\x94\xb3\xc3\x5f\x84\x99\xf7\x1e\x07\x9e\
+\xc7\x78\xf2\x74\x61\x42\xa5\x4c\x06\xb6\xc2\xe4\x1e\xf8\x28\xd5\
+\xab\xb2\x5f\x05\xce\x8a\xb2\xfe\xdd\x8e\x36\x1e\x86\x99\x93\xb7\
+\x4a\xbb\x5e\x02\xbd\xc0\xff\x62\xa2\x7c\xbc\x8a\x49\xd0\xf4\x2e\
+\x46\x15\x3e\x02\x33\x2a\xed\x8d\x89\x99\xbc\x99\xa3\x8c\x04\x7d\
+\x98\xa1\xbf\xd2\x38\x0c\x83\xc2\x90\x31\x00\x80\xb5\xcc\xb9\x76\
+\xc8\x2a\xec\xc7\x1a\xcc\x5c\xfb\xc3\x72\x59\xc9\xbc\x20\xf4\x80\
+\xd3\x31\x19\x37\x1b\x25\xf8\x95\xc2\x49\x51\xd6\x77\x25\x7a\xac\
+\x3b\x86\x94\x01\x80\x24\x30\xf5\x65\x0c\x26\x18\x4f\xe5\x88\x81\
+\xeb\x31\xf1\x72\x52\xa3\x64\xb9\x60\x43\xe5\x5d\x8c\xc9\x73\x34\
+\x14\x96\x53\x1a\xf8\x56\x94\xf5\x1b\x91\x2f\xc8\x89\x21\x67\x00\
+\x00\x2f\x08\x3f\x81\x99\x73\xeb\x92\x14\xc1\x81\x87\x31\x31\x0c\
+\x9f\x19\x4c\x21\x5e\x10\x4e\xc5\xc4\xe2\x6b\xa4\xe2\xea\x6d\x8c\
+\x4c\x52\x2e\x2c\x5f\xdd\xb1\x41\x18\x00\xc0\x0b\xc2\x09\x98\xaf\
+\x73\xd0\x71\xf1\xf3\xb0\x04\x23\x67\xfc\xaa\x5c\x08\x9a\x6a\x61\
+\x43\xb8\x1c\x8d\x59\x6d\xa4\x45\x55\xaf\x15\x7f\x04\x8e\x8b\xb2\
+\xfe\xd2\x3a\x96\x59\x31\x36\x18\x03\x24\xf0\x82\x70\x3a\x70\x06\
+\x46\xa2\xaf\xda\xbb\x13\x93\x12\xf5\x0e\x8c\x34\xfe\x48\xbd\x33\
+\x9e\x16\xc3\xee\xda\xed\x83\x59\x7d\xcc\xc4\x91\x8c\xa9\x0c\xfa\
+\x80\xf9\xc0\x9c\x28\xeb\x3f\x5e\xc7\xe6\x55\x8d\x0d\xce\x00\x09\
+\xec\x52\xf1\x53\x18\x69\x79\x2f\x4c\xf6\xac\x34\x2c\xc3\x78\x0a\
+\x3d\x8f\xf9\xda\x1f\xd8\x50\xae\x57\xd6\x75\x6d\x3f\xcc\xa8\xb0\
+\x0b\xb0\x25\x6e\x05\xd4\x42\x4c\x18\xf7\x05\x98\x35\x7e\xc3\x97\
+\x78\x95\xe0\xff\x01\x4c\xf0\xc7\xcb\x53\xcf\xc6\x43\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\xbc\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x39\x49\x44\
+\x41\x54\x38\x8d\x55\x93\x4d\x4c\x5c\x55\x18\x86\xdf\x73\xef\xb9\
+\xc0\xcc\x38\x3f\x74\x18\x3b\x14\x11\x09\x2d\x41\x6d\x6a\x43\x3a\
+\x6a\xa2\xee\x6c\x13\x43\x42\x9a\x2e\x6c\x5c\x90\x92\x74\xa3\xac\
+\x4c\x5c\x75\xa1\x09\x8b\x1a\xdd\xd1\x59\x5c\x37\x90\x26\xba\xd2\
+\x26\x4d\x6c\xdc\x54\x93\x2a\x86\x85\x03\x4c\xd0\x2a\xa9\x25\xb4\
+\x22\x04\x9a\x3a\x0c\x65\xee\x65\x98\xf3\xf3\x9d\x73\xdc\x88\xa1\
+\xcf\xfa\x7d\x9e\xdd\xcb\x9c\x73\x38\xcc\xe4\xe4\x24\x4f\xa7\xd3\
+\x67\x94\x52\xc3\x1e\x77\xaf\x4b\x29\x9d\x6c\x51\x55\x29\xb5\x24\
+\x84\x98\x2f\x97\xcb\xf2\xf0\x9e\x1d\x04\x18\x63\x2c\x0c\xc3\xc1\
+\xf6\xb4\xfb\x66\xf0\xa5\xec\xa9\x6c\x7b\x1e\xe9\x44\x06\x41\xdf\
+\x26\x6a\xd1\x06\xfe\xde\xd8\x72\xf3\xdf\x89\xe5\xdd\x4d\x37\x16\
+\x86\xe1\x6f\xee\x3f\x91\x1f\xc8\x53\x53\x53\x63\xbd\x43\x1d\x33\
+\x7d\xf9\x14\xcf\x65\x0c\x82\x40\xa1\xd9\xdc\x42\x47\x6c\xd1\x93\
+\xca\x21\x91\x37\xac\xef\xa3\xbf\x4e\xde\xb9\xd5\x9c\xbf\x2c\x2f\
+\x7f\xc8\x18\xfb\xc2\x39\xe7\x38\x00\x84\x61\x38\xd8\x3b\x94\x98\
+\xe9\xcf\x7b\x9c\x74\x0d\xb5\x7f\x08\x5d\x5d\x05\xac\xae\xae\xe0\
+\x59\xf8\x28\xbc\xc0\xb1\xf3\xb8\x89\x23\x05\x8b\xb7\x2f\xe8\x40\
+\x50\xeb\xda\xc5\xbd\x8b\x0b\x00\x16\x7c\xc6\x18\xef\x2c\x26\x6e\
+\x0f\x1c\xcb\xf6\x04\x5c\x80\x07\x3e\xb8\xef\xc3\x3a\x8b\xb5\xb5\
+\x87\x08\x3a\x23\xa4\x8f\x2a\x74\x14\x23\x18\xb4\xb0\xfc\x03\xa1\
+\xff\xcd\xd8\x7b\xf0\x6b\xf0\xd6\x74\xf8\xd5\x75\x2f\x9b\xcd\x96\
+\x8a\xa7\x6a\xa7\x79\xf7\x5d\x6c\x6c\x6e\x40\x6b\x82\x75\x80\x56\
+\x84\x28\x8a\xd0\x94\xbb\xd0\x88\xa1\xd1\x84\xb4\x2d\x04\xb9\x7d\
+\x64\x72\x12\x2f\x8f\xee\x0c\xf9\xbe\x7f\x8e\x13\x51\x29\xd3\xbd\
+\xcf\x78\x36\x46\xa1\x04\x78\x41\x03\xe6\x51\x11\x76\x2f\x85\x66\
+\xb3\x05\xa1\x24\x14\x63\x30\xd0\x30\x5c\xa0\xe7\x55\x81\x28\x96\
+\x28\x1c\x37\x30\x26\x39\xcc\x9d\xaf\x5f\x4b\xe6\x15\x5a\x91\x45\
+\x5b\x46\xc3\xfa\xfb\xf0\x4e\x6c\xc3\x45\x29\xd4\xbf\xad\xa1\x1a\
+\x6e\x82\x4f\x33\x1c\x3d\xde\x86\xf3\x57\xba\x11\x3d\x11\xb8\xf1\
+\x71\x0b\x67\xaf\x6a\xf8\xed\x9d\x25\x6e\x8d\xe1\x04\xc2\xca\x6c\
+\x80\xfc\x80\x46\xf1\x45\x05\xc0\x00\x99\x16\x46\xae\x64\xe0\x58\
+\x0a\xa4\x15\xa4\x51\xd0\x10\xe0\x47\x24\x46\x3e\x25\xd8\x0c\x81\
+\x88\x7c\x2e\x9a\xa6\xb2\xbb\x8d\x77\x07\x46\x62\x78\x9e\x84\x86\
+\x01\x40\x70\x8e\x80\x76\x82\x03\x81\xb5\x11\x02\x18\x28\xa6\xb0\
+\xb5\x2a\xa0\xa0\x40\x91\x87\x56\x6c\x17\x39\x80\xc5\xfa\x1a\x5c\
+\xaa\x60\xd8\x9f\x77\x18\x0a\x27\x0c\x3a\x9f\x53\x70\xcc\xc0\x41\
+\xc3\x82\x60\xa1\x41\xd0\x30\x50\xb8\xbf\x28\x20\xad\x40\x32\x9f\
+\x01\x11\x2d\xf1\x7a\xbd\x5e\x71\x3f\xb5\x2d\x77\xbd\x82\x93\xca\
+\x12\x2c\x27\x28\x46\x70\x4e\xff\x1f\x30\xd0\x88\x9f\x68\xc4\x91\
+\xc0\xe9\xf7\x2c\xf6\x85\x8f\x9b\xef\xe7\xd6\x85\x10\xb7\xbd\x72\
+\xb9\x2c\x1b\x8f\xdc\xd8\x1f\xb7\x3a\x74\xff\x59\x42\xc7\x31\xc2\
+\xec\x75\xc2\xef\x3f\x12\x24\x23\xdc\xfb\x45\x40\x3a\x8d\xfb\x55\
+\x81\x9b\x57\xf7\xa0\xa0\x51\x99\xc9\x9a\xfa\x43\x76\xa9\x52\xa9\
+\xc4\xcc\x39\x07\xc6\x18\x9b\x98\x98\xf8\xa0\xf7\x8d\xdd\x6b\x43\
+\x17\xea\x7c\x6b\x45\x22\xd5\x25\x91\xcc\x13\xa6\xc7\x1b\x38\xff\
+\xb9\x87\xdc\xf3\x06\x52\x18\x2c\xcc\x64\xcc\xf2\x8d\x67\x3e\x99\
+\x9d\x9d\xfb\xcc\x39\x67\x9f\x3a\xd3\xf8\xf8\xf8\x99\x64\x97\xfa\
+\x72\xe0\x9d\x9d\xa1\xec\x40\x8c\x74\xef\x3e\x2c\x14\x76\xd6\x19\
+\x1e\xdf\xf3\x70\xf7\xeb\xec\xfa\xf6\x03\x77\x69\x6e\x6e\xee\x67\
+\xe7\x9c\x7d\xea\x8d\x07\x8c\x8e\x8e\x26\x13\x89\xc4\x39\x22\x1a\
+\x66\x81\x2e\x11\x69\xbf\x15\xdb\x45\x22\x5a\x6a\x34\x1a\xdf\x57\
+\xab\xd5\xc8\x1d\x92\xfe\x05\x1f\x40\xce\x81\x8d\x7c\xc1\x5d\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x04\x0c\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8d\x89\x1d\x0d\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x0a\x03\
+\x0f\x10\x17\xf8\x3c\xc2\xb5\x00\x00\x03\x8c\x49\x44\x41\x54\x38\
+\xcb\xad\x94\x5d\x6b\x9c\x45\x14\xc7\x7f\x67\x66\x9e\x97\x6c\xd2\
+\xed\xae\xa6\x2d\x95\x48\x5b\xac\xac\x11\xb5\x14\x2b\x06\xc4\x10\
+\x09\xa2\x50\xfd\x16\x21\x17\xa5\x1f\xc2\x7b\xef\xbd\x28\xbd\xf0\
+\x3b\x68\xaf\x34\xda\x16\x44\xb1\x29\x41\xad\x36\xa5\x69\x2d\x41\
+\xf3\xb6\x9b\x7d\x49\x36\xbb\xcf\xee\xf3\x3c\x73\xbc\xc8\x6e\xd2\
+\xd7\x3b\x07\x0e\x73\x31\xcc\xef\xfc\x67\xfe\xe7\x1c\x51\x55\xfe\
+\xcf\xe5\x5e\x74\x30\x77\x41\xa6\x11\x33\xeb\x44\x67\x72\x95\xf3\
+\x00\x56\x74\x29\x53\xb9\x8e\xfa\x85\xab\x8b\x7a\xf3\x79\xf7\xe4\
+\x69\x85\x97\xa6\xa4\x9c\xe5\x72\xa5\x70\xa4\xf4\xd9\x1b\x93\x93\
+\x6e\xfc\xd8\x71\x57\x7a\xe9\x18\x00\xcd\x7a\x95\x5a\x75\x2b\x5b\
+\xbe\x7b\x37\xeb\xec\x36\xbf\x75\x56\xe7\xbf\xfa\x45\x1b\x2f\x04\
+\xce\xbd\x2b\x1f\x18\x6b\xbf\x79\xf3\xad\xb7\x0b\xef\x9c\x3b\x1f\
+\x09\x9e\x68\xb4\x84\x18\x03\x80\xf7\x9e\x7e\xbb\x81\x8a\xe5\xf7\
+\xdf\x96\x7a\x7f\xdd\xf9\xa3\xe3\xf3\xfc\xf3\xab\xb7\xf5\xa7\x67\
+\x80\x97\xa6\xa4\x9c\x79\xfb\xe0\xa3\xd9\x8f\xcb\xe5\xb1\x10\x1b\
+\x44\x04\xf1\x18\xaa\x1e\x54\x39\x4c\xac\xa4\xdd\x36\xaa\x9e\x46\
+\xbb\xcf\x8f\x0b\xdf\x35\x9c\xc9\x5f\x1b\x2a\x35\x43\x72\x96\xcb\
+\x95\x4a\xa5\x52\x28\x86\x9e\x76\x75\x15\x9f\xf5\x49\x76\xb7\xe9\
+\xed\xd6\xf7\xf7\x76\x9d\xde\xee\x36\xc9\xce\x36\x79\x9a\xb0\xb3\
+\xf1\x90\x62\xe8\xa9\x54\x2a\x85\x2c\x97\x2b\x43\x8e\x19\x1a\x10\
+\x17\xc6\x2e\x4e\x56\xce\x46\xd5\x95\x45\x5c\x18\x91\xee\xd5\xc9\
+\x3b\x75\xf2\x6e\x03\xdf\x6d\xe2\xbb\x4d\xf2\x6e\x93\xac\xd3\xa0\
+\xdf\xae\x63\xac\xa5\xba\xb2\xc8\x64\xe5\x6c\x14\x17\xc6\x2e\xce\
+\x5d\x90\xe9\x43\x97\xc5\xcc\x9e\x39\x35\x11\x24\xdb\xab\x90\xee\
+\xa1\xc9\x0e\x9a\x39\x40\x11\x14\x11\xf6\x9f\xac\x0a\x5e\xc1\x7b\
+\x48\xfb\x68\xba\x47\xa7\xb6\xca\x99\x53\x13\xc1\x9f\x77\xef\xcd\
+\x02\x37\x1d\x80\x13\x9d\x29\x17\x47\x9d\x26\x35\xe2\xc0\x40\xb7\
+\x86\x04\x11\x46\x40\xf0\xc3\xcf\xc6\xab\x62\x54\x51\xaf\xd0\xef\
+\x11\x39\x41\xbb\x0d\xca\x47\xc6\x9d\x13\x9d\x39\x50\x98\xab\x9c\
+\x2f\x8d\xc5\xe4\x5b\xbb\x44\x4e\xb0\x59\x07\xa3\x5d\x8c\xc8\x3e\
+\x54\x06\x76\x0c\x04\x8a\x57\x10\x45\x9c\x41\xd3\x36\x47\x4b\xaf\
+\x32\xac\xd5\x83\xc2\x36\x9a\x62\x9d\x60\xc4\xe0\xac\xe0\x8c\x60\
+\xcd\x10\x28\x03\xa0\x92\x2b\xe4\x5e\xb1\xb9\x62\x44\xf1\x16\xd0\
+\xf4\xc9\x4e\xb1\x46\x96\x5a\xad\xd6\xf4\x89\x38\x86\xa4\x47\xe8\
+\x1e\x87\x1e\x02\xbd\x57\x72\xaf\x64\x9e\x03\xe5\x1a\x8e\xb0\xd5\
+\x6a\x62\x8d\x2c\x1d\xb8\x9c\x79\xbd\x5e\x6f\xee\xa4\x61\x18\x13\
+\x38\x43\x60\x85\xd0\x0a\x91\x13\x22\x67\x88\x83\xc3\x88\x02\x43\
+\xe8\x0c\x81\x35\x38\x23\x84\x51\x4c\xbd\xb9\x9b\x66\x5e\xaf\x1f\
+\xd6\xa1\xfa\x85\xfb\xff\xd4\x33\x5c\x84\x73\x01\xce\xca\x3e\x74\
+\x00\x1b\x19\xc0\xc2\x41\x32\x67\x04\x67\x20\x08\x43\xb0\x21\x2b\
+\xff\xd6\x33\xd4\x2f\x1c\x00\xaf\x2e\xea\xcd\xac\xdf\xbb\xf6\xeb\
+\xf2\x7a\x12\x8e\x96\x10\xc0\x18\xc1\x5a\xc1\x59\x21\x0a\x0c\x91\
+\xdb\x4f\x62\x8d\x60\x06\x11\x8c\x1c\xe5\xd6\xbd\x8d\x24\xeb\xf7\
+\xae\x0d\x87\xc5\x41\xa7\x18\xa3\xf3\x2b\x6b\xad\xee\x83\x8d\x36\
+\x76\xa4\x08\x62\xf1\x5e\xf1\x03\x13\x72\x0f\x5e\x15\x55\x10\x31\
+\x98\xb8\xc8\xdf\x5b\x7b\xac\xac\xb7\x12\x63\x74\xfe\x99\x5e\x16\
+\x91\xc2\xa7\xaf\xf3\xc9\x44\xc9\x7e\x7d\xf6\x44\x21\x9e\x99\x2c\
+\x85\x05\x9b\xe3\x8c\x22\xba\x5f\x8b\x1e\x43\xa6\x42\x27\xb3\xdc\
+\x58\x6e\xa6\x2b\x9b\x9d\xe4\x7e\x35\xbf\x7c\xe3\x11\xdf\x03\x35\
+\x55\xed\x3f\x0e\x3c\x0a\x8c\xbf\x52\xe4\xcc\xcc\x69\xf9\xa2\x38\
+\x62\xdf\x9b\x3c\x19\xdb\x93\xa5\xd0\x1e\x2f\x06\x00\x6c\xed\xa4\
+\xac\x35\xfb\x7e\x79\x3d\xc9\x1b\x9d\x7c\xe9\x87\x87\xfa\x65\x75\
+\x8f\x47\x40\x0d\x58\x7b\x02\x38\x80\x16\x81\x71\xa0\xfc\xfe\x04\
+\x1f\x1e\x1b\x33\x53\x2f\x17\x38\xe7\x0c\xa7\x01\xd2\x9c\xd5\xda\
+\x9e\xde\xd9\x6c\xeb\xad\xdb\x6b\xfc\x0c\xb4\x80\x2a\xb0\xa9\xaa\
+\xd9\x73\x07\xec\x00\x6c\x81\x91\xc7\x22\x02\x04\xc8\x80\x64\x10\
+\x1d\xa0\xa7\x4f\x01\xfe\x03\xdf\x53\xd1\x84\xfc\x7f\x63\x50\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\x38\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x02\xff\x49\x44\x41\x54\x78\x5e\xed\x9a\x3f\x6c\x12\x71\
+\x14\xc7\xbf\x50\xe0\xc0\x1c\x68\x62\x8d\xa5\x85\xc1\x94\xb6\xb6\
+\x0e\x4a\xdb\xa1\x2e\x4d\x07\x13\x07\x75\x51\x13\x8d\xae\x26\x4e\
+\x1a\x1d\x1c\x35\x31\x4e\x26\xc6\x68\x9a\x98\x36\x65\xad\xb6\x89\
+\xba\x98\x26\x0e\x4d\xd4\x0e\xca\x60\xa1\x9a\x58\x6c\x2b\x71\x28\
+\x0d\xfe\x1b\x5a\xb8\xf4\x28\x02\x35\x77\x08\x8a\x5a\x38\xe2\x0f\
+\xee\x8e\x7b\xbf\x89\xc0\xdd\xbb\xf7\xfd\xdc\x7b\xef\xf7\x8e\x77\
+\x26\x18\x7c\x99\x0c\xae\x1f\x04\x80\x22\xc0\xe0\x04\x28\x05\x0c\
+\x1e\x00\x54\x04\x2b\xa6\x40\x28\xb2\xf4\xd4\x04\xd3\x61\x9d\x46\
+\xca\x94\xbf\xdb\x77\xb4\x9c\xef\x65\x01\x44\x22\xb1\x9d\x29\xa4\
+\xbe\x00\x30\xeb\x14\x40\xae\x99\xe7\x78\xaf\xd7\x2b\x6e\xe5\x7f\
+\xc5\x08\x08\x47\x3e\x6c\x16\x4e\xce\x64\xb2\x10\x44\x11\x76\xce\
+\x06\xbb\xcd\x56\xb4\x29\xac\xe7\xed\xf3\xdb\x1c\x9a\xe3\xe4\xef\
+\xf6\x95\xd5\xa8\x18\x80\x24\x7e\x6e\x31\x8a\x82\xd8\x03\x5d\xed\
+\xd8\xe1\xe4\xb1\xb4\xbc\x82\x95\xcf\xdf\x64\xe1\x6d\xbb\x9b\xd1\
+\xe1\x6d\xd3\x14\x04\x66\x00\x56\x93\x02\xe6\x16\xa2\x45\x71\x05\
+\xb1\xcf\x5f\xbf\x29\x11\x3c\xd4\xbf\xbf\x31\x01\xa4\xd2\x69\x04\
+\xdf\x46\x8a\xe2\x7c\x9e\x56\x78\x5a\x76\x21\xfc\x3e\x8a\x35\x41\
+\x90\xbf\xdf\xce\xf3\xf0\xef\x6d\x6f\x4c\x00\x92\x2a\x29\x0a\xbe\
+\xae\xae\xc1\x61\xb5\xc9\xe2\xa5\x25\xa5\xc6\xc7\xf8\x27\xf9\xf3\
+\x1e\x77\x0b\x2c\x96\xa6\xc6\x05\xa0\x29\x65\x0a\x9d\x61\x56\x03\
+\x14\x5e\x4f\x73\x87\x31\x03\xf0\x6e\x7e\x1e\x0f\x1f\x3d\xd6\x9c\
+\xc0\x7f\x39\x74\xf2\xc4\x71\xec\xeb\xe9\x91\x7f\x62\x06\xe0\xe5\
+\xab\x20\xee\x0e\x0f\xeb\x02\xc0\xa5\x8b\x17\x70\x70\x60\x80\x00\
+\x50\x04\x50\x0a\x50\x0d\xa0\x22\x48\xbb\x00\xab\xa7\x41\xda\x06\
+\xa9\x0f\xa0\x46\x88\x3a\x41\x25\xad\x70\xfa\xd8\x11\xd8\x9e\x4c\
+\xa9\xda\x32\xab\xda\x0a\x27\x42\x41\x98\x17\x16\x61\xbf\x75\x07\
+\x96\xd9\x90\x2a\x20\x54\x07\x50\x50\x6d\x79\xf6\x42\x06\x61\x8e\
+\xc7\xeb\x0a\x42\x5d\x00\x33\xd3\x00\xcf\x97\x08\xe6\x46\xc6\x60\
+\xbd\x3f\x09\xf3\xcf\xbf\xce\x6a\x4d\x43\x55\x00\x39\x9e\xc7\xf7\
+\xb3\xa7\xb1\x71\xfe\x5c\xa9\xce\x64\x12\xdc\x48\x00\xdc\x83\xc9\
+\x5a\xeb\x87\xaa\x00\x0a\xea\x72\x6e\x37\xc4\x2b\x97\x91\x1d\x1a\
+\x2c\x11\xec\xea\xcd\x3f\xa7\xd7\x72\x69\x02\x80\x24\x50\x8a\x06\
+\xf1\xf6\x4d\x64\xfb\xfb\x8a\x7a\x0d\x03\x60\xe3\xcc\xa9\x7c\x1a\
+\x38\x9d\xbf\x6e\xb6\x20\xc0\x35\x78\xa8\x96\x37\x5f\xb6\xad\x6a\
+\x04\x64\xfa\x7a\x21\x5e\xbf\x8a\xcd\x56\x77\x69\x21\x1c\x0d\xc0\
+\x3a\x3e\x51\x97\x42\xa8\x2a\x00\xa9\x0f\xf8\x7d\x35\xcd\x86\xe0\
+\xb8\x76\xe3\xaf\xad\x50\x4a\x8f\x5c\x67\x47\xd5\xd1\x60\x09\x85\
+\x2b\x9e\xa3\x09\x00\xa6\x78\x5c\x16\xbe\x55\x33\x24\x45\xca\xfa\
+\xd8\xbd\x8a\x62\xfe\x3c\x40\x49\x0d\x51\x17\xc0\xcc\x34\xb8\xf1\
+\x09\x70\xa3\x81\xb2\xe2\x1a\x16\x80\x14\xda\x4a\x1a\x1e\x39\x05\
+\xba\x3a\xab\x8e\x00\x25\xed\xb5\xaa\x11\x50\xb5\xa2\x1a\x9c\x40\
+\x00\x68\x30\x42\x93\x21\x1a\x8d\xd1\x6c\x90\x86\xa3\x34\x1d\x96\
+\x37\x58\x9a\x0c\xd1\x64\x88\xd1\x8b\x92\x86\x1f\x8d\x25\x12\x09\
+\x2c\xc7\x62\x35\x68\x5c\xd9\x9b\xf4\x7a\x3c\x70\xb9\x5c\x6c\x6b\
+\x00\x7b\x37\xeb\x63\x91\x59\x11\xac\x8f\xbb\xec\xaf\xf2\xdf\x00\
+\xd8\xbb\xa4\x2d\x8b\x15\xdf\x16\xd7\x96\xbb\xec\xbd\x21\x00\xec\
+\x99\xea\xcb\x22\x45\x80\xbe\xee\x17\x7b\x6f\x29\x02\xd8\x33\xd5\
+\x97\xc5\x1f\x3c\x95\x3c\x5f\xed\x10\xb4\x31\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\xf5\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8d\x89\x1d\x0d\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\
+\x09\x70\x48\x59\x73\x00\x00\x0d\xd6\x00\x00\x0d\xd6\x01\x90\x6f\
+\x79\x9c\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\
+\x72\x65\x00\x70\x61\x69\x6e\x74\x2e\x6e\x65\x74\x20\x34\x2e\x30\
+\x2e\x31\x33\x34\x03\x5b\x7a\x00\x00\x03\x65\x49\x44\x41\x54\x38\
+\x4f\xad\x94\x6b\x48\x93\x61\x14\xc7\x4f\x34\x22\xca\x56\x96\x11\
+\x15\x98\x65\x46\x25\x94\x54\x08\x96\xcc\x04\xc1\xa0\x2f\x49\x05\
+\x11\x74\xc1\xe8\x82\x25\xad\xe9\x9a\xba\x4a\x67\xe6\xd4\xb9\x96\
+\x76\xd1\xc0\x14\xf1\x43\x46\xf4\x41\xfb\x20\x46\x66\xea\xb4\xb6\
+\x2e\x76\xa5\x0b\xdd\xe8\x7e\xb5\x92\xf2\x32\x8b\xd3\xff\xbc\xef\
+\x9a\x5b\xfa\xb1\x0f\x3f\x3c\x7b\x9e\xf3\xff\x9f\x73\x9e\xe7\x79\
+\x25\x66\xfe\xaf\x0c\xbb\x28\x50\xac\x5e\x07\x2c\x14\x97\xde\x42\
+\x3a\x7d\xb7\x82\xc4\xb2\x86\xbd\xe1\x34\xc2\xd0\x85\x68\x53\x30\
+\xc5\x1b\xce\xd2\x2a\x73\xef\xd4\xcc\x9a\x81\x28\x7b\x03\x27\x56\
+\x75\x28\x48\x2c\x6b\xb2\xa7\xe4\x20\x77\x88\x3e\xe0\xc7\x72\xfd\
+\x32\x5a\xbe\xa7\x6b\xfc\x9e\x8a\xbe\xf8\x4a\x17\xeb\x2a\xdd\xbc\
+\xaa\xf6\x16\xaf\x3e\x73\x5b\x21\xe9\x74\x27\xc7\x56\xb8\x59\xf6\
+\x24\x47\x72\x45\x13\xe0\xe1\x0b\xa4\x33\x24\xcc\x2d\x38\xcf\xf3\
+\x8f\xb5\xf3\xa2\x93\x57\x39\xbe\xfa\xba\x62\x2a\x26\xcb\x4e\xb9\
+\xbc\xb8\x39\xf2\x44\xbb\x92\x23\xb9\x8a\xa9\x5f\xa7\x83\x86\x18\
+\x61\xf4\xae\x93\x7d\x61\x25\x6d\xac\xb1\x5c\xe0\xc8\xe3\x1d\xaa\
+\x08\x44\x94\x3a\x15\x66\x83\x70\xec\xcf\x2a\x71\x32\xe5\x34\xb2\
+\xe4\x8a\x46\xb4\x01\x86\xca\x05\xac\xcc\xec\x99\x5f\xda\xca\xb4\
+\xa3\x8e\xb5\xf9\xcd\x1c\x62\x6b\xe5\x60\x7b\x1b\x4f\xb2\x39\x39\
+\xb8\x58\x65\x42\x61\x1b\x6b\xad\x2d\x1c\x64\x6d\x66\x4d\xee\x45\
+\x25\x57\xd1\x40\xfb\xf7\xa2\xfe\x1a\x5a\x46\xa5\x56\x0c\x68\xf2\
+\x9a\x98\xb6\x60\x0c\x4b\x33\x53\x7e\x1b\x68\x07\x1d\x4c\x87\xc0\
+\x41\xc4\x79\x20\x17\xeb\x39\x2d\x4c\x66\x18\x26\xd7\x2b\xd3\x88\
+\x56\x3c\x06\x0d\xf1\x1c\xb4\xe6\x73\x4c\x59\x97\x98\x52\x90\x68\
+\x84\x68\xff\x15\x70\x95\x69\x9f\x1b\x7f\xbd\x98\xf1\x3b\x13\xeb\
+\x26\x8c\xbc\x1b\x45\xb7\x37\x42\xd3\xc4\xe3\x44\x0b\x8f\x41\x43\
+\xbc\xb1\x90\x22\x98\x19\x90\x94\x0a\xb3\x34\x88\xd2\x60\x60\xbc\
+\x01\x71\x27\x4c\x6e\xab\x18\x11\xcb\x9a\xc1\x05\x43\x74\x9b\x8a\
+\x4e\xd3\x70\x3c\x85\x98\x0c\x1e\x7e\x1d\xea\xbb\x83\x0a\xb1\x69\
+\x82\x91\x24\xef\xbd\x89\x6e\xee\x82\xfb\x4c\x07\x1e\x32\x65\x3f\
+\x42\x87\x40\x62\xf3\x3d\x74\x75\xc7\x6b\x8c\x8e\xd3\x9d\x3c\xae\
+\xe0\x32\x3a\x0c\x30\xc4\x17\x90\x55\x0f\xa1\xb7\x23\x31\xca\x7e\
+\x8c\xb3\x7c\x86\x73\x7b\x89\x33\x7c\xa5\x92\xf7\x02\x67\x88\x35\
+\x31\xcf\x42\x41\x29\x9c\x7d\x8d\x29\xa3\xee\x9f\x91\xe5\x73\xda\
+\x5a\xe5\xa1\x02\xe9\x0a\x1d\xe4\x3c\x51\xc5\xd6\xb7\x4c\xb6\x8f\
+\xe0\x93\x17\xc4\xf9\x6f\x70\x41\xcf\x55\x53\x99\xc2\x7a\x8b\x69\
+\x1b\xb4\x01\x97\x22\xcf\x26\xd1\xd4\x43\x76\x8c\x92\x8b\x44\x11\
+\x28\x66\x5f\x98\x1c\xdd\x4c\xa5\x3f\x55\x1c\xdf\x54\x63\xeb\x6b\
+\xb5\x53\x0b\x8e\xc0\x86\x89\x44\xeb\xff\x6c\x94\x20\x2e\xfd\x2c\
+\x6d\x2c\xef\xa5\xe2\xa7\x6a\x77\x45\x1f\x98\xec\x30\x38\xda\xc3\
+\x74\xe2\x17\x53\x19\x10\x53\x7b\x97\x5a\x4c\x8a\x1e\xc6\x24\x1b\
+\xca\x7a\x45\xeb\xf3\xf1\x05\xf2\xe9\xc5\x19\xba\xc8\xd0\x80\x44\
+\x9c\x97\xed\x3d\x3a\x82\x58\x4c\x8e\x7b\x98\x8e\xf5\x21\xfe\xc1\
+\x54\xfc\x19\xc5\xde\x21\x07\x45\xf5\xc8\xd5\xe9\xbf\x0e\xff\xe9\
+\x11\x8d\xa1\x85\x49\x49\x48\xf8\x4e\xeb\xcb\xfb\xc9\x86\x91\x1c\
+\xe8\xb2\xe4\x2b\xd3\x11\x2f\x0e\x1c\x81\x1d\x85\x8a\x30\xc5\xba\
+\x32\x8f\xf2\x2f\x6d\xde\x8a\x8d\xd0\x4e\x03\xa3\xfe\x35\x1c\x0f\
+\xc2\x49\x1b\x9a\x40\x31\xdb\x9c\x94\x60\xec\xa7\xcd\x35\xbf\x48\
+\x8f\xf7\x69\xc1\xad\x0b\x12\x6f\xaa\xfe\x8d\x3d\x0f\x45\x27\xbb\
+\x68\xec\x94\x35\xd0\x2c\x01\x61\x20\xd0\xd0\x6b\xaa\x05\xb3\xc0\
+\x62\x9a\xa9\xd3\xd3\x82\xb5\xb5\x14\xb3\xf3\x01\xde\x18\xce\x09\
+\xc4\xa4\x3c\xa2\x05\x6b\xce\xd1\x8c\xa5\x19\xc8\x89\x03\x51\x60\
+\x3a\xd0\xf8\x3c\xfc\x0d\x7d\x8b\x44\x23\x41\x10\x98\x0c\x42\x41\
+\x04\x98\x03\xa4\x98\x8c\x37\x11\x8c\x06\x23\x02\xb5\x4c\x7f\x00\
+\xaf\x76\x67\x75\x1e\x78\xc6\xf6\x00\x00\x00\x00\x49\x45\x4e\x44\
+\xae\x42\x60\x82\
+\x00\x00\x0a\x98\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\
+\x00\x00\x0a\x4f\x49\x44\x41\x54\x78\xda\xe5\x5b\x69\x50\x53\xe7\
+\x1a\x46\xc5\xba\x94\xab\x54\x90\xa2\x48\x45\xd9\x44\x43\x4e\x88\
+\x01\x65\x11\x28\xc8\xbe\x29\x42\x11\x44\x20\x06\x11\x94\xc8\x26\
+\xb2\x1b\x51\x16\x6d\xbc\xa2\x22\x2a\x6a\xaa\xbd\xa2\x58\x82\x32\
+\x23\x45\x6a\xf1\x12\x97\x51\x2b\x16\x51\x91\x2a\xa0\xe2\x54\x5b\
+\x6c\x19\x2f\x73\x2f\x33\xd7\xe9\xf0\xe3\xbd\x79\x4f\x3d\xe9\x49\
+\x48\x30\x3f\xae\x11\xc2\x37\xf3\x0c\xe7\x04\xf2\xe3\x79\xbe\x77\
+\x79\xbe\xf7\x1c\x74\x74\x46\xd1\x02\x80\x71\x3a\x63\x79\x0d\xbc\
+\xe9\x75\xff\xe3\x8f\x01\xc6\x98\x23\xde\xd3\x23\x99\xfc\xea\x3f\
+\x37\xca\x5f\xff\xf7\x41\xc6\x98\x23\xff\xf0\xb7\xa3\xac\xae\xbe\
+\xaa\xf6\x67\xaf\xc5\xe5\x7f\x7d\x76\xd0\xb8\xab\xef\xc4\xce\xa7\
+\xaf\x6b\xb4\x4b\x10\xc9\x6f\x02\x3d\xfa\xfd\x9d\x9f\xb7\xf1\x5a\
+\x7f\x29\x7a\x73\xf7\xd7\xd2\x3a\x09\x08\x74\x6f\xfe\x9c\x6d\xd1\
+\xf2\x72\xdb\x31\xfc\xec\xde\xaf\xbb\xcb\xb5\x8a\x7c\xf3\xf3\xd8\
+\x35\x92\x2e\xfe\x1c\x52\x08\x89\x9b\x6e\xc5\x8d\xb0\xf2\xab\x3d\
+\xf1\x70\xf5\xd9\xfa\x5b\x92\xae\x35\x73\x9a\x9f\xc6\x1c\xbe\xf2\
+\x8c\x37\x48\x7e\xd6\x93\x20\x96\x48\x04\xba\x5a\x43\xbe\xae\xcd\
+\x4d\xbf\xe1\xb1\xb7\x84\x22\x5f\xd0\xb0\x4c\x9c\x7c\xce\x19\x1a\
+\x1e\xfb\x76\xd7\xff\xe4\x19\xdb\xf0\xd8\xab\xb7\xa1\xd3\x07\x10\
+\x17\x3b\x7d\x1b\xf1\x6f\xb4\x6a\xf7\x6b\x1f\xb2\x0e\x8b\x3b\xd8\
+\xa5\x78\x9d\x7c\x9e\x55\xc6\x28\x25\xe0\x54\x9b\x5d\xdf\xd7\x3f\
+\xb2\x4f\x71\xab\xd9\x50\x74\xd9\x0e\x6a\x3b\xd8\x88\xeb\x35\x0f\
+\xdd\xf4\xb4\x8a\xbc\x50\xc2\x64\x54\xde\x26\x06\x6b\xee\x31\x5d\
+\xb6\x5f\x22\x84\xa6\x02\x02\x32\x2f\x10\x83\x85\xdf\x13\xdd\x96\
+\x3b\x09\x08\x39\x4e\x80\xb8\x9d\x05\x35\xed\xac\x47\x35\x0f\x17\
+\x6a\x17\x79\x5c\x11\x27\x59\x75\x85\x97\x58\xfd\xbb\x9b\x59\x19\
+\x16\x52\xc2\xce\xfb\x08\x48\x14\x13\x60\x94\x4b\x00\x67\x0f\x01\
+\x67\xee\xb1\xe0\xec\x3d\x24\x6f\x6f\xac\x75\xe4\x4f\xff\xc8\x58\
+\xe0\x71\x90\x80\x5d\xff\x64\xf6\x21\x71\xc3\x1c\x02\xd8\xc2\x3f\
+\x7f\xce\x2f\x24\xa0\xf2\x96\x54\x80\x36\xa2\xf7\x9b\x56\x3b\x0b\
+\xad\xec\xf1\xc7\x6f\xb3\xca\x4c\x0a\x08\x58\x29\xfa\x93\x34\x05\
+\xdc\xfd\xe2\x26\x02\xaa\xee\x32\xfb\x50\x24\xad\x35\x39\x69\xe7\
+\x6c\x7b\xe8\xc4\x29\x24\xd5\x30\xe1\xab\x1f\x98\x03\x55\x6d\x84\
+\xb3\xd6\x92\x2f\x6d\xb4\x36\x0b\x3a\x6c\x3b\x84\xbc\xcf\x41\x5b\
+\xd8\xf3\xdd\xa2\xc1\xfd\xcd\x0c\x37\xad\xb6\xb8\x39\x67\x6c\xa2\
+\x59\x25\x4c\x39\xf2\x44\xb1\x2d\x64\x9f\xb5\x19\x2c\xa8\x5d\xb4\
+\x4a\xeb\x3d\x7e\x52\xa5\x75\xd9\xec\xbc\xbf\xc8\x9b\x6f\xb3\x85\
+\xf8\x23\x0b\x20\x59\x64\xc3\xd3\x56\xce\x42\x29\x64\x47\x59\xbf\
+\x12\xab\x26\x8a\xfc\xac\x5c\x26\x04\x97\x5a\x41\xe4\x1e\x8b\x14\
+\xad\xdd\x71\x26\x73\xee\x9d\xfc\xfc\x55\x83\x7a\x7a\x53\x50\x88\
+\xc9\xec\x1c\xab\x5e\x4a\x00\xfb\x1c\x2b\xf0\xc9\x9f\x2f\xd4\xf6\
+\xa8\x0f\x3c\x79\x72\x13\xdc\xbc\x59\x04\x4b\x96\x58\xb4\x99\xc4\
+\xcd\x27\xc9\x9b\xa5\xdb\x80\x3d\xdf\xac\x5c\x67\x2c\x2c\x06\xc3\
+\x54\xd2\xdf\x7f\x02\x5e\xbd\x3a\x0a\xeb\x12\x3c\x60\x8a\xc3\x4c\
+\x98\xc1\xd4\xef\x1f\x3f\x71\x3c\xe6\x7d\x9c\x14\x66\xda\xae\x81\
+\xef\xd9\xb3\xa9\x80\x22\xdc\xbf\x2f\x84\xdd\x42\x1e\x88\x4e\xa6\
+\xc3\x99\xaa\x33\x70\xed\xca\x35\x78\x2b\x82\x76\xaf\xb8\x38\xf7\
+\x17\x74\x01\xaa\xaa\xb3\xa0\xe9\xdb\xcb\xd0\xf5\xa0\x7b\x6c\x08\
+\xb0\x64\x89\xa5\x58\x51\x80\xc6\xda\xef\xe1\xa7\xdb\x9d\x63\x43\
+\x00\x47\x47\xcb\x46\x45\x01\xea\x4f\x35\x42\xc7\xd5\x2e\x14\x20\
+\x59\xdb\xf9\x1b\x67\x64\x04\xbe\x51\x14\xa0\xee\x68\x03\xfc\xde\
+\xfa\x6f\xb0\x5b\xc4\x6e\xd3\xea\x42\x68\x66\x36\xb3\xee\xf9\xf3\
+\x0a\xb9\x22\x78\xb3\x65\x1f\xa4\xc4\xa6\xc1\x2f\x57\xff\x05\x5d\
+\xdf\xbd\x00\xee\x8a\xf8\x01\x1c\x13\x68\x1b\x77\x5d\x03\x03\xbd\
+\xb2\xc6\xc6\x5c\x92\x3c\x5d\x80\xee\x9e\x13\x90\x96\xbe\x0a\xae\
+\x1c\xbb\x0d\xcf\xbf\xfd\x9d\xc4\xa1\x6c\x11\x18\x7d\x62\x7c\x42\
+\xfa\x3d\xad\x98\x00\xb9\x9b\x9b\x7f\xda\xd4\xd4\x94\x2f\x23\xaf\
+\x28\x40\xeb\xfd\x0a\x88\x0d\x8a\x83\xae\xea\x5e\x19\x9a\x0f\xb4\
+\x80\xbd\xf5\xd2\x76\xe9\xf7\x47\xed\x3c\x80\x83\xc6\xa7\xa2\x82\
+\x07\x7d\x7d\x22\x39\xf2\x8a\x02\x50\x51\xd0\xbc\xef\x0e\x3c\x38\
+\xf1\x5c\x86\xbb\xc7\x9f\xc2\x6a\xcf\x18\x4c\x89\x90\xd1\x44\x7c\
+\xb2\x34\xdc\xcb\x0b\x0b\xc3\x07\x95\x11\x57\x25\x00\xd6\x82\xf5\
+\xc1\x9b\xe0\x87\xca\xce\x21\xc8\x89\xde\x09\x1f\xe9\x7e\x24\x18\
+\x0d\xe4\x17\x38\x39\x59\xb5\xb7\xb6\xee\x56\x49\x9c\x42\x7d\x7d\
+\x16\x7c\x5d\x95\x29\x13\x00\xb1\x36\xca\x1f\x2e\xef\x6b\x03\x49\
+\xf9\x83\x21\x28\xe3\x8b\x60\xc6\x34\xc3\x53\x58\x4f\x46\x2a\x79\
+\xe7\x35\x6b\x5c\xfa\xd1\xe7\xbf\x8b\x3c\x25\x00\xda\x60\x6c\x83\
+\x94\x00\x78\x5d\xc0\xdd\x03\x17\xff\x7e\x47\x29\x2a\xb7\x8a\xc1\
+\x78\x86\x49\xfd\x48\x2c\x8e\xce\x7c\xbe\xef\xc0\x70\x84\x5f\xbe\
+\x3c\x02\xa9\xa9\xfe\x40\x15\x43\x14\x00\x09\xc7\x72\xbd\xa0\xe5\
+\x6e\xb9\x4c\x84\x60\x8f\x10\x38\x5f\x7a\x43\x25\x2a\x32\xbe\x01\
+\x73\x13\xeb\xeb\x23\x49\x04\x56\x52\x92\xf7\xb0\xe4\x9b\x9b\x05\
+\x10\x19\xe9\x4c\xe6\x3d\x3d\x02\x50\x00\xcc\xfd\x84\x44\x7f\x99\
+\x00\xeb\xd7\x07\x80\x28\xef\x22\x9c\x2e\x94\xa8\x44\x45\x66\x2d\
+\x18\xea\x1b\x4b\x46\x42\x3a\x18\x06\x04\xd8\xbd\x18\xae\xd8\x95\
+\x94\x44\x42\x5e\x5e\xe8\x90\x4e\x40\x09\x80\xa4\xb7\x6c\x0d\x83\
+\xcb\x57\x76\x93\xd7\x67\xc5\xb9\x90\x1c\x2e\x80\xaf\xf2\x2f\x0d\
+\x8b\xe2\xc4\x63\x30\x63\xfa\xcc\x53\x1f\xd8\xd9\x19\x89\x9f\x3c\
+\x29\x57\x4a\x1c\x09\xc7\xc7\x7b\x02\x75\xfc\x55\x56\x03\x28\x01\
+\x1e\x3e\x3e\x0a\x91\x51\xee\xe4\xf5\xe3\x27\x22\x08\x70\x0d\x85\
+\x43\x59\xf5\xef\x44\xea\xea\x62\xd0\xd5\xfd\x28\xeb\x83\x4d\x78\
+\x44\xa2\x44\x95\xf9\x1e\x1e\xee\x48\x4e\x7f\x86\x2b\x82\xf4\x02\
+\x98\x57\x10\x29\x8b\x82\x2f\xc2\x7c\xa0\x2c\xfd\x9c\x5a\xf0\x77\
+\x8a\x1a\x44\xcf\xa1\x71\x6b\xeb\xe5\xc5\xec\x56\x45\x7e\xed\x5a\
+\x57\xb9\x7c\x57\x25\xc0\x51\x51\xaa\x4c\x80\xfb\x1d\x47\x64\xb5\
+\x00\xc5\xc8\xe3\x1d\x81\x5d\xfc\xea\x77\xa2\x78\xd3\x69\x98\x67\
+\xb2\xf0\x8e\xa6\xeb\xc1\x6a\x24\xa0\x2c\xec\x71\xe7\x3b\x3a\xf6\
+\xaa\x24\x8e\x51\x83\x22\xe1\xf7\x59\xac\xf9\xa4\x0d\xa6\x44\xe0\
+\xad\xf7\x25\x85\x38\x7f\x41\x00\xab\x7d\x52\xa0\x30\xf1\x1f\x6a\
+\x21\x2e\x38\x07\x8f\xd2\xd1\x1a\x63\xef\xe0\x60\x21\x51\x46\x2e\
+\x39\xd9\x57\x65\xd8\xa3\x38\x59\x59\x21\x50\x59\x99\x20\x8b\x80\
+\x3d\x65\x09\x24\x69\x4a\x80\x0b\x0d\x85\x50\xbc\x8b\x4b\xd6\x84\
+\x65\xec\x40\xc8\x8b\x17\xa9\x0d\xb3\xd9\x36\xd7\x35\xc5\x7f\x0e\
+\x56\x76\x45\x82\x38\xf1\x1d\xae\x26\x60\x41\xa4\x1f\x88\x50\x88\
+\x4b\x97\x4b\x60\x5b\x61\x34\x49\x9c\x12\x81\x2a\x86\x01\xde\x01\
+\x90\x19\x7b\x44\x6d\xb8\xb2\x57\x62\x14\xe8\x6b\x42\x80\x44\x45\
+\xab\x8b\x67\x7c\x2e\xd7\x5d\x25\xf9\xe0\x60\x0e\xb4\xb4\x94\xc8\
+\x7d\x8e\x87\xa4\x2b\xd7\x85\x64\xd5\xa7\x47\x01\x3f\x25\x84\x8c\
+\x80\x88\x2f\xfc\x20\x65\xcd\x41\xb5\x11\xf2\xf9\x46\x14\xc0\xe5\
+\xbd\xb3\xb7\xb6\x9e\x55\xad\x48\x52\x20\x08\x53\x9a\xf7\x18\xf6\
+\xa1\xa1\x0e\x43\xc8\xd3\x05\xa0\x8a\x1e\x46\x03\x5e\x53\xf6\x78\
+\x63\x72\x10\x24\x45\x94\xa9\x8d\x70\x9f\x2d\x28\x40\xd8\x7b\x17\
+\x20\x2a\xca\xa5\x5d\x91\x64\x62\xa2\x97\xd2\xdd\xc7\xa8\x50\x46\
+\x5e\x51\x00\x2c\x7c\x48\x18\xaf\xd1\x16\xa3\x20\x3b\x8a\x62\x20\
+\xc2\x2f\x17\xe2\xc3\x84\x6a\x01\xff\x56\x23\x43\xd5\xb4\xb4\xc0\
+\x41\x3a\x11\xcc\xeb\xaa\x2a\xfe\x10\x82\x7b\xf7\xc6\x40\x6d\x6d\
+\xba\xca\x6e\x80\x02\x34\x5c\x2a\x92\xeb\x00\xd4\x35\x8a\x81\x47\
+\xe5\x95\x5e\x5b\x20\x66\x45\xa9\x5a\x08\xf5\xde\xaa\x19\x01\xb2\
+\xb3\x57\xc8\x11\xd9\xbf\x9f\x3b\xa4\xe7\xe3\x3d\x1e\x7c\x54\x91\
+\xc7\xa8\x71\x73\x5b\x08\xa1\xab\x9c\x65\xa4\x91\x30\x25\x08\xd6\
+\x01\x4c\x03\x4f\x27\x1e\x44\x05\x15\xa9\x05\x9f\x65\x49\x28\x40\
+\xe0\x7b\x17\x60\xf3\x66\xbf\x01\x45\xaf\xdf\xd9\xb9\x6f\x48\x3b\
+\xa4\x86\x9f\xca\x80\x35\x03\xd3\x23\x3a\xc6\x53\x96\x06\x12\xe9\
+\x4f\xec\x08\x94\x00\x68\x92\x3c\x9c\xe2\x21\xdc\x7f\xbb\x5a\x70\
+\x5e\xbc\x1a\x34\xe2\x08\x23\x22\x1c\xdb\x14\x8d\x0d\xbd\xf7\xe3\
+\xd9\x00\xfb\xbd\x2a\xf2\x58\x13\xf0\xf7\x98\x02\x68\x78\x90\x2c\
+\x15\x05\x94\x13\xa4\x52\xc0\xc7\x95\x0f\x2b\x7d\x0a\xd4\x02\xc3\
+\x7a\xb9\x66\xda\xa0\xa9\xa9\xc1\x31\x3a\xa1\x6b\xd7\x0a\x49\x0f\
+\x40\xdd\x63\x3d\x50\x1c\x80\xd2\x81\x36\x19\x87\x26\x54\x11\xa4\
+\x1f\x85\x91\x38\xb6\xc5\xcd\xa9\x2b\x20\x3b\x37\x02\x7c\xdd\xd3\
+\x21\xc8\x2b\x57\x2d\x98\x7c\xba\xf0\x91\xc6\x1e\xef\xd1\x77\x1c\
+\xf3\x99\x9e\xef\x48\x4c\xd5\x39\x00\xc7\xe2\xf8\x7b\x7a\x17\x48\
+\xcb\x08\x95\xd9\x61\x14\x03\x0f\x44\xb8\xfb\xdc\x75\xfe\xe0\xe7\
+\x91\xa5\x16\x7c\xdc\x33\x60\xd2\xc4\xa9\x65\x1a\x9b\x01\x48\x0b\
+\xa1\x5c\x27\x40\x97\x47\x77\x84\x38\x00\x51\x65\x95\xa9\xb9\xc0\
+\xce\x9d\x11\x24\x71\xcc\x75\xf1\xf9\x7c\xb9\xdc\xc7\x7b\x4f\x4f\
+\x77\xf0\x76\xcf\x54\x0b\xb6\x36\x01\x9a\xc9\x7f\x6a\x31\x18\xa6\
+\x62\xfa\x80\x03\x77\x93\x22\x8d\x05\x11\x8b\x9c\x32\x01\xe8\x91\
+\x82\xdd\x04\x49\x53\x3b\x4e\xa5\x00\x46\x04\x4e\x89\xac\xcc\x9d\
+\xc0\xd3\x35\x5d\x2d\xe8\x4f\x9b\x7d\x4b\xe3\x0f\x3b\xa8\x50\xa6\
+\x5e\x78\xa0\x93\xc3\x9d\x56\x26\x00\xbd\x85\x52\x02\x50\x39\x4f\
+\x09\x80\x7e\x00\xdd\x20\xc1\x08\x05\x77\x97\xd4\x77\xc2\xc6\xca\
+\x17\x77\x7f\xb9\xc6\xa7\x21\x96\x96\xc6\x75\xe8\xf3\x29\x42\x5f\
+\x7e\x19\x2d\xeb\x06\x78\xad\xcc\x01\x2a\x8b\x00\x04\x0a\x80\xfe\
+\x1f\xc9\x6f\x90\xd6\x81\x55\xe1\x1e\xb0\xcc\x31\x19\x5c\x9d\x36\
+\x0f\x8b\xa5\x1c\x1e\x4c\x9a\xf4\xb7\xea\x0f\x36\x11\xdb\xb0\x61\
+\x79\x3f\x3d\x0a\x70\xf0\x89\xa9\x81\xc2\x28\xb3\xc7\xc3\x09\x80\
+\xcf\x08\xf0\x24\x58\x76\x20\x11\xe6\x99\x39\x80\xb3\x54\x80\xe1\
+\xe0\xb4\x74\x23\xe8\x4f\x37\xed\xd6\xd4\x09\x50\xd5\x0a\x38\x70\
+\x80\x2b\x37\xfd\xc5\x01\x28\x15\x05\x8a\xed\x10\xe7\x83\x75\x75\
+\x99\x72\x02\x60\x21\xc4\x96\x87\xbb\x8f\x02\x24\xf3\x57\x00\x87\
+\xcd\x05\xc7\x25\x9b\x54\x62\xa9\x43\x12\x18\x1a\x58\xf5\x8e\x88\
+\xe7\x86\x53\xa6\xe8\xa6\xd0\x7d\x00\xd6\x06\x34\x47\x18\x09\xd8\
+\xf3\xe9\x43\x53\xea\x33\xba\x00\xd5\xe2\x5c\x72\x30\x82\xb6\x18\
+\x05\x30\x9b\xcb\x91\x12\xdc\xa8\x12\x9c\xc5\x3c\xe9\xce\x7f\x36\
+\x32\xc8\xd3\x16\x0f\xdf\x01\xa4\xd7\x03\x14\x01\xc9\x23\x61\x7a\
+\xad\xc0\x41\x08\x46\x01\x25\x00\x8e\xc4\x71\xf7\x03\x02\x1d\x80\
+\xbd\x78\x11\x2c\x66\xaf\x03\x07\xfb\x24\xa5\x58\x68\x13\x0a\x53\
+\xa7\x1a\xe0\xe4\x67\x44\xfe\xef\x80\x4b\x60\x20\xbb\x87\x9a\x0b\
+\x60\x6a\x60\x3b\xc4\xb6\x88\xb5\x81\x6e\x9e\xf0\x1e\x3b\x05\x0a\
+\xe0\xeb\xc7\x81\xcf\x3d\x08\x30\x36\xfe\x04\xac\x2c\xfd\xc1\x9e\
+\x93\x38\x04\x6c\x3b\x2e\x18\x19\xd9\xbe\x19\x3f\x5e\x37\x63\x24\
+\x3f\x1b\xc4\xa5\x67\x68\xa8\x27\xcc\xcd\x5d\xf9\x06\x0f\x43\xe8\
+\xfc\xa8\x09\xf1\x8e\x1d\x11\x64\x11\x44\x81\xf0\xde\xc8\x68\x3a\
+\x1c\xaa\xe4\x83\xa1\xe1\x34\x98\x3b\xd7\x08\x66\xcf\x5e\x0c\x1c\
+\xce\x06\x39\x10\xc4\x5a\x98\x35\x8b\x3d\xa8\xab\x3b\x05\x5f\x96\
+\x98\x33\x9a\x1e\x91\x5b\xe0\x1b\x21\xf8\xc8\x0c\x87\x9f\x19\x19\
+\x41\x64\x34\xd0\x85\x60\xb1\xcc\xc0\x64\x8e\x21\x98\x98\x18\xe0\
+\xee\x4a\x73\x3b\x81\x04\xdb\x8e\x07\xe6\xe6\xde\x60\x60\x60\xf9\
+\x62\xdc\xb8\x09\xa5\xa3\x8d\xf8\x90\x77\x05\x70\x5c\x6d\x6b\xfb\
+\x59\xb5\xa7\x27\xa3\x7f\xde\x3c\x23\xd8\xb2\x25\x88\x8c\x82\xa8\
+\x28\x17\x69\x3e\x4f\x92\x46\xc0\x02\xb0\xb4\x0c\x90\x46\x00\x07\
+\xf4\xf5\xe7\x3f\x9a\x30\x61\xa2\xf0\xad\xb1\xd1\xae\x7f\x8d\xa3\
+\x5c\x34\x3e\x53\x90\x62\xfb\xc7\x1f\x4f\xc6\xb0\xc6\x77\x84\x53\
+\xdf\x12\xfe\xbf\xf6\x73\x81\x8e\xce\x78\x9d\xb1\xbc\x04\x6a\x46\
+\xd0\xff\x00\x59\x3b\xb4\x2d\x87\x70\x8a\xc1\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\xc2\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x3f\x49\x44\
+\x41\x54\x38\x8d\x55\x93\x4b\x68\x9c\x55\x1c\xc5\xcf\xfd\xee\x37\
+\x9d\x4c\xd2\xc9\x4c\x93\xe9\xa4\x1a\x4d\x43\x6a\x1e\x90\xf8\x0a\
+\x8d\xf5\x49\x15\xa1\x5a\x17\x59\xb4\x54\x51\x08\x0d\x58\x17\xce\
+\x2a\xeb\x82\x48\x96\x41\x91\x38\xc8\xa7\x82\x52\xa9\x15\x71\x25\
+\xb8\x91\x66\xd1\x45\x08\xc8\xe4\xd1\x24\x0b\x1b\x2b\xcd\xa3\x79\
+\x21\xa6\x79\x7d\x5f\xbe\xcc\x9d\x7b\xff\xf7\xfe\x5d\xd8\x40\x7a\
+\x36\x67\x73\x7e\x67\x75\x8e\x60\x66\x1c\xd6\xe0\xe0\xa0\x9f\x4e\
+\xa7\x4f\x6b\xad\xbb\xa5\x74\x2f\x6a\xad\xb9\x5c\xa6\x29\xad\xf5\
+\xb4\x52\x6a\xbc\x58\x2c\x56\x0e\xe7\xc5\x41\x81\x10\x42\x04\x41\
+\xd0\x56\x73\x94\x7e\xee\x6c\xad\x7b\x3e\x75\xa4\x0e\xd5\xc9\x2c\
+\x1e\x6f\x5e\xc5\x6e\xb4\x82\x95\xb5\x75\xfe\xfd\xa6\xfa\x73\x7d\
+\x8d\xfb\x82\x20\x98\xe5\x87\xa0\x7f\x00\x0f\x0f\x0f\xf7\xb5\xb7\
+\x27\xbf\xcf\x27\xea\xfd\x8c\x67\x91\x90\x06\xf1\xde\x1a\xd4\x86\
+\x43\xb6\x2a\x0b\x2d\xad\x18\xf8\x68\xb1\xeb\xb7\x5b\xf1\x78\xa5\
+\xf2\xe1\x80\x10\xe2\x6b\x66\x66\x1f\x00\x82\x20\x68\x6b\x6d\xf3\
+\xbf\x3b\x21\x7d\xdf\xc4\x1b\xd8\x08\x09\xb9\xdc\x71\xdc\xbb\xfb\
+\x37\x9c\x95\x68\x3a\xe9\x63\xe3\x9f\x18\xf5\x39\x87\x0b\x6f\x98\
+\x84\xa6\xf2\x97\x7b\x7b\xef\x4d\x00\x98\x90\x42\x08\x3f\x7f\xa2\
+\x6a\xa4\x29\x5b\xd7\xe8\x3b\x05\x5f\x4a\xf8\xbe\x84\x73\x0e\x4b\
+\x8b\x0b\xc8\x64\x43\xe4\xf3\x1a\xf9\x86\x10\x10\x65\xdc\x1a\x21\
+\xbc\xfc\x4a\xe4\xcd\xde\x49\xbc\x16\x04\x3f\x5e\xf3\x32\x99\x4c\
+\x4f\xfb\xa9\xaa\x67\xed\xfe\x0e\x56\x56\xd7\x60\x88\xe0\x1c\x60\
+\x0c\x21\x0c\x43\xa8\xca\x0e\x20\x22\xb0\x88\x61\x5d\x19\x99\x63\
+\xfb\xc8\xd6\x54\x70\xfe\x9d\xad\x0e\x29\xe5\x39\x9f\x88\x7a\x6c\
+\xa5\x56\x48\x11\x22\x5d\x7b\x0c\xe4\x00\xc1\x16\x80\x43\x1c\x97\
+\x61\x74\x05\x4e\x08\x00\x06\x32\xa1\xd0\x7d\x46\x21\x8a\x2b\x68\
+\x69\xb1\xb0\xb6\xba\xdb\x93\xd2\x9c\xb1\x5a\x42\x69\x07\xe1\x1f\
+\x81\x36\x0e\x9a\x1c\xb4\x71\x78\xb0\xb5\x8d\x6f\xbf\x99\xc3\xc7\
+\x57\x26\xf1\xf9\xd0\x1c\xd8\x53\xd8\xde\x55\xf8\xf4\x6a\x19\xd5\
+\xa9\x18\xc9\x24\xf5\xf8\xd6\x5a\xdf\x82\xb1\xb4\xbc\x8e\xcc\xd1\
+\x2a\xe4\xea\x32\x80\x63\x48\x49\xb8\x52\x68\x46\x7d\x3e\x09\x63\
+\x34\xc8\x69\xb0\xa7\x90\xcd\x55\xf0\xc9\x10\xa1\x26\x4b\x20\x22\
+\xe9\xc7\xb1\x2d\x59\xc1\xef\x36\x36\xb5\x40\x38\x0d\x6d\x09\xb5\
+\xe9\x10\x6d\x9d\x33\x48\x26\xf7\x00\x10\xfc\x14\x01\xb0\x60\xa1\
+\xb1\xb0\xa8\xe0\x3c\x8d\x1d\xf2\x10\x45\x6e\xd2\x07\x30\x09\xb9\
+\x06\xe2\x06\x2c\x2d\xcc\xe3\xe9\x67\x76\xd0\xf5\xc2\x3c\x00\x02\
+\xb3\xf9\xdf\x85\x01\x84\x01\x0b\x8d\xa9\xdb\x0a\x56\x28\x64\x1f\
+\xab\x05\x11\x4d\xfb\x9b\x9b\x9b\xa5\x99\x19\xf5\x57\x57\x67\x6d\
+\x47\xc3\xc9\x25\x3c\xf1\xd4\x36\x38\x61\x00\x36\x10\x6c\xc0\x20\
+\x00\x06\x5b\x3b\x06\x61\xa4\x70\xb1\xcf\x41\x91\xc4\x40\x21\xbb\
+\xac\x94\xba\xe9\x15\x8b\xc5\xca\xea\x8a\x7a\x7f\x2b\xfc\xc3\xbc\
+\x7d\x31\xc4\xf1\x26\xc2\x8d\x1b\x84\xd1\x51\x82\x4b\x10\xc6\x27\
+\x14\xac\x34\xb8\x3d\xab\xf0\xd9\xd0\x1e\x5c\xc2\xe0\xfa\xb5\x8c\
+\xbd\xbf\x20\x2e\x97\x4a\xa5\x08\x0f\x27\x2d\x0a\x85\x42\xe1\x87\
+\x9f\x3e\x30\x9b\xe5\xb7\xb8\x34\xfb\x3a\xdf\x5b\x79\x89\x1f\xec\
+\xf7\xf0\xa5\x4b\x6d\x7c\x67\xb1\x83\x43\xd7\xca\xff\x96\x5b\xf8\
+\x8b\xaf\x9e\xa3\xb3\x67\x5f\xbd\x0a\xc0\x63\xe6\x47\xcf\xd4\xdf\
+\xdf\x7f\xba\x3e\xa7\xaf\xbf\x79\x7e\xab\xa3\xf9\x54\x84\xc6\x27\
+\xf7\xc1\xd0\x58\x5d\x16\xb8\x3b\xe7\xe1\xd7\x5f\x32\xcb\x4b\xf3\
+\x7c\x79\x6c\x6c\x6c\x94\x99\xdd\x23\x6f\x3c\x50\x6f\x6f\x6f\x75\
+\x2a\x95\x3a\x47\x44\xdd\x89\x84\xe9\x21\x32\x32\x8a\xdc\x24\x11\
+\x4d\xef\xee\xee\x8e\x4c\x4d\x4d\x85\x7c\x08\xfa\x0f\x4b\x85\xd4\
+\xf4\xa8\x04\xed\xfc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
+\x82\
+\x00\x00\x07\xca\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x2c\x00\x00\x00\x2c\x08\x06\x00\x00\x00\x1e\x84\x5a\x01\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x03\x12\
+\x0b\x1b\x37\x09\x46\x12\x44\x00\x00\x07\x4a\x49\x44\x41\x54\x58\
+\xc3\xed\x98\x6f\x8c\x5c\x55\x15\xc0\x7f\xe7\xde\xf7\xde\xbc\xb7\
+\x33\x9d\xd9\xed\xee\xb2\xdd\xdd\x6e\x44\xb0\xa5\x2d\xd2\x96\x18\
+\x0d\x6a\xfc\x66\x48\x4c\xf8\x93\x98\x18\x13\x15\x12\x8d\x21\xf1\
+\x83\xc1\x2f\xed\x02\x82\xd9\x50\xa4\x7f\xf4\x83\x41\xfe\xc4\x0f\
+\x92\xd8\x7e\xc3\xf0\x01\x8c\x92\xa8\x51\x29\x62\xd0\x14\x42\x6b\
+\x04\x4b\x2d\xd8\xd0\x75\xd7\x6d\xb7\x0a\xbb\x3b\x3b\x33\xef\xde\
+\x7b\xfc\xf0\x66\xa6\xdb\x52\xba\x5b\x28\x42\xb4\x93\xdc\x79\xb9\
+\x73\xef\x9d\xfb\xbb\xe7\x9d\x73\xee\x39\x47\x78\x97\x9f\x5d\xbb\
+\x76\xd5\xd4\xf8\x2f\x0a\x7c\x1a\xe4\xda\xc8\x9a\x01\x31\x76\x95\
+\x73\xb9\x57\xd5\x53\x0a\xc7\x45\xe5\x79\x35\x61\x7f\x73\xc1\xfd\
+\x7c\x62\x62\xa2\xf5\x6e\xf6\x93\x77\xba\x70\xe7\xce\x9d\x57\x88\
+\xf1\x77\xd4\x7a\x7b\xbf\x32\x3a\xb2\x36\x5b\xbd\x7a\x35\xe5\x72\
+\x05\x6b\x6d\x77\x4e\x9e\xe7\xbc\xf9\xe6\x1b\x9c\x38\x31\xc3\xe4\
+\xe4\x24\xcd\x56\x6b\x46\x45\x7f\x1c\x91\xff\x60\xdb\xb6\x89\x99\
+\xff\x1a\xf0\xee\xdd\xf7\xdf\x52\xca\xd2\x47\x36\x6d\xba\xba\x3c\
+\x32\x3c\x42\xa5\x52\x21\x4d\x53\xd2\x52\x8a\x8d\x22\x44\x40\x83\
+\xe2\xbc\x23\x77\x8e\x66\xb3\x41\xbd\x5e\xe7\xd0\xc1\x83\xbc\xfa\
+\xda\x51\x54\x99\x41\xc2\x6d\x77\x6c\xbb\xfb\x89\xf7\x1c\x78\xf7\
+\xee\xfb\xbe\x3c\x38\xb4\x66\xdf\xd6\xcd\x5b\xa5\x6f\x75\x1f\xfd\
+\xfd\x03\x58\x6b\x11\x11\x04\x41\x51\x54\x3b\x2d\x10\x54\xd1\xa0\
+\x28\x8a\x73\x8e\x63\xc7\x8e\xf1\xcc\xfe\xa7\x69\xb6\x9a\x5e\x54\
+\x3e\x3f\x3e\x7e\xd7\x93\xef\x19\xf0\xfd\xdf\xbf\x7f\xc3\xe8\xd0\
+\xf0\xa1\xad\x5b\xae\x8d\x07\x06\x06\xa8\xd5\x6a\x20\x82\x41\x10\
+\x11\x9a\x79\x8b\xf9\xb9\x39\x16\x1b\x75\xac\x89\xc8\xb2\x8c\x38\
+\x8e\xb1\x91\xed\x1e\x40\x55\x99\x99\x99\xe1\xa9\xa7\x7e\x41\x9e\
+\xe7\x4d\x43\xb8\x7a\xfb\xf6\x7b\x8e\xae\x94\x21\xba\x10\xe0\x4a\
+\x96\xed\xba\xe6\xa3\x9b\xe3\xde\xde\x5e\xaa\xd5\x02\x56\x10\x4e\
+\xcc\x9e\xe4\xd0\x8b\x2f\xf0\xf2\xe1\xbf\x12\x42\xe8\xce\x8f\xe3\
+\x98\x35\x6b\x86\xd9\xb8\x61\x13\x43\x43\x43\x94\xd2\x04\x55\x65\
+\x60\x60\x80\xeb\xae\xfb\x24\xcf\x3c\xb3\xbf\xa4\xd8\x1d\xc0\x97\
+\x2e\xba\x84\xf7\xec\xd9\x71\xe5\x15\x57\x5e\x75\x64\xe3\xc6\x4d\
+\xb2\x76\x6c\x0c\x6b\x2c\x22\x70\xf0\xd0\x8b\xec\xdf\xff\x74\x67\
+\xda\x51\x84\x83\x1a\xe4\xb8\x88\x2a\xc2\x18\xca\x35\x22\xb2\x6e\
+\xcb\xe6\xad\xac\x5b\xbf\x9e\x55\xab\x2a\xa8\x2a\x41\x95\xc7\x1f\
+\xff\xa9\xce\xcf\xcd\xab\x7a\xb3\xee\xce\x3b\xef\x7c\xf5\xa2\x4a\
+\xd8\x63\x6e\x1a\x1d\x19\x95\x4a\xa5\x82\x35\x06\x11\x98\x9e\x9e\
+\xee\xc0\x3a\x51\xbd\x7d\xfb\xf6\x6f\x3f\x22\x22\xba\x74\x9d\xaa\
+\xca\x9e\x3d\x3b\x6f\x3d\xf4\xe7\x83\x3f\xaa\x56\xab\xa5\x38\x1e\
+\xa3\x54\x4a\x30\x22\x6c\xb8\x6a\x83\x1c\x38\x70\x40\x4c\xe4\x6f\
+\x00\x1e\x58\x09\x87\x59\x29\x70\x56\x4a\x3f\x57\xae\x54\xc8\xb2\
+\x9e\xae\x81\x3d\xf7\xdc\x1f\x8a\xd7\x24\xba\x6d\x7c\xfc\xee\x87\
+\xcf\x86\x2d\xc6\x44\xc7\xc7\xef\xfa\x49\x08\xe1\xb6\x57\x5e\x39\
+\xcc\xc2\xfc\x7c\x77\x6c\x74\xed\xda\xe2\x50\x41\xae\x5f\x29\xc7\
+\x8a\x81\xd3\xac\x67\x1d\x40\x92\xc4\x88\x08\xce\x39\x8e\x4f\x1e\
+\x07\x98\x5d\x5c\x70\x0f\x2e\xb7\x7e\x7c\xdb\x5d\xfb\x16\x16\xe6\
+\x5f\xcd\x73\x87\xf7\x1e\x80\x72\xb9\xdc\x19\x5e\x77\xd1\x81\xad\
+\x91\x3e\x00\x6b\x23\x40\x58\xa8\xd7\x51\x55\x40\x9f\x9f\x98\x98\
+\x70\xcb\x1a\x8b\x88\xb6\x5c\xfe\x47\x80\xbc\x95\x17\x46\x19\x25\
+\x1d\x4b\xea\xbf\xe8\xc0\x41\xb5\x56\x00\x17\x37\x59\xee\xf2\x0e\
+\xca\xc9\x15\xbb\x19\x95\x49\x00\xdf\xf6\x24\xc6\x74\x6d\xbe\xff\
+\xa2\xb8\xb5\xbd\xdf\xbb\xbe\x1c\x16\x4b\x97\x8b\xf0\x61\xd7\x06\
+\x95\xc2\x90\xd0\xf6\xa6\xa9\x3f\x75\xc3\xde\x7b\x6f\x3a\xaa\x90\
+\x0a\x64\x80\x05\xaa\x80\x07\xde\x04\x9a\x40\x1d\x91\xfa\x42\xfe\
+\x8f\xc1\xb7\xdb\x6b\xdf\x7d\x37\x7f\x4a\x35\x4c\x79\x3f\x37\xf5\
+\xd5\x89\xdf\x35\x56\x0c\xac\x8a\xec\xdd\x71\xe3\x37\x04\xf9\x26\
+\x8b\x6c\x30\x00\x0a\xd2\x96\x86\xa2\x04\xb4\xeb\x6f\x05\xad\x02\
+\xd5\x73\xf8\x47\x0b\xf4\x2d\xfd\x63\xeb\x5b\x1d\xcf\xf1\x56\xe1\
+\x07\x7d\x16\x04\x6b\xaa\xec\xbd\xf7\xc6\x97\x15\x1e\xbc\xf5\x9e\
+\x9f\x3d\x22\x82\x9e\x17\x78\xdf\xbd\x37\x7e\x4d\x44\x1e\xea\xe8\
+\x6b\xa5\x5a\xa3\x5a\xeb\xe7\xb5\xc0\xe9\xcd\xb4\xb8\x6a\x01\xfa\
+\x06\x47\xd8\x72\xf9\x7a\x54\xde\xea\xd6\x35\x78\x34\x04\x54\x03\
+\xde\xe7\xbc\x7e\xaa\x3b\xf2\x16\xe0\xde\xfe\x21\x1a\x8b\xf3\xb4\
+\x1a\x0d\x42\xf0\x1b\x05\x1e\xda\xbb\xe3\xa6\x06\x3c\xf9\xe8\xf9\
+\x55\x42\xe4\x76\x80\x8f\x7f\xe6\xb3\x78\x93\xe1\x5c\x60\xa1\xde\
+\x40\x66\xa5\x0b\xdc\x69\x00\xce\x79\x54\xce\x7d\xff\x88\x89\x90\
+\xb6\x95\xd8\x24\x25\x50\x7f\x5b\xf5\xeb\xff\xd0\x66\x2a\x3d\x29\
+\xd6\x08\x84\x45\x5e\xf8\xfd\xaf\x10\xf8\x16\xf0\xe8\x72\x3a\xbc\
+\x09\xc0\x91\xe1\xbd\xd2\x72\x9e\x46\x2b\x27\xf8\xc2\x11\x34\x9b\
+\x4d\xac\xb5\x2c\xd6\xeb\x5d\x69\xe5\x2e\x90\x24\xd1\x79\x2f\xd1\
+\x56\xee\x8a\xc3\xa9\x12\x42\xf1\x7e\x5c\xdb\x70\x05\x68\xe6\x8e\
+\xc4\x07\x4a\x26\xc2\x44\xe5\x33\x58\x96\x03\xb6\x00\xa1\x1d\xd0\
+\x94\x92\x84\x3c\x0d\x90\xbf\xc1\xa9\x53\xb3\xdd\x38\xf7\xc0\xf3\
+\x7f\x02\x60\x68\x75\x4a\x52\x4a\x96\xe0\xc9\x39\x2f\xfc\x52\x29\
+\xa1\xbf\xda\x62\x7a\x7a\x8a\xe1\xe1\x11\x16\xeb\x8b\x1c\x3f\xfe\
+\x7a\xa1\x0e\x15\x4b\x9a\x96\x28\x95\x92\xb6\x08\xe4\x0c\x96\x15\
+\x79\x09\x31\xa6\x0b\x50\xab\xae\xe2\x63\x1b\x6a\x1c\x9d\xfc\x0b\
+\x47\x5e\x51\x16\xea\x4d\xd2\x44\xb9\x76\x5d\x1f\xc3\xfd\xd9\x12\
+\x54\x39\x6f\xb4\x32\x7c\x59\x95\xa9\xd9\x63\xfc\xed\xf0\x2c\x0b\
+\x0d\xc7\xec\xc9\x69\x2e\x5f\x53\xe6\x23\xa3\x55\x4a\x89\x6d\xcf\
+\x95\x77\xe6\xd6\xa2\x24\x2b\x84\x65\x2c\xc6\xc6\x94\x4c\xcc\x96\
+\xde\x3e\xbc\xcb\x09\x3e\x47\x35\x2c\xd5\xfb\x15\x44\x53\xc5\xc8\
+\xd8\x48\x4f\xbb\x9f\xc0\xfa\x2b\xce\x7d\x3a\x7d\x07\xc0\xbd\x03\
+\x6b\xc1\x24\x05\xb0\x89\x10\x63\x01\x25\x78\x87\x06\xb7\xc4\x35\
+\xc9\x45\xc8\x61\xce\x5a\x14\x1a\x17\x0e\xdc\x57\x09\x2c\xb4\x1c\
+\xc6\x18\x44\x14\x8c\x00\x86\x60\x0c\x41\xe3\xf6\x16\x82\x88\x39\
+\x53\xc2\xc6\x9e\xee\xab\xa2\xc1\x77\x85\xa6\xa1\x30\x3a\xf5\x8e\
+\x10\x8a\xa6\xc1\xb5\xb5\x56\x31\x46\x10\x1c\x69\xe4\x2e\x1c\x38\
+\xb6\x86\x6a\xaa\xf8\xc6\x49\x82\xc4\x68\x88\xd0\x00\xae\xd5\x22\
+\xe4\x39\x2e\x6f\x92\xb7\x9a\xb8\xbc\x81\xcb\x9b\xb8\x56\x03\x97\
+\x37\x4e\xff\xe6\x5a\x78\xef\x8b\x16\xdc\x99\x4f\xef\x89\x92\x84\
+\xac\xa7\x42\xd6\x53\x26\x2b\x57\x48\x7b\xca\x64\x3d\x15\xca\xb5\
+\x41\x22\xdb\xf3\xee\x63\x89\x0f\xca\xe7\x12\xf0\x25\xe0\x0b\x02\
+\x56\xbd\x24\xe1\xf7\x10\x58\x2f\xe9\xf0\x25\xe0\xff\x01\xe0\x4b\
+\x5e\xe2\xff\xcf\x4b\x44\xcb\xa9\x84\xa2\xa0\x01\x08\xa8\xca\xfb\
+\x7e\x98\xe5\xab\x97\x1a\x40\xdb\x71\x6c\x90\x6e\x7c\xfb\xc1\x03\
+\xd6\xf6\x57\xbb\x6a\x5e\xc0\xb6\x85\xad\x1f\x2c\x09\x6b\x91\xb3\
+\x78\xc0\x60\x92\x2a\xea\x1c\x22\x5a\x34\x55\x44\x02\x12\x14\x31\
+\x0e\x31\x16\x11\x73\x3a\xf3\x38\xa3\x46\x51\xf4\x45\x0c\x62\x14\
+\xc1\x60\x54\x50\x04\x1b\x25\xd8\xa8\x84\x8d\x4b\xd8\x38\xc5\xc6\
+\x19\x36\xee\x39\xbb\x32\x14\x56\x02\x7c\x04\x58\x7f\xec\xc8\x4b\
+\x8c\x5d\xb9\x09\x30\x45\x41\x84\x22\x4b\xd2\xa2\x32\x08\x44\x04\
+\x0c\x88\x05\x13\x83\x89\x91\x28\x41\x6c\x13\x13\x95\xb0\x4b\x33\
+\x0e\x75\x04\x1f\x8a\x8c\x23\x04\x82\xf7\xd8\x38\x26\x4a\x32\x6c\
+\x94\x62\x6c\x7c\x3a\xb5\x52\x65\xf2\xef\x87\x3b\x2c\x2f\x2f\x5f\
+\x5b\x13\x7d\x50\x54\x1e\x78\xf6\x37\xbf\x44\x7e\xfb\x6b\x06\x86\
+\x86\x49\xd3\x32\x71\x9a\x51\xca\x2a\x94\x92\x8c\x28\xce\x88\xe3\
+\x04\x44\xf0\xae\x85\x00\x3e\x6f\x02\x45\x49\xaa\xf3\x9a\x42\xf0\
+\x84\xe0\x71\xce\x11\xbc\xc3\x05\x87\x77\x1e\xef\x1d\xd2\x6a\xd2\
+\x6a\x34\x98\xfb\xf7\x29\x34\x14\x87\x09\xce\x53\x9f\x9f\xa3\xd5\
+\x6c\xb4\x59\xe4\x87\xcb\xa4\xab\x9d\xfa\xda\xcd\x5f\x57\xf4\x3b\
+\xc0\xd8\xfb\xa4\xaa\x53\x88\x7c\xf7\x96\xbb\x9f\x78\xf8\xec\x62\
+\xe0\xdb\x26\xe5\x8f\x3d\xf6\x05\xdb\x78\xa9\x31\x6a\x22\x46\x82\
+\x37\x83\x2a\x3a\x28\x22\xc3\x68\xb8\x4c\x45\x06\x45\x59\x23\x50\
+\xd5\xc2\x97\xd7\x3a\xc9\x76\xfb\x59\x6b\xfb\xf8\x7f\xb5\xfb\x6f\
+\xb4\xf5\x71\x4e\xc0\xa1\x2c\x04\x61\x5a\x54\xfe\xa9\x46\x4f\x18\
+\x95\x29\x44\x67\x44\x74\x46\xf0\x53\x47\xdc\x27\x5e\x9f\x98\x98\
+\x08\xe7\xe2\xfa\x0f\xa3\x92\x6c\xa5\x7c\x7e\x20\x54\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x01\xcf\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x4c\x49\x44\
+\x41\x54\x68\x81\x63\x60\x18\x05\xa3\x60\x64\x03\x46\x74\x01\xfb\
+\xc0\xad\xff\xa9\x61\xf0\xc1\xf5\xde\x18\x66\xd3\x02\x30\xd1\xc3\
+\x12\x5a\x02\x16\x5c\x12\x09\xe1\xaa\x64\x19\xb8\x60\xe5\x6d\xb2\
+\x1d\x43\x0e\x18\xf2\x31\x30\xea\x81\x81\x06\x34\x2b\x85\x44\x65\
+\x8f\x52\xc3\x18\x0c\xb0\x66\x52\x1b\x8a\x9b\x87\x7c\x0c\xd0\xac\
+\x14\x4a\x2f\x68\x24\xcf\x45\x38\xc0\xcc\x09\xf5\x58\xc5\x87\x6f\
+\x0c\xd0\xbb\x3c\x27\x17\x0c\xdf\x18\x18\xad\x89\xe9\x04\x46\xf3\
+\xc0\x40\x83\xd1\x3c\x30\xd0\x60\x34\x0f\x0c\x34\x18\xcd\x03\x03\
+\x0d\x46\xf3\xc0\x40\x83\x51\x0f\x0c\x34\x18\xf5\xc0\x40\x83\x21\
+\xef\x01\x9c\xc5\x28\xa5\x00\x57\x27\x9c\xda\x60\xc8\xc7\xc0\x90\
+\xf7\x00\x49\x49\x48\x4b\xeb\x36\x43\xa0\xff\x4e\x06\x06\x06\x06\
+\x86\x75\xeb\x3d\x18\xae\xdf\x50\xc1\xa9\x76\xcd\xa4\x36\x46\xd7\
+\xa8\x14\xac\xa3\x7c\xbb\x97\xcd\xa1\xda\xdc\x01\x49\x31\x10\x12\
+\xb8\x83\x41\x58\xe8\x03\x83\xb0\xd0\x07\x86\x90\xa0\xed\xd4\x72\
+\x03\x45\x80\x24\x0f\x30\x32\xfd\x43\xb0\xe9\x32\xff\x42\x18\x90\
+\x34\xb8\xab\xa5\x75\x9b\x21\x34\x78\x2b\x03\x03\x03\x23\xc3\xaa\
+\xd5\xde\x78\x93\x10\x1b\xe7\x7a\x72\xdc\xf3\xec\x3f\x03\x43\xd6\
+\x9e\x65\x73\x36\x12\xab\x81\xa4\x3c\x70\xed\x9a\x2a\x43\xe3\xb5\
+\x02\xd2\x9d\x45\x3c\x90\x62\x64\x60\x98\xc2\xc0\xc0\x40\xb4\x07\
+\x06\x63\x29\x24\x43\x8a\x62\x9a\x55\x64\xd4\x2c\x69\xf0\x81\xc1\
+\x18\x03\xa3\x60\x14\x8c\x02\x12\x00\x00\x32\x01\x4c\x5e\xfe\xa2\
+\xf9\x66\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x08\x5c\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x08\
+\x16\x49\x44\x41\x54\x78\x01\xed\x9c\x79\x6c\x14\x55\x1c\xc7\x7f\
+\xbb\xdb\x2d\x14\x28\x90\x48\x5b\x45\x8e\x42\x0f\x0a\x15\x48\xb6\
+\xe5\xa8\xb4\x15\x0d\x12\x2e\x09\x04\x15\x08\xa2\xc5\x78\x10\xff\
+\x51\x84\x00\x12\x90\x43\x51\x42\x40\x63\x34\x01\x1a\x0c\x84\x23\
+\x1c\xd1\xc4\x04\x39\xa2\x08\xc8\x21\x77\x51\xb0\xc8\x19\x29\x08\
+\x06\x8a\x0a\x02\xf6\xd8\xed\x8e\xdf\xdf\xb4\xbb\xcc\xec\xec\x76\
+\xf7\xcd\x76\x68\x77\x98\x5f\xf2\x3a\xf3\xce\xdf\x7b\x9f\xfe\xde\
+\xbc\xb7\xef\xbd\x19\x1b\x09\x48\x65\x65\x65\x1a\x92\x0f\xb7\xd9\
+\x6c\xf9\xb8\x66\xc1\x75\x80\x6b\x0d\xe7\x80\x8b\x45\xa9\x41\xa5\
+\xff\x85\xfb\x03\xee\x8c\x24\x49\xfb\x71\xdd\xd6\xbc\x79\xf3\x0b\
+\xb8\x46\x24\xb6\x70\xa9\x50\xa8\xbd\xba\xba\x7a\x14\xd2\x4d\x83\
+\xcb\x0b\x97\xde\x24\xf1\x07\xd1\x8e\x25\xf1\xf1\xf1\xdf\xc0\x58\
+\xbc\xf5\xb5\xa9\x5e\x80\x00\xd7\x07\x00\x97\xa1\x80\x9c\xfa\x0a\
+\x31\x71\xdc\x71\x00\x7c\x0b\x20\x8f\x84\x6a\xa3\x3d\x58\x04\xa0\
+\xd9\xaa\xaa\xaa\x66\xe0\xfa\x13\xe2\x1f\x56\x78\x8c\x26\x07\x0c\
+\x0e\xd4\xb1\x08\x6a\x6c\x9a\x40\x64\x70\xc0\xf2\x56\x22\x73\x11\
+\x97\x60\x89\x9f\xc0\x6a\x58\xe2\x6b\xb0\x48\x7e\x6e\xfa\x45\x05\
+\x90\x2d\x0f\xf0\x56\x23\xf6\x65\x7f\x0a\xeb\x46\x49\x60\x2d\x20\
+\xbe\x02\x88\x92\x2f\x50\xd5\x85\x01\x6f\x06\x22\x2c\x78\x3e\x3a\
+\xda\xeb\xc4\x3a\x46\xfe\x18\xbf\x05\x22\x22\x17\x16\xc8\xa3\x4f\
+\x9c\x3f\xd6\xba\x09\x46\xa0\x06\x16\x98\x07\x4b\x3c\xca\x91\xb2\
+\x05\x02\x9c\x1d\x6e\x39\xfc\x16\xbc\x60\xc8\xd4\x61\x0e\x66\xc5\
+\xcc\x38\x58\xfe\x03\xeb\x1b\x8d\xfb\x87\x79\xb4\x55\x23\x0a\xef\
+\x73\xd5\x31\xab\x05\x88\xf4\xef\x86\xcf\x63\xa5\x08\x20\x30\x95\
+\xfd\x36\xfc\x3c\x4b\x47\x9f\x3e\x1f\x10\x69\x79\x23\x20\x80\x6e\
+\x9c\xc1\xcf\xbc\x61\x11\xa4\x0d\x99\x04\x85\xd0\xa1\xc3\xc7\x69\
+\xf7\x8f\x07\xa8\xa4\xe4\x24\x5d\xbf\x71\x93\x6a\x6a\x54\x53\x25\
+\x9a\xf7\xfe\x34\x1a\x3c\x68\x60\xc8\x32\xe6\x2d\x58\x42\xdf\xed\
+\xdc\x13\x32\xbe\xa1\x22\x1c\x0e\x07\xa5\x24\xb7\x23\x97\xab\x17\
+\x3d\xfd\xd4\x00\xea\xdf\x2f\x87\x60\x3c\xd1\x14\x3f\x2c\x0e\x05\
+\x14\xea\x2d\xe1\x52\xd9\x15\x5a\xb0\xf0\x13\x3a\x73\x26\x36\x0c\
+\x98\xff\xb1\xd7\xfe\xbc\x4e\xd7\xb6\x7e\x4f\xdf\xc2\x65\x65\x65\
+\xd0\xec\xf7\xde\xa1\xae\x5d\x3a\xeb\x42\x00\x76\x05\x3c\x88\x74\
+\xd3\x93\xbb\xe4\xc4\x49\x7a\x63\xf2\xb4\x98\x81\x17\xac\x8d\xfc\
+\x8f\x7f\xfd\xcd\xa9\x74\xec\xf8\x2f\xc1\xa2\x23\x09\xcb\x62\x80\
+\x1d\x23\x49\xa9\x4c\xf3\xfb\xa5\xcb\x34\x73\xd6\x42\xba\x7b\xef\
+\x9e\x32\x38\x26\xef\x2b\x2a\x2b\x69\xd6\xec\x8f\x88\x7b\x93\x0e\
+\xe9\xc0\x00\x5b\x89\x66\x5c\xb4\xf8\x73\x53\xc0\xf3\xb5\x9b\x0d\
+\x81\xdb\xa4\x43\x12\x19\xa0\xd0\x62\xe8\xc1\x43\xc7\xe8\xd4\xaf\
+\xbf\xe9\xd0\xd5\xb4\xb3\x9c\x3c\x75\x5a\x1e\x0c\x05\x6b\xe9\x60\
+\x80\x42\xb2\x6b\x0f\x2f\xda\x9a\x53\x7e\xd8\xbd\x4f\xb8\x61\xc2\
+\x00\x4b\x4a\x4e\x09\x2b\x89\x95\x0c\x7a\xda\x26\x0c\xb0\xfc\xe6\
+\x5f\xb1\xc2\x43\xb8\x9e\x7a\xda\x26\x0c\xd0\xe3\xf1\x08\x57\x2c\
+\x56\x32\xe8\x69\x9b\x30\xc0\x58\x81\xf1\xa0\xea\x69\x01\x8c\x92\
+\xb4\x05\x30\x4a\x80\xc2\x0b\xa8\x1f\xce\x9f\x29\xac\x32\xbb\x87\
+\xae\x5f\x8b\xc2\x7a\x1a\x23\x83\x30\xc0\x67\x9e\xce\x6f\x8c\x7a\
+\x36\x59\x9d\x56\x17\x8e\xf2\x5f\x63\x01\xb4\x00\x46\x49\x20\xca\
+\xec\x96\x05\x5a\x00\xa3\x24\x10\x65\x76\xcb\x02\xa3\x04\x28\x3c\
+\x8d\x99\x3d\x77\x91\xb0\xca\x17\xc6\x8c\xa4\xde\xbd\x7a\x08\xe7\
+\x8b\x85\x0c\xc2\x00\x77\xed\xde\x2f\xdc\xae\xc2\x82\xfe\xc8\x63\
+\x4e\x80\x56\x17\x16\x36\x07\x75\x06\x0b\xa0\x9a\x87\xb0\xcf\x02\
+\x28\x8c\x4c\x9d\xc1\x02\xa8\xe6\x21\xec\xb3\x00\x0a\x23\x53\x67\
+\xb0\x00\xaa\x79\x08\xfb\x2c\x80\xc2\xc8\xd4\x19\x84\xe7\x81\xea\
+\xec\xb1\xe1\x4b\xeb\x9a\x4a\xb9\x39\xbd\xa9\xe7\x13\xdd\xa9\x53\
+\xc7\xc7\x29\x19\x27\xb4\x12\x12\x12\xe4\xca\x57\x54\x54\xd0\x0d\
+\x9c\x28\xbb\x7c\xe5\xaa\xae\x03\x03\xa6\x05\x98\x90\xd0\x9c\x46\
+\x8d\x1c\x42\x23\x86\x0f\xa6\x2e\xa9\x9d\x42\xfe\xa7\x9d\xce\x44\
+\x6a\xdd\x3a\x91\xd2\xd3\xbb\x90\x9e\xc5\x62\xd3\x01\xe4\x33\x80\
+\x63\x46\x0f\xa7\x49\x45\xe3\xa9\x0d\xc0\x18\x2d\xa6\x02\xd8\xb1\
+\x43\x7b\x9a\x3f\x77\x3a\x65\x75\x4b\x37\x9a\x9b\xbf\x7c\x61\x80\
+\x7a\xcc\x3c\x25\x39\xd9\xaf\x30\xd8\x4d\x76\x8f\x4c\xf2\xd4\x44\
+\xb7\x61\xdf\x2d\x33\x9d\x5e\x7c\xfe\x39\x6a\xd6\xac\x59\x30\x15\
+\x86\x85\xf1\x3b\x71\xfe\xb7\x6e\x0c\xd3\xd2\xd8\x05\xe3\x18\xb2\
+\x6d\xff\x7e\xb2\x6f\xdf\x4e\xf6\x83\x78\x15\xe6\xc2\x05\xb2\xdd\
+\xba\x25\xd7\x4a\x6a\xdb\x96\xf0\x00\x24\x6f\x5e\x1e\x79\x87\x0e\
+\x25\x29\x1f\x9b\x66\x02\xc7\x7e\xcd\x0d\xd0\xeb\x25\xfb\xba\x75\
+\xe4\x58\xbc\x98\x6c\xe7\x23\x3b\x86\x2c\x01\x66\xcd\xf4\xe9\xe4\
+\x9d\x38\x11\x2f\x81\x84\x9f\xe5\x99\x16\xa0\x0d\x56\x16\x57\x54\
+\x44\xb6\xa3\xf2\x0b\x45\xc2\x7d\x40\xea\xd3\x87\x3c\xeb\xd7\x93\
+\xd4\xb9\xfe\xf3\xd3\xa6\x04\x68\xdf\xb9\x93\xe2\xc6\x8d\x23\xba\
+\x73\x47\x18\x9c\x2a\x43\x52\x12\xb9\x37\x6e\xac\xed\xd6\xaa\x88\
+\xfb\x1e\xd3\x01\xb4\xed\xdb\x47\xce\x61\x78\x73\xa3\xba\xfa\x7e\
+\x2b\xa3\xb9\x8b\x8f\x27\xcf\x96\x2d\xe4\x1d\x38\x30\x68\x29\xa6\
+\x02\x68\x2b\x2b\x23\xe7\x80\x01\x44\xe5\xe5\x41\x1b\xab\x3b\x30\
+\x31\x91\xdc\x18\x7c\xa4\x8c\x0c\x4d\x11\xe1\x9f\x92\x9a\x2c\x4d\
+\x34\x00\x03\x46\xdc\x84\x09\x0d\x0f\x8f\x9b\x8b\x47\x41\xdc\xa4\
+\x49\x44\xd0\x11\x28\xc2\xf3\x40\x3d\x7b\x22\x7c\xb8\x28\x25\x25\
+\x29\x50\xb7\xdf\x5f\x7a\xfa\x2c\x5d\xbf\x1e\xb9\xd5\xf4\xeb\xeb\
+\xa2\x96\x2d\x5b\xf8\xf3\xf3\x8d\x7d\xed\x5a\xdd\x03\x86\xaa\xa0\
+\x10\x1e\x1e\x8c\xec\x6b\xd6\x90\x17\x03\x93\x52\x84\xbb\xf0\x93\
+\x85\x23\x94\xf9\x23\xba\x6f\xc8\x57\xbd\xd2\xd3\x52\x69\xcd\xaa\
+\x2f\xd4\x7a\x31\xcf\x73\x66\x67\x93\xed\xe2\x45\x75\x78\x03\xfb\
+\xa4\xb4\x34\x72\x97\x96\xaa\xe6\x89\x31\xd7\x85\x73\x5c\xbd\x35\
+\x58\x78\xe0\x30\x1a\x1e\x2b\x65\x1d\xac\x4b\x29\x31\x07\x30\x3b\
+\x9b\xbf\xf7\xa3\x16\xfb\x8e\x1d\xea\x00\x03\x7d\x81\xba\x62\x0e\
+\x60\x6a\x67\xed\x9b\x69\xf6\xc3\x87\x0d\x44\xa6\x2e\xda\x7e\xe4\
+\x88\x2a\x40\x18\x20\x2f\x17\x35\xa6\x24\x25\x3d\xa2\x55\x7f\xee\
+\x9c\x36\xcc\xa8\x90\xb3\x67\x55\x25\x0b\x03\x4c\x4e\x6a\xa7\x2a\
+\xe0\x41\x7b\x5a\xd4\xad\x24\x2b\xf5\xda\x6e\xdf\x56\x7a\x0d\xbd\
+\xf7\x2d\x42\xf8\x94\x08\x03\x74\xb9\x7a\xfa\xf2\x5a\x57\x10\x10\
+\x06\xc8\x6f\x7a\x37\xa6\xfc\x87\x3d\x8c\x40\x91\xda\xb4\x09\x0c\
+\x32\xcc\x2f\x2f\x7f\x29\x4a\x67\x80\x35\x0a\x7f\xd8\xdb\xbc\xfe\
+\xb9\xf2\x9b\xde\x61\x13\x1a\x94\xa0\xbc\x3c\xc8\xab\x66\x99\x99\
+\x06\x69\x0b\x52\x6c\x80\x2e\x06\x78\x37\x48\xb2\x90\x41\xfc\x8d\
+\x81\x39\xb3\xa6\x50\xab\x96\x2d\x43\xa6\x31\x32\xa2\xec\xf2\x15\
+\x4d\xf1\xde\x7e\xfd\x34\x61\x46\x05\x04\xea\x62\x80\xda\x1a\x85\
+\xd1\xce\xbb\x5c\x1f\x2f\x9c\xd5\x28\x10\x4b\x4b\xd5\xa3\x20\x57\
+\xd5\x3b\x64\x48\x98\x1a\x37\x5c\x74\xa0\x2e\x06\xa8\xad\x51\x04\
+\xfa\xf8\x17\x41\xf1\xf2\x25\x0f\x74\x03\x87\xab\x75\xec\xf8\xcf\
+\x9a\xda\x49\x05\x05\xc4\x3f\xb3\x8c\x16\xd6\xc1\xba\x94\xc2\x9f\
+\x7c\xda\xab\x0c\x10\xb9\xe7\x49\xed\x97\xc5\x9f\xd2\xd2\xc5\xf3\
+\xb0\xff\xfa\x2c\x3d\xf6\x68\x0a\x19\x3d\x4f\xbc\x70\xf1\x12\xf1\
+\x37\x1b\x54\x82\xc7\x0a\x2f\xc3\x1b\x2d\xb2\x8e\x80\xfd\x12\xf3\
+\x7c\x78\x07\x4b\x4d\xce\xc2\x42\xc3\x56\x64\xa4\xdc\x5c\x72\xef\
+\x85\xad\x05\xfc\x90\xb0\xd7\x7d\x70\x15\x5b\x55\x31\x2e\xd8\x00\
+\xf2\x60\x03\x89\xb0\x0c\xdf\xe0\x82\x05\x55\xcf\xaa\x55\x1a\x78\
+\xd0\xf3\x37\x3f\x03\x59\x96\xd6\x5e\x62\xfb\xaf\x94\x9a\x4a\xee\
+\x0d\x1b\x88\xb0\x0c\xdf\x60\xc2\x4b\xfa\x9b\x37\x93\x14\x30\x7d\
+\xe1\xf2\xf1\xf8\xfb\x40\xfe\xee\x11\x6e\xf8\x4b\xbd\xbc\x7d\xe5\
+\x6a\x30\xc5\x8d\x58\x90\xbc\xa9\x34\x76\x2c\x26\x68\x42\x33\x34\
+\x6d\x8d\xdb\xb5\x23\xf7\xa6\x4d\xa1\x36\x95\x4e\xe0\x1b\x82\x7d\
+\x65\x0b\xc4\xdc\xce\x0b\x37\x19\x25\x44\x77\x3c\x40\x5b\x85\x46\
+\x09\xf1\x0e\x1a\x44\xee\x43\x87\x88\x9f\x5b\x7a\x45\x7e\xe6\x1d\
+\x38\x10\x0a\x5e\x05\x8c\x6e\x3c\x98\x79\x7c\x5d\x18\x56\x2f\x7f\
+\x91\x71\x8e\x5e\x85\x4d\x2d\x1f\x6f\x00\xb9\xb1\xf8\xe9\x29\x2e\
+\x26\xde\x2c\x8f\x54\x38\xad\x67\xc5\x0a\x39\x2f\x3f\x12\x82\x88\
+\x17\xf0\xc6\x61\xec\x90\xa7\x7f\x72\x17\xf6\x25\x42\x04\x7f\x84\
+\x76\x0d\xfc\x2f\xf9\xc2\x4c\x71\xf5\x1d\xed\xd8\xb6\xad\xf6\x68\
+\x07\xaf\x2c\x2b\x8f\x76\x60\x7e\x27\x1f\xed\xc0\x76\x68\x98\xa3\
+\x1d\x12\xac\xae\x08\xc6\xc6\x8c\x64\x51\x01\xe4\x10\x40\xb4\x3e\
+\x83\x5c\xcb\x26\xf0\xaf\x1b\x01\xe3\x71\x78\xe9\x6b\x65\x84\xbf\
+\x0b\xfb\x02\x41\xb8\x06\x84\x5f\x85\x9f\xdf\xed\x37\xc5\x33\xd1\
+\xd7\xb6\x28\xae\x57\x61\x58\x3d\x03\xe1\x71\x79\x1a\x0b\x54\x2a\
+\x41\x77\xee\x8b\x8c\xcb\x10\x66\x8a\xd1\x59\xd9\xb6\x08\xef\xf9\
+\x8b\xbd\x9f\x39\x9d\xce\x19\x3c\x60\x04\xcb\x53\x2f\x40\xce\x00\
+\x80\x3c\xc5\x19\x83\xdb\x29\x70\x79\x1c\xf6\x10\x08\x2f\x3a\x7e\
+\x85\x9e\xf8\x36\xc0\xfd\x53\x5f\x7b\xc3\x02\x54\x66\xc6\xf7\x56\
+\xf9\x6c\xc3\x30\x14\x9a\x8f\x6b\x77\xb8\xf6\x70\x38\x60\x57\xbf\
+\x25\x23\xbe\x29\x0b\x9f\x8f\xac\x82\xbb\x09\x77\x0c\x06\xb3\x12\
+\x23\xec\xd6\x48\x2b\xfc\x3f\xae\x79\x78\x29\x13\xb5\x62\x91\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x6f\x6a\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5c\x72\xa8\x66\
+\x00\x00\x6f\x31\x49\x44\x41\x54\x78\xda\xed\x5d\x07\xc0\xe5\x44\
+\xb5\x3e\xc9\x2d\x7f\xd9\xf2\x6f\x63\x0b\xb0\xbb\x2c\x75\xe9\x45\
+\x40\x50\x9a\x52\xa4\x29\x55\x44\x01\x11\xc5\xf2\x9e\xe8\x93\x87\
+\x8a\x60\xa1\x0a\x82\x15\x50\x9f\xa0\x02\x76\x9f\x22\x88\x4f\xb0\
+\xa0\x82\x88\x28\x58\x50\x10\xa4\xf7\x6d\x2c\x6c\xdf\xbf\xdc\x92\
+\xbc\x73\xa6\x24\x93\x7a\x27\xed\x96\xdd\xff\x40\x36\xb9\xf7\xcf\
+\x4c\x92\xc9\xfd\xbe\x39\x6d\x66\x0c\x18\x97\x9e\x97\xdf\x95\x4a\
+\x15\xdc\x6d\x8a\xdb\x5c\xdc\x36\x57\x37\x03\x60\x0e\xee\xe9\xef\
+\x25\xdc\x4c\xcd\xfd\x18\x6e\xcb\x94\x6d\xa9\xef\xf3\x32\xac\x77\
+\xd9\x41\xcd\xe6\xea\x4e\x3f\xfb\xb8\x64\x13\xa3\xd3\x37\x30\x2e\
+\x7a\x82\x20\x9f\x87\xbb\xbd\x71\xdb\x12\x82\x40\x9f\x05\xe2\x5d\
+\x16\xf5\x42\x23\xea\x95\x44\xb1\x04\xb7\x7f\xe1\xf6\x00\x6e\xff\
+\xc0\xed\x9f\x48\x0e\xeb\x3a\xdd\x66\xe3\xd2\x5a\xc6\x09\xa0\x0b\
+\x05\xc1\x3e\x19\x77\x7b\xe2\xf6\x6a\x65\x9b\x1d\x76\x6e\x91\x2f\
+\x30\x43\xdd\x36\x6e\x4f\x82\x4b\x08\x6c\x8f\xa4\xb0\xb4\xc0\xdb\
+\x1d\x97\x14\x32\x4e\x00\x1d\x16\x04\x3b\xa9\xdc\x3b\x83\x17\xec\
+\x0b\x81\xab\xe3\x01\xe9\x52\xc0\xeb\xd6\x4d\xda\xc2\x03\x62\xfb\
+\x1d\x6e\x77\x1f\xd8\x6c\xd6\x0a\xbc\xec\xb8\xb4\x90\x71\x02\xe8\
+\x80\x20\xe8\x77\xc1\xdd\xf1\xb8\xbd\x1e\xb7\x57\xe1\x36\x18\x77\
+\x7e\x9b\xd5\xfa\x76\xd6\x4f\x66\xc2\x6f\x71\xbb\x9d\x36\x24\x83\
+\x17\x0b\xbe\xa5\x71\xf1\xc9\x38\x01\xb4\x49\x10\xf4\x7b\xe1\xee\
+\x04\xb1\x6d\x1d\x77\xee\x06\x0c\xf8\x56\xf2\x20\x70\x32\xb8\x0d\
+\xb7\x3f\x21\x21\x34\x0b\xbe\xe5\x8d\x5e\xc6\x09\xa0\x20\x41\xc0\
+\x93\x0a\xff\x5a\xe0\x80\xa7\xde\x7e\x6e\xd4\xb9\x3d\xae\xd6\x17\
+\x53\xb7\x01\x2b\x71\xf7\x6b\xe0\x64\xf0\xcb\x03\x1a\xcd\xe5\x05\
+\x5e\x6e\xa3\x95\x71\x02\xc8\x51\x10\xf4\x65\xdc\xbd\x0e\x38\xe8\
+\x8f\x05\xee\x9d\x0f\x48\xcf\x82\xb2\xc8\xba\xe3\x2b\x27\x4d\x80\
+\x88\xe0\x3a\xdc\x7e\x81\x64\x60\x15\x78\x2b\x1b\x95\x8c\x13\x40\
+\x0e\x82\xc0\xdf\x03\x77\xef\x07\x0e\xfa\x69\x61\xe7\x6c\xc4\x6a\
+\x7d\x78\xbd\xe9\x2b\x7e\x1e\xb7\x6f\xd2\x86\x44\xb0\xa8\xe0\xc7\
+\xdf\xe0\x65\x9c\x00\x52\x8a\xf0\xde\x1f\x83\xdb\x87\x70\xdb\xdf\
+\xff\xf7\x9e\xed\x89\x8b\xac\x3b\xdf\xca\x1d\xad\x00\xab\xfd\xc5\
+\xfe\xe3\x5a\x41\x2a\x19\x27\x80\x84\x82\xc0\x1f\xc2\xdd\xbb\x70\
+\xfb\x00\x6e\x5b\xc8\xef\x7b\x16\x94\x45\xd6\x5d\x50\xe5\x21\xd5\
+\x3a\x5a\xc1\xfe\xe3\x5a\x41\x22\x19\x27\x00\x4d\x41\xe0\x93\xe7\
+\xfe\x83\xb8\x9d\x81\xdb\xc4\x9e\x05\x65\x91\x75\x17\x58\xb9\x66\
+\xd5\x52\x2b\xf8\x3c\x12\xc1\xdd\x05\x3e\xea\x06\x23\xe3\x04\xd0\
+\x42\x10\xf8\x14\xab\x27\x35\xff\x28\x23\x22\x39\x27\xab\x6c\x84\
+\x76\x7c\x3b\xee\xf9\x0e\xdc\x3e\x89\x44\x70\x5f\x71\x77\xd9\xfb\
+\x32\x4e\x00\x21\x82\xa0\xa7\x76\x79\x1b\xfe\xf3\x51\xdc\xef\x52\
+\xc4\x35\xc6\x7b\xf9\xb6\xdd\xf3\xcf\xb1\xee\x4f\xed\xd7\x68\x3e\
+\x50\xdc\x25\x7a\x57\xc6\x09\xc0\x27\x77\x96\x4a\xe4\xd0\xfb\x02\
+\xf0\x5c\xfc\xdc\x64\x1c\xf0\xed\xbb\xef\x90\x7a\x69\x6c\xc2\xcd\
+\xc0\x89\xe0\x91\xe2\x9e\xa8\xf7\x64\x9c\x00\x84\x20\xf0\xb7\xc2\
+\xdd\x95\xc0\x93\x76\x32\xcb\xb8\x5a\xdf\xc6\x7b\x6e\x79\x86\x2d\
+\x0f\x28\x52\xf0\x03\xdc\x2e\xda\xaf\x61\x3d\x51\xdc\x93\xf6\x8e\
+\x6c\xf4\x04\x80\xc0\x9f\x82\xbb\x4f\x00\xf7\xea\x57\xd3\xd6\xd3\
+\x29\xc0\x1b\xe5\x32\x0c\xcc\x9d\x0b\x7d\x33\x67\x42\x65\xc6\x0c\
+\xa8\xce\x98\x0e\xe5\x69\xd3\xa0\x3c\x34\x84\xdb\x64\x28\x4f\x9c\
+\x04\xa5\x89\x13\xc0\x1c\x18\x84\x52\x7f\x1f\x18\xd5\x3e\x30\xab\
+\x65\x30\x28\x67\x09\xd1\x6e\xdb\x08\x0e\xab\xc9\xf7\x4d\x0b\x6c\
+\xca\xbe\xc5\xcd\x16\x9b\x35\x56\x83\xc6\xaa\x55\x50\x7f\xe5\x65\
+\x18\x5b\xba\x14\x46\x17\x2d\x82\xd1\x67\x9f\x83\xe1\xa7\x9e\x82\
+\xda\xcb\x2f\x77\xac\x4d\xe2\xeb\xb6\x5b\x15\x6f\xe0\xf6\x6d\xdc\
+\x2e\x46\x22\x78\xae\xc0\xdb\xec\x7a\xd9\x68\x09\xe0\x4e\x9e\xb5\
+\xf7\x5e\xdc\x2e\xc4\x6d\x46\x9a\x3a\xda\xfd\x03\xaf\x4e\x9d\x0a\
+\x53\x5e\xb3\x2f\x4c\xda\x7d\x77\x18\xdc\x66\x6b\xe8\x9b\x3d\x07\
+\x4a\x93\x27\x21\x8e\x3b\xf3\x1a\x6d\x0b\x09\x63\x74\x14\x1a\xeb\
+\xd6\xc3\xd8\xa2\x17\x61\xf8\xf1\xc7\x61\xed\xdf\xfe\x0e\xab\xee\
+\xbf\x1f\x89\x63\xac\xf0\xf6\xf0\xdd\x4d\x9a\x6a\x69\x24\xe2\xe7\
+\xb0\xee\x8b\x5f\xdb\xb0\xf2\xbd\xe1\x1e\x91\x8d\x92\x00\x10\xfc\
+\x47\xe1\xee\x73\xc0\x87\xdd\x6a\x4b\xdb\x00\x8f\x80\x9e\xb8\xed\
+\xb6\x30\xb4\xcf\xab\x61\xe2\x4e\x3b\xc1\xc0\x82\x2d\xb0\x67\x9f\
+\x01\x66\x7f\x7f\xfb\x1b\x2b\x85\x90\x36\x61\xad\x5d\x07\x63\xcb\
+\x96\xc2\x08\x6a\x0a\x6b\x1f\x7c\x10\xd6\xdc\xfb\x27\x18\x5d\xb6\
+\x2c\x5d\x7b\x84\x5f\x25\xf5\xfd\x85\xd4\xfd\x18\x6e\x67\x22\x09\
+\xdc\xd3\x89\xf6\xea\xa4\x6c\x54\x04\x80\xc0\xdf\x11\x77\x5f\xc4\
+\xed\x50\x9d\xf3\xdb\xa5\xd6\x57\x51\x65\x9f\x79\xfc\x71\x30\xb4\
+\xd7\x5e\xd0\xbf\xf9\x66\x50\x9e\x32\xc4\x55\xf4\x0d\x48\x88\x14\
+\x9a\x6b\xd6\xa0\x96\xf0\x04\xac\xfa\xe3\x3d\xf0\xca\x2f\x7f\x05\
+\xf5\xd5\xee\x8c\x62\x6d\x06\x7c\xd4\x05\xfe\x07\xb7\x8f\x21\x11\
+\xac\xed\x70\x73\xb5\x4d\x36\x0a\x02\xb8\x93\x87\xf5\xce\xc6\xed\
+\x32\xdc\xfa\x3a\xd5\x18\x6a\xfd\x53\xf6\xda\x13\x36\x79\xe3\x1b\
+\x61\xd2\x1e\xbb\x43\x65\xfa\xf4\xe2\xd4\x78\xbb\xe5\x17\xdc\xf3\
+\x67\xa7\x07\x58\xaa\xdb\xc2\xeb\xd5\x5f\x7e\x05\xd6\x3f\xf2\x30\
+\xac\xba\xeb\xf7\xb0\xf2\xae\xbb\xa0\x19\x30\x1b\xd2\xdd\x53\xc6\
+\x96\x7c\x01\xb7\xff\x40\x12\xb8\xad\xad\x0d\xd2\x21\xd9\xe0\x09\
+\x00\xc1\xbf\x19\xee\x6e\xc4\xed\x90\x76\x37\x80\x5a\x77\x79\xc2\
+\x04\x98\x79\xcc\x1b\x61\xda\xeb\x5e\xcf\xec\xf7\x48\x75\x5e\x05\
+\xa2\x9f\x14\x72\x04\xa9\xac\x49\xf7\xf9\x93\x5c\xd9\x48\x50\xc6\
+\x39\xb7\xd9\x80\xb1\x17\x5e\x84\x15\xbf\xf9\x0d\xbc\xf4\xe3\x1f\
+\x43\x7d\x6d\xb2\x4e\xb8\x80\xf7\xf8\x7d\xdc\x3e\x84\x44\xb0\x41\
+\x0f\x43\xde\xa0\x09\x00\xc1\x4f\xc3\x72\x69\x08\xa9\x33\x42\xaf\
+\x9d\x8e\x3b\xb3\xbf\x0f\x36\x3b\xed\x34\x98\x7e\xe8\xa1\xd0\x87\
+\xaa\x7d\x11\xbd\x3c\xe3\x04\xab\x01\xb6\x45\xde\x7c\x4b\x78\xf5\
+\x2d\xe6\xa0\x03\xf5\x58\xfe\x8d\xbc\xfb\xf2\x6f\xce\x8d\x1b\xee\
+\xbd\x89\xbd\xcd\x0e\x29\xf7\xd1\x04\x83\x36\x1a\xfb\x84\x9b\x3c\
+\x36\xca\xf8\xd9\x2c\x17\xd3\xa0\xf5\x3a\x0c\x3f\xfd\x34\xac\xb8\
+\xfd\x76\x58\x7e\xeb\xad\xd0\xac\x05\x67\x0d\x2b\xf2\x3d\x2a\xe9\
+\x9e\x14\xe6\xf8\xd0\xbe\x0d\xeb\x7b\x05\x5e\xae\xa3\xb2\x41\x12\
+\x00\x02\x7f\x22\xee\xae\xc6\xed\x8c\x4e\x84\xe7\xa6\xee\xbb\x0f\
+\xcc\x79\xfb\x69\x30\x69\xe7\x9d\x62\x6d\xf9\xc4\xfd\x39\xf6\x92\
+\x76\x1d\x01\xdc\x68\xb0\xcd\x6a\xd4\x79\xd8\x8e\x55\xa6\xd4\x16\
+\x75\xec\xb9\xb8\xed\xfb\x18\x55\x3e\xfc\x8e\xe5\xf9\x9c\x0c\xca\
+\x6c\x33\x2b\x15\x30\xaa\x55\x30\xab\x15\x44\x51\xc9\xd7\x50\xe9\
+\xb4\x19\x1b\xc1\xbf\xfe\xd1\x47\xe1\x65\x24\x82\x95\xa8\x1d\x58\
+\x8d\x46\xd2\x56\xd3\x92\x16\x39\xde\xbf\xc0\xed\x3d\x48\x04\x1b\
+\xdc\x94\x65\x1b\x1c\x01\x20\xf8\xf7\xc1\x87\x22\xc6\xde\xb2\x88\
+\xfa\xa3\x1a\xac\x0f\xed\xf8\x4d\xdf\xf5\x4e\x54\xf1\x0f\x82\xf2\
+\x94\x29\xd9\x2e\x82\xe0\xb0\xb1\x17\x94\x40\xb7\x1b\x4d\x06\x76\
+\x07\x34\x7e\xf0\x24\x04\xbc\x1d\x77\x7e\x0b\xc0\xb7\xbc\x9e\xf8\
+\xc8\x88\x01\x89\xc0\x44\x42\x30\x2a\x62\xdf\x17\xe9\x7e\xd1\x12\
+\x6b\x78\x18\x56\xdf\x7b\x2f\x2c\xbe\xf6\x5a\x18\x5d\xbc\x38\x53\
+\x5d\x29\x06\x75\x90\x36\xf0\x56\x24\x81\xdf\x64\xba\x70\x97\xc9\
+\x06\x43\x00\x77\xf1\xf1\xf9\x9f\x04\x9e\xd4\x53\xca\x58\x9d\x23\
+\xad\x1a\x68\xfa\x41\x07\xc2\x66\x67\x9c\x01\x03\x5b\x6f\x19\xec\
+\xf5\x74\x44\x82\x9d\xb6\x5a\xdd\x49\xc0\xb1\x41\x03\x70\x71\xbd\
+\xa8\x0e\xe0\x23\xc0\x1e\x5f\x46\xaf\x7c\xd8\xf5\x89\x14\x4c\x24\
+\x01\x73\x60\x00\x4a\xb8\xa5\x25\x04\x22\xc5\xe1\x7f\xff\x1b\x96\
+\xdc\x70\x03\xac\xbe\x4f\x6f\xac\x4f\x4e\xa3\xb8\x48\xdd\xfa\x38\
+\x92\xc0\x15\xf9\x54\xd7\x79\xd9\x20\x08\xe0\x2e\x9e\xc6\xfb\x5d\
+\xdc\xf6\xc9\x5a\x97\x56\x83\xa0\x1d\xbc\xf9\xe9\xa7\xc1\xac\x13\
+\x4e\x84\xf2\xf4\x69\xf1\xe7\x86\x81\xd4\xa2\x0c\xbb\x3a\x53\x6f\
+\x2d\x02\xbe\xd5\x8c\x2e\xa3\x01\x78\x02\x98\xe1\xfb\x1c\x5e\xde\
+\x53\xaa\xf5\xf9\x9a\xe5\x23\xc9\x26\xe6\x9e\xe9\x7b\x46\x08\x48\
+\x04\xe4\x10\x2d\x0d\x0e\x32\x8d\x21\xa9\xd4\x16\x2f\x81\x97\x7e\
+\xf2\x13\x58\x7e\xf3\xcd\xac\x2d\xa5\x14\x32\x6c\x53\x88\xc1\xc7\
+\x15\xbc\x63\x9f\x0d\x20\x5c\xd8\xf3\x04\x80\xe0\x27\xef\xfe\x4d\
+\xb8\x0d\x15\xdd\x08\x64\xe7\xce\x7d\xf7\x99\x30\xf3\xb8\x63\xa1\
+\x34\x71\x62\xa2\x6b\xd8\x63\x08\x76\x04\xbc\x5d\xaf\x79\xed\x58\
+\x5d\xf0\x79\xbe\x76\x01\x5f\x98\x3a\x1f\x17\x3e\xd4\x35\x27\xa2\
+\x9e\x25\xea\x18\xdb\xb7\x8c\xed\x5a\x9a\x34\x09\x89\x21\x59\xd2\
+\x53\x73\xcd\x5a\x58\x79\xe7\x9d\xb0\xe4\xba\xaf\x43\x63\xf5\xaa\
+\x44\x65\x5b\x49\xc4\xef\xe3\x51\xdc\x8e\x43\x12\x78\x34\xd7\x8b\
+\xb5\x59\x7a\x9a\x00\x10\xfc\x34\x33\xcf\xd7\x70\x4b\x94\x35\x93\
+\xf4\xa1\xa9\x97\x9a\xff\x1f\xef\x83\x19\x47\x1e\x09\xe6\xe0\x80\
+\x76\x39\xd6\xc3\x8f\x8e\x41\x73\x6c\x34\xdc\x7e\xd7\x04\x8f\x16\
+\xe0\x8b\x52\xe7\x75\x9d\x85\xde\x93\xa2\xcf\x89\xd3\x16\x94\x73\
+\xc8\x6f\x40\x24\x5b\x96\x64\x20\x73\x15\xa2\x22\x29\xa2\x2e\x6b\
+\x64\x14\x5e\xb9\xfd\x76\x58\x7c\xdd\x75\xcc\x67\x90\x46\x12\xfc\
+\x3e\x48\x03\x38\x03\x49\xe0\x27\xa9\x2e\xd4\x05\xd2\x93\x04\x70\
+\x17\x4f\xec\xf9\x34\x6e\xe7\x15\xf9\x90\xd4\xe3\xcf\xfb\xcf\xf7\
+\xc1\xcc\x63\x8e\x61\x24\xa0\x23\x64\xcb\x53\x1e\xbc\x35\x3a\xea\
+\x7a\xe8\x9d\x3f\xea\xa9\xf3\x91\xe7\x74\x52\x9d\xcf\xd8\xbb\xdb\
+\x19\xca\x33\x32\x40\x22\x60\x64\xa0\x49\xc0\xa4\x11\x2c\x47\xd3\
+\x60\xe9\x77\xbf\xcb\x88\xb8\x95\x64\x04\x02\x8d\x22\x3d\x1f\x89\
+\xa0\xe7\xd6\x31\xe8\x39\x02\x40\xf0\x93\x6e\xf8\x2d\xdc\x4e\x2a\
+\xec\xc1\xb0\x97\xd9\xec\xd4\x53\x60\x0e\x6e\xf4\xc3\x6b\x25\x2c\
+\x24\x27\x41\x2f\xd5\xfb\x04\xbd\x7b\xe8\x79\x9a\xea\x78\xdb\xd5\
+\x79\xcd\x08\x84\x9d\xb5\x3c\x40\xa8\xd6\x43\xd1\x04\x39\xda\xd1\
+\x28\xb5\xb6\xf4\xeb\xaf\xac\x80\x65\x48\x02\x2f\xdf\x72\x8b\x87\
+\x90\x0b\xf8\xe1\xd3\x0a\x47\x27\x23\x09\xbc\x9c\xb9\xa6\x36\x4a\
+\x4f\x11\x00\x82\x9f\x46\xed\xdd\x8a\xdb\x6b\x8a\x7a\x90\x69\xaf\
+\x7d\x0d\x6c\xf1\xd1\x8f\x40\x65\xc6\xf4\x96\xe7\xda\xf5\x06\x34\
+\x51\xcd\x64\x23\xdf\x92\xf6\xee\xfe\xf3\x92\xf6\xee\x29\xca\xe7\
+\xaa\xce\xc7\xd5\x95\xa6\xbc\xd8\x1b\xa0\x47\x6a\x94\x90\x44\xe1\
+\x56\x22\x03\x22\x85\x88\x27\x76\xa4\xbe\x74\x29\x2c\xf9\xe6\x37\
+\x61\xe5\x1d\x77\xe4\x9e\xf6\xac\xfc\xf6\x68\x72\xd2\x23\x5e\xdd\
+\xb0\x7a\x66\xd2\x91\x9e\x21\x00\x04\xff\x76\xc0\x27\x7c\xdc\xaa\
+\x88\x1b\xa7\xf1\xf4\x5b\x5f\x78\x01\x4c\xdc\x75\xe7\x96\xe7\x32\
+\xbb\x1e\x81\x6f\x4b\xaf\x73\x52\x75\xbe\xa0\xd8\x7b\xe0\x43\x17\
+\xaa\xf3\xc1\xdb\xd5\x25\xb5\xe8\x36\x26\x5f\x41\x05\x89\x80\x69\
+\x6b\x71\xd9\x96\x96\x05\xc3\x8f\x3e\x0a\xcf\x5f\x7a\x29\x8c\x2d\
+\x4a\x3f\x79\x70\x8b\xdf\x1e\x69\x00\x87\x21\x09\xf4\xc4\x14\x64\
+\x3d\x41\x00\x08\xfe\x03\xf1\x46\x6f\xc1\xc3\xa9\xf9\xb7\x80\x01\
+\xf3\xd1\xce\x9f\x75\xe2\x89\xf1\x61\xa8\xa6\x05\x4d\x54\xf1\xc9\
+\xb1\x64\x47\x2c\x59\x97\x35\x76\x5e\x44\xec\x3d\x50\x3e\xe2\xfc\
+\xb8\x6b\x26\xee\xdd\xfd\xe5\x75\xca\x24\x88\x80\x44\x95\x27\x5f\
+\x41\x75\x93\x4d\x98\x56\x10\x47\x04\x64\xaa\xbd\x7c\xd3\x4d\xb0\
+\xf4\xc6\x1b\x5d\x93\x2d\x46\x52\x80\x84\xc2\x10\xa4\x09\xfc\x39\
+\x79\xd1\xf6\x4a\xd7\x13\xc0\xef\x4b\xa5\xd3\x70\xf7\x0d\xc8\x30\
+\x5b\x4f\xe0\xa1\xc5\x53\x4f\x5c\xb8\x10\xb6\xba\xf8\x22\xe8\xdb\
+\x74\x8e\xf7\x04\xf5\xc7\x6b\xd9\x60\xad\x5f\x0f\xcd\x91\x11\xfd\
+\x78\x77\x17\xc7\xde\x5b\x1e\x43\x0a\xc0\x47\x81\x5d\xf7\x9a\x99\
+\x7d\x24\xde\xf2\x8c\x08\x50\xa3\x8b\x25\x02\x2c\x33\xf6\xc2\x0b\
+\xf0\xc2\x95\x57\xc2\xfa\x7f\xfd\x2b\xf0\xe7\x1c\x80\x41\x2b\x1f\
+\x1f\x8d\x24\xf0\xfb\xec\x55\x15\x27\x5d\x4d\x00\x08\xfe\xb3\x70\
+\x77\x4d\x2e\x0f\xaa\x3c\x29\x79\xf7\x17\x7c\xe4\xc3\x30\xfd\x0d\
+\x6f\x88\x76\x24\x21\xf0\x09\xf4\x4d\x04\x3f\xcb\xd6\x6b\x87\x3a\
+\xdf\x81\xd8\x7b\x61\xea\x7c\x8a\x74\x65\x7d\x52\xd4\x23\x28\x1a\
+\x97\x50\x9d\x35\x0b\xca\x53\xa7\x46\x12\x01\x99\x71\x2b\x7f\xfb\
+\x5b\x58\x7c\xcd\x35\xa9\xc3\x86\x31\x82\xbd\x06\x1c\x87\x24\xf0\
+\xab\xbc\x2b\xce\x4b\xba\x96\x00\x10\xfc\xef\x04\xde\xf3\xa7\xba\
+\xc7\x28\xe2\x9f\xbc\xeb\xae\xac\xd7\xaf\x4c\x8b\xb0\x26\x08\xf8\
+\xa8\x22\x36\xd7\xad\xe3\xa3\xe6\x48\x7a\x3c\xf6\x1e\x99\x47\xa0\
+\x59\x3e\x93\x3a\x9f\x73\xef\xae\x73\xcf\xfe\x67\xa4\xf4\xe3\x50\
+\x22\x50\xf2\x0a\xea\xcb\x97\xc3\xa2\xab\xaf\x86\x35\xf7\x64\x9f\
+\x14\xc8\xf7\xdb\xa3\x18\xe4\x49\x7b\xd7\xad\x5b\x33\x57\x5c\x80\
+\x74\x25\x01\x20\xf8\x4f\x06\x3e\xa0\x47\x3b\xa3\xb3\xe5\x48\x5b\
+\xb2\xf5\xcf\x3a\x8b\xcd\xbc\xc3\x86\xb2\x2a\xc2\x7e\x2e\xa4\xea\
+\x8f\x8d\x41\x73\xed\x5a\xaf\x8d\xdf\x63\xb1\x77\x27\xef\x1e\x72\
+\xee\xdd\x35\xcb\x64\x8f\x80\x24\xf7\x57\xe8\x92\x1a\x11\x41\xdf\
+\xe6\x9b\x43\x69\xf2\xe4\xf0\xd3\x1b\x0d\x58\x71\xdb\x6d\xb0\xf8\
+\x2b\x5f\xd1\xf2\x0d\x48\xd1\x18\xe5\x4d\x95\x9d\x86\x24\xf0\x43\
+\xed\x4a\xdb\x24\x5d\x47\x00\x08\x7e\x5a\x70\x93\x52\x7b\x63\xb3\
+\xfb\x92\x0c\xad\xaf\x4e\x99\x02\xdb\x7e\xf1\x0b\x30\xb8\x25\x1f\
+\x20\xe8\xff\xb9\x90\x1a\xd8\x58\xb3\xc6\xf5\xea\x87\x9d\xd4\xa5\
+\xb1\x77\x2d\xc0\x27\x88\xbd\xb7\x7c\xae\x56\x7f\x4b\xd2\x46\xba\
+\xcf\x9c\xa3\xc9\x42\xc7\x14\x3e\xac\xce\x9d\xcb\x86\x2f\x87\x09\
+\x4d\x6e\xfa\xfc\x05\x17\x40\xed\xa5\x97\x42\xff\x9e\x72\x5a\x07\
+\x52\x27\xdf\x8d\x24\x70\x7d\xaa\xd2\x05\x49\x57\x11\x00\x82\xff\
+\x0d\xb8\xfb\x19\x44\x38\xfc\xd2\x34\xfc\xe4\x3d\xf6\x80\xad\x2f\
+\xb9\x38\x3c\x77\x1f\x55\x7c\x52\xf5\x29\xa4\xd7\x99\xd8\x7b\xa0\
+\x60\xec\x35\xd4\xb2\xda\x80\xd7\x88\xbd\x6b\x97\xe9\x02\x75\x3e\
+\xb2\xae\xa4\xd7\xc4\x1f\x53\x75\xce\x1c\xa8\xa0\x69\xc0\x26\x44\
+\xf1\xfd\xb9\xb1\x7a\x35\x2c\xfa\xe2\x17\x61\xf5\x1f\xfe\x20\x4f\
+\xcf\x43\xe8\x66\x3e\x88\x24\xf0\xe5\x5c\x6a\xcb\x41\xba\x86\x00\
+\x10\xfc\x07\x02\x9f\x78\xc1\xc9\xf5\xcc\xda\xe8\x9b\xbf\xf3\x0c\
+\x98\x7d\xea\xa9\x7c\x36\x1b\x12\xe5\xc7\xc0\xd4\x7d\xea\xf5\xd9\
+\xaa\xd2\xed\x88\xbd\x87\x16\x8c\xbd\x4e\xae\xce\xbe\xa8\xf3\x35\
+\xcb\x7b\x4f\xcf\x51\x9d\xcf\x39\xa1\x28\xa9\x46\xc2\xcc\x82\xf9\
+\xf3\x43\x33\x3e\xc9\x0c\x58\x89\x26\xc1\x92\x6b\xbf\xe6\xd5\x0e\
+\x33\x0a\xfe\xac\xdf\xbe\x57\xdd\xfa\x4e\x6e\x15\x66\xbb\x97\xce\
+\x0b\x82\x9f\x86\xf1\xde\x41\xb3\x61\xe7\x51\x1f\xbd\xd4\x6d\x3f\
+\x73\x39\x9b\x3f\x3f\x20\x08\xf8\xc6\xda\x35\x2c\x16\x4c\x52\x5c\
+\xec\xbd\xf5\xf9\xf1\xe5\x7b\x23\xf6\xae\x53\xbe\x48\x75\x3e\xba\
+\x78\xb2\xf2\x14\x32\xec\x9b\x37\x8f\x45\x88\x1c\x11\x03\x90\x46\
+\x1e\x7f\x0c\x9e\xbf\xe8\x62\xa8\x2f\x7f\x09\xd2\x8a\x0f\x68\xe4\
+\x18\x3c\x0c\x49\xa0\xe3\x21\xc2\x8e\x13\xc0\xdd\xe5\x12\xa1\xf4\
+\x77\xb8\x65\x9c\x46\x87\xcb\x00\xbe\xc4\xed\xd0\xde\xa7\x99\x76\
+\xfd\x42\x21\xbd\x06\x4d\x36\x29\x5f\x7c\xca\xd8\x7d\xa8\xfa\xad\
+\x03\xf8\x82\x62\xf7\x79\x3a\xeb\x02\xf7\xd6\x6e\x75\xbe\x83\xe1\
+\x43\x02\x7f\xff\x82\x05\x50\x0a\x99\xd1\xa9\xb1\x62\x05\x92\xc0\
+\x85\x6c\x22\x12\x1d\xd1\x00\xd6\x4a\xdc\xf6\x45\x12\x78\x4c\xab\
+\xc2\x82\xa4\xa3\x04\x80\xe0\xdf\x01\x77\xc4\x82\xa9\x56\xe6\xf1\
+\x3f\xc8\xe4\xdd\x77\x83\xad\x3f\x7d\x99\x77\xf8\x28\x0a\x85\xf3\
+\x68\x79\x2b\x67\xb5\x9a\x84\xb1\xfb\x60\x7e\x7a\x64\xe1\xce\xaa\
+\xf3\x5d\x10\x7b\x8f\xac\x3b\xe1\x33\x76\x84\xa0\x44\xcb\x56\x66\
+\xce\x84\xbe\xb9\xf3\xd8\x78\x03\x55\x9a\xeb\xd7\xc1\xe2\xcf\x7f\
+\x01\x56\xff\x31\x18\x2a\x4c\x09\xa4\xa7\x70\xdb\x07\x49\xa0\x63\
+\x03\x88\x3a\x46\x00\x08\x7e\x02\xfd\xdf\x70\x9b\x97\xc7\xcd\x4f\
+\x3b\xe8\x20\xd8\xe2\xfc\xf3\x02\x9e\x5d\x9a\x84\xa3\xb1\x72\xa5\
+\x32\x79\xa6\xfa\xd7\xf0\x1f\xac\x36\xe0\x75\x7f\xfc\x6d\x50\xe7\
+\x75\x09\x26\x2f\xf0\x18\x71\xd7\xd7\x7d\xce\x4e\x87\x0f\x03\xe4\
+\xed\x1e\xd2\x2c\x45\xfd\x5b\x6f\xcd\xa6\x2e\x53\xc5\xaa\x8d\xc1\
+\xb2\x1b\x6e\x80\x15\xb7\xdc\x02\x39\xc9\xbd\xb8\x1d\x8c\x24\x30\
+\x9a\x57\x85\x49\xa4\x23\x04\x80\xe0\x27\xaf\x1c\x65\x47\x1d\x9c\
+\xc7\xcd\xce\x3c\xf6\x58\x98\x7b\xd6\xfb\x03\xce\xbe\x06\x79\xf8\
+\xd7\xa8\xb3\x36\x69\x02\x26\x21\xe0\x0b\xeb\xdd\xc5\xdf\xec\x04\
+\xe5\x3d\xe5\xf2\x04\x8f\x6f\xf1\x10\x9d\xde\xdd\xe8\xa6\xf0\x21\
+\xc4\xbd\xe3\x88\x72\xa8\x01\xf4\xcd\x9d\x0b\xd5\xd9\xb3\xbd\xa7\
+\x36\x1a\xf0\xca\xad\x3f\x85\x97\xae\xbf\x3e\x72\x5c\x48\x12\xc1\
+\x96\xfd\x11\xee\x4e\xde\xb3\x6e\xd9\x99\x2b\x4b\x7e\xed\xf6\x0b\
+\x12\x00\x4d\xaa\xf8\xd1\x3c\x6e\x6e\xb3\x77\x9c\x0e\xb3\x4f\x3b\
+\xcd\x33\xe7\x3e\x79\xf6\x1b\xab\x56\x7a\x16\xa8\x4c\x33\x93\x4e\
+\x26\xc0\xe7\x10\x7b\x0f\xfc\x2d\xcc\x77\xe1\x39\xa5\x4b\x9c\x75\
+\x3e\xb2\x30\x62\xca\x17\xad\xce\x87\x36\x74\x6c\xdd\xc1\xbf\x51\
+\xde\x40\xff\x96\x5b\xb1\x31\x06\xce\x19\x68\x56\xae\xb9\xfb\x6e\
+\x58\xfc\xa5\x2f\xa6\x5a\x08\x35\xe4\xb7\x7d\x39\x12\xc0\xf9\x89\
+\x2b\xca\x28\x6d\x27\x00\x04\x3f\x2d\xd6\x71\x53\x1e\x37\x33\xff\
+\xec\xb3\x61\x93\xa3\x8f\xf6\x14\xa4\x97\x51\x5f\xb1\xc2\x4d\xe3\
+\x25\xe9\xa4\x3a\x9f\x32\xf6\xee\x7c\x52\x9f\xc3\x77\x3f\x76\x0c\
+\x29\x19\xce\xc5\xf2\x71\xd6\xd9\xbe\x73\x8c\x90\xf2\x0e\x09\xfb\
+\x35\x07\x88\x72\x9c\x26\x37\x07\x8c\x88\x73\xd2\x01\xde\x0e\xaf\
+\xdb\x57\x9e\xfe\x46\x91\xa5\x81\x85\x0b\x03\x33\x43\xad\xff\xc7\
+\x3f\xe0\xc5\xcb\x2f\x63\x19\xa4\x71\xa2\xf9\xdb\x3e\x13\x49\xe0\
+\x9b\x7a\xa7\xe6\x23\x6d\x25\x00\x04\x3f\xad\xc6\x7b\x3f\x6e\x4e\
+\xd0\x35\x6d\xa2\xff\x56\x17\x5e\x08\x53\xf6\x7b\xad\xe7\x6b\xe6\
+\xe5\x5f\xa5\x4c\x08\x19\xd4\xa7\x95\xc3\x0e\x3a\xeb\x34\xca\xab\
+\x80\xb3\xa5\x3a\x1f\x42\x64\x33\xfe\xe3\x3f\xd3\xb4\x60\x6e\xb2\
+\xfc\x4b\x5f\x88\x6f\x27\x12\x4a\xb4\x49\x19\x7e\x0c\x03\xbc\x36\
+\xd8\x03\x9f\x23\x00\xef\x03\x7b\x54\x79\x1a\x38\xd6\xbf\xcd\xb6\
+\x50\x9e\x3a\xc5\x53\x66\xf8\x5f\xff\x82\x17\x2e\xbe\x88\x0f\x1c\
+\x93\xe7\xa6\x6b\x4e\x4a\x19\x3e\x02\x49\xa0\x6d\x6b\x0f\xb4\x8d\
+\x00\x10\xfc\x04\xfa\xfb\x8d\x84\x4b\x72\x87\xc9\x36\x97\x5e\x0a\
+\x93\xf7\x55\x66\x00\xc7\x97\x44\xa9\xbc\xcd\xb5\xeb\x94\xb3\x34\
+\x1d\x6c\x6d\x52\xe7\xb5\xea\x52\xea\x91\x1a\x8c\x45\x3f\x54\xcb\
+\x76\x7f\xf4\x3e\xdb\x7e\xe6\xfb\x3f\x90\xb5\x39\x33\xc9\xb2\xcf\
+\x7f\x36\xf4\x7b\x43\x3c\x87\xa9\x4c\xe6\x19\xd0\x1a\xfc\xcf\x9d\
+\x15\xf0\x11\x60\x97\x75\xcb\xfb\x8a\x04\x7c\x4c\x79\xf5\x6f\x7d\
+\xf3\xb7\x80\xea\xa6\x9b\x7a\xfe\xbc\xfe\x81\x07\xe0\xc5\xcb\x3e\
+\x9d\xc7\x88\x42\x5a\x32\xf9\x55\x48\x02\x4f\x65\xad\x48\x47\xda\
+\x46\x00\x7f\x28\x97\x68\xe6\xd4\xe3\xb3\xd6\xb3\xe0\xa3\x1f\x81\
+\x69\x87\x1d\xe6\x7e\x81\x2f\xa6\xbe\x72\x15\x58\x23\xc3\x9e\xef\
+\xc2\x8e\x73\x1f\x37\x9f\xb4\x8c\xa6\x2d\x2c\x7b\x7b\xbe\xce\x1f\
+\x07\xbf\x5c\xf3\xcf\x7f\xdd\x59\x1f\xf8\xaf\xac\x4d\x9a\x49\x96\
+\x7c\xf6\x0a\x37\x32\xa0\x98\x00\x86\x5c\x6f\x50\x9a\x06\xbe\x74\
+\x5b\x4f\x5a\x73\x01\xea\xbc\x5b\x77\x74\x3b\x47\x02\x3e\xd2\x64\
+\x74\xff\x46\x29\xc4\xfd\x34\xb6\xc4\xf1\x3d\xd9\xb0\xee\xfe\xbf\
+\xc0\xa2\x2b\xaf\x70\x92\xcc\xd2\x0a\xd6\x48\x13\x89\xec\xf7\xaa\
+\x7a\xf1\x93\x8c\xb6\x85\x00\x10\xfc\xe7\xe2\xee\x33\x59\xeb\x99\
+\xfb\x9e\x77\xc3\xcc\x93\xdc\xb9\x40\x59\x7c\xff\x95\x57\xbc\x4e\
+\x98\x2e\x54\xe7\x83\x97\x8b\xd7\x08\xa4\xfa\x6f\x8b\x05\x3d\xd9\
+\x5e\x29\xa7\x96\x9f\xfd\xc1\x0f\x65\x6d\xd6\x4c\xb2\xe4\x8a\xcb\
+\x03\xdf\xd1\x8f\x4a\x12\x80\xdc\x6c\x70\x7f\x6c\x46\x9a\xde\x3d\
+\xf0\xb9\x35\xe0\xe3\xdf\xbf\x06\xe0\x7d\xf7\xe9\xf5\x11\xd8\x50\
+\x1a\x9a\x02\x03\x0b\xb7\x73\xd6\x7f\xa4\xeb\xad\xa5\xa5\xcb\xbe\
+\xf0\x79\xad\x99\x88\xfd\x6d\xe6\x93\x8b\x90\x00\x2e\x4c\x54\x49\
+\x0a\x29\x9c\x00\x10\xfc\xb4\x70\xc7\x2f\x21\xc5\x72\x5d\xea\xcd\
+\xcd\x7a\xcb\x49\xb0\xd9\x99\x67\x3a\x9f\x29\x14\x53\x7f\xf9\x65\
+\xcf\xb0\xcd\x9e\x4e\xa5\x8d\xb0\xfd\x2d\x04\xbf\x25\x89\x80\x7a\
+\x53\x1f\xc1\xcd\x39\xfb\x9c\xa4\xcd\x9a\xab\x2c\xbe\xfc\xd3\xca\
+\x27\xb7\x37\x94\xc0\x37\x4d\xd3\x1d\xd3\x6d\x48\xc3\x40\x6d\x1c\
+\x8d\x76\x8e\x9d\x6b\x21\x7c\xf6\x60\xad\xde\x3d\xe6\xfa\x1e\xc0\
+\x7b\xda\xdc\x5b\x9c\xf2\x05\x06\x77\xda\xc9\x99\x98\x94\x91\xc0\
+\xdd\xbf\x87\xc5\x57\x5f\x1d\x3b\x7e\x40\x03\x78\xd4\xfb\x93\x16\
+\x50\xe8\xb4\x62\x85\x12\x00\x82\x7f\x73\xdc\xd1\xe4\x88\xda\x99\
+\x7e\x61\x37\x34\xe3\xf0\xc3\x61\xee\xd9\x1f\x72\x54\x4c\x02\x3d\
+\x0d\xd5\x0c\x9d\xb0\xc3\x2f\x29\x63\xef\x49\xcb\xeb\x2d\xcd\xd5\
+\x9a\xa0\x2c\xe9\xec\x23\xf0\x13\xe8\x69\x9d\x40\x01\x7e\x4b\x9a\
+\x00\x4a\xd1\xcd\xce\xf9\xb0\x6e\xd3\x16\x22\x2f\x5e\x76\x69\xc0\
+\xbe\x36\x0d\x70\xd4\x7e\x13\x3f\x98\x86\x29\xfe\x68\x04\x7a\xd1\
+\xf0\xf6\x88\xd2\xe2\xc0\xbd\x0e\xa4\x00\x7c\x8c\x76\xa1\x05\xf8\
+\x88\xeb\x31\x12\xd8\x79\x67\x30\xfa\xc4\x20\x56\x7c\x4f\xab\x7f\
+\xfb\x5b\x58\xfa\x3f\xff\xe3\x74\x50\x29\x81\xf6\x34\x6e\xbb\x22\
+\x09\xac\x4b\x57\xbc\xb5\x14\x4d\x00\xb7\xe3\xee\x88\x2c\x37\x30\
+\xe5\x35\xfb\xc2\x82\x4f\x7c\xc2\x19\xa4\x41\xeb\xbf\xd5\x55\xf0\
+\xab\x92\xa7\xb3\xae\x45\x79\xbd\xa5\xb9\x52\x9a\x03\x4a\xcf\x2f\
+\x7b\x7f\x8b\xe5\x88\x08\xcd\x40\x79\xf6\xcd\x3f\x72\xae\xc6\x9b\
+\x28\x4e\x9e\xbf\xe4\x22\xb6\x37\xd4\x96\x25\xe0\x1b\x1c\xf8\x94\
+\x4e\x6b\x4a\xfb\x3f\xd2\x11\xd8\x1a\xf0\x79\xab\xf3\x52\x02\xfe\
+\x87\xa4\x1a\x89\xf8\x9e\x93\xc0\x2e\x6c\x1a\x32\xf9\x3d\x65\x0b\
+\x2e\xff\xf6\xb7\xb2\x36\xf1\x0d\x48\x00\xef\xcc\x5a\x49\x94\x14\
+\x46\x00\x08\xfe\x53\x80\x2f\xd8\x99\xf0\x82\x6e\xe3\xd2\xa4\x9d\
+\x5b\x7f\xf6\xb3\x4e\xa3\x32\xb5\x7f\xf9\x72\x25\xad\xb7\x3d\xce\
+\x3a\x2d\xc0\xa7\x70\xf6\x81\xd2\x73\x3a\xf6\xbd\xf8\x5e\xf6\xf8\
+\x4d\xd2\x00\x1c\x22\x10\x67\x28\x44\x31\xf7\x63\x6d\xcf\x1d\xf1\
+\xc8\x73\x17\x5d\xe0\x34\x8e\x63\x7f\x0b\x02\x20\x2d\xa0\x24\x09\
+\xc0\x71\x04\xc6\x39\x65\x21\xd0\x1e\xc1\xf6\x4c\xa7\xce\x4b\x31\
+\x74\x7a\x77\xcd\x6b\xfa\xcb\x97\x88\x04\x76\xd9\xc5\x59\xf5\x98\
+\xfc\x00\x4b\xbf\xfa\x55\x58\x73\xd7\x9d\x99\xda\x18\xdb\xe3\xc4\
+\x3d\xea\xc5\x2c\x3f\x56\x08\x01\x20\xf8\x37\xc1\x1d\x2d\x8e\x30\
+\x23\x09\xe0\x55\xa9\x0c\x4d\x81\xed\xbf\xf1\x75\x67\xfa\x26\x06\
+\x7e\xea\xf9\x5b\x80\x3f\xab\xb3\xce\xf9\x0d\x43\x8b\xa8\x41\xd2\
+\xde\x5d\x39\x2f\x6e\x32\x0f\x35\x02\x60\xe1\xb3\x12\x01\x48\x4d\
+\x80\x7f\xcf\xef\x52\x96\x9f\x77\xde\xc7\x93\xbd\x9c\x9c\xe5\xb9\
+\x0b\x3f\xe5\xf9\x6c\x88\x98\xbf\x81\xbd\xbf\x59\x32\x91\x00\xb8\
+\x1f\xc0\xef\x08\x4c\xe3\xec\x4b\xa2\xce\x3b\xf7\x93\x50\x9d\x8f\
+\x0b\xff\xf9\xcb\x87\x99\x0c\xc6\x00\xd7\x04\x4c\x41\x02\x94\x20\
+\xf4\xe2\x25\x17\xc3\xe8\x13\x4f\x68\xb7\x69\x08\x66\x56\xe0\xb6\
+\x0b\x92\x40\xfa\xc5\x0c\xf4\xaf\x95\x5d\xee\x29\x97\x68\x3e\xbf\
+\xb7\x85\xff\xd5\x6e\x5d\x01\xfe\x58\x16\x5e\x73\x0d\x0c\x6c\xbd\
+\x35\x2f\x11\x03\xfe\xac\xce\xba\xb6\x3b\xfb\x82\x85\x3c\xc7\x52\
+\x03\x60\xe0\xa7\x5e\x9f\xd2\x9a\x9b\x0d\x8f\x6f\x40\xad\x6d\xfe\
+\xf9\x9f\x68\xdd\x9e\x05\xca\xb3\x9f\xfc\x84\xab\x01\x48\x88\x1b\
+\xc0\x9d\x7f\xd2\x09\xc8\x8e\xcd\x70\x13\x40\x13\x7c\x3a\xbd\xbb\
+\x4a\xb0\xc1\xaa\x93\x69\x14\x41\x2b\x43\x23\x47\x41\x1c\x52\xb6\
+\x20\xf9\x04\xc8\x2c\x20\xa9\x2d\x5e\x04\x2f\xa0\x19\x4b\x83\xd2\
+\xa2\x44\x03\x88\x94\x1c\x74\xd8\x1e\x39\x8f\x17\xc8\x9d\x00\x10\
+\xfc\x47\xe1\xee\xe7\xbe\xd6\x4b\x54\xc7\xbc\xb3\xce\x82\xe9\x94\
+\xe2\x4b\x25\xa5\xcd\x2f\xc0\x9f\x45\x9d\xcf\xea\xec\x0b\x16\x4f\
+\x58\x46\x43\xa3\x60\x96\x3e\x01\x9f\x42\x9c\xb4\x18\x89\xd5\x64\
+\xcf\xde\x14\x1a\x80\x24\x00\x79\xed\x05\x9f\xbc\x20\x51\xdb\xe6\
+\x2d\x4f\x7f\x42\x9a\x20\x12\x20\xfc\xdf\x92\x00\x7e\xa9\x54\x82\
+\x12\x01\xdf\x34\x83\x19\x81\x09\x63\xef\xfc\x4f\x51\x26\x94\x66\
+\x3b\x6b\xaa\xf3\xce\x93\xc4\xe5\x28\xc4\x10\x4c\x69\xc2\x04\x6e\
+\x0e\x90\xf9\x8a\x15\x0d\x3f\xf8\x20\xbc\x78\xd9\x65\x60\x8b\x90\
+\x75\x4a\xe0\x9d\x83\x04\xf0\x85\x74\x45\xc3\x25\x57\x02\xb8\x87\
+\x67\xfb\x3d\x8c\x2d\x30\x37\x6d\x1d\xd3\x0e\x3c\x10\xe6\x9f\x7b\
+\x2e\xfb\xb1\x10\x08\x6a\xcb\x96\x71\x4f\x6a\x06\x75\x3e\xf6\xfc\
+\x56\x7f\x73\xbe\x4e\xae\x11\x68\xfb\x18\xfc\xf7\x2c\xed\x7f\x7c\
+\xee\xa6\x34\x03\x9a\x16\xff\xf1\x39\x26\x02\x3f\x7f\xcb\x0b\x2f\
+\x4a\xdb\xd4\xb9\xc8\x93\xe7\x7f\x8c\xdd\xb4\x67\xdd\x05\x00\x17\
+\xfc\xb4\x17\x64\x20\x94\x83\x90\xf6\x08\x6f\x33\x3b\x42\x23\x88\
+\x05\x7c\x4a\x75\xde\xb9\xf7\x16\xbd\xbb\xf6\x35\xc9\xff\x31\x79\
+\x88\x47\x07\x68\x94\x2a\x9e\xb3\xea\xd7\xbf\x86\x97\xae\xbb\x36\
+\x5e\x23\x8c\x17\x62\x8f\x57\x21\x09\x3c\x9c\xb6\x02\xbf\xe4\x4c\
+\x00\xe6\x57\x70\x97\x28\x39\x5d\xbd\x81\xfe\x39\x73\x60\xdb\xaf\
+\x7c\x85\x0f\xb8\xc0\x46\xa2\x50\x5f\xcb\x85\x37\xbb\x58\x9d\x0f\
+\xbd\xc7\x16\xe5\xa5\x06\x40\x80\xb7\xb0\xf7\x6f\x08\x12\x60\x4e\
+\x40\xca\x0a\xb4\x65\xe8\x93\xef\xb6\xba\xf8\x92\x24\xcd\x9d\xbb\
+\x3c\x71\x2e\x1f\xd4\x69\x48\x54\xca\xf8\x3f\x01\xa0\x64\x02\x8d\
+\xfc\x36\x69\x93\x49\x41\xca\xbd\x07\xda\x26\x85\x46\x90\x56\x9d\
+\x97\xa2\x05\xf8\x14\x11\x08\xd6\x14\x48\x7a\x2c\x63\x70\xdb\x6d\
+\xb9\x0f\x04\xdf\xe5\xf2\x6f\xdd\x08\xab\x6e\xbf\x3d\x4b\x93\xdf\
+\x81\x04\x70\x58\x96\x0a\x54\xc9\x8d\x00\x10\xfc\xfb\xe1\xee\xee\
+\x56\x75\x46\xfe\x11\x7f\x24\xdb\x5f\x7b\x2d\xf4\x89\x1c\xeb\xfa\
+\x2b\xaf\x38\xab\xf2\x38\xd2\xad\xea\x7c\x1c\x29\x25\x2c\xcf\x08\
+\x80\x00\x8f\xdf\x31\x0d\x00\xb7\x86\x4a\x00\x4e\x28\x90\xef\xb7\
+\xbe\xe4\x52\xe8\xa4\x3c\xf1\xd1\x8f\xf0\x03\x43\xf6\xce\x06\x23\
+\x03\xb2\xf9\xcb\x65\xd2\x00\x4a\x4c\x13\x90\x8e\x40\x23\x29\xe0\
+\x53\x82\x2f\xaa\xbc\x27\x5f\x23\x31\xe0\xe3\xb4\xb8\x08\xed\x06\
+\x9f\x9d\xe6\x1a\xac\x6e\xb1\x05\xfb\x68\x91\x53\xf0\x82\x4f\xc1\
+\xd8\x73\xcf\x25\x6a\x67\x1f\x6e\x8e\xde\xbd\x6e\xdd\x96\xa8\x02\
+\xbd\x7a\xd3\x09\x82\x9f\x5c\x9e\xff\xc4\x6d\xbb\xb4\x17\x99\xf7\
+\xdf\xff\x0d\x53\x0f\x3e\x98\x9d\x4b\x03\x7b\xd8\xa8\xbe\x1e\x53\
+\xe7\xf3\x9a\x15\x47\x7a\xfd\xa9\xe7\x6f\x08\x02\xe0\xe1\x40\x5b\
+\x31\x03\x78\x91\x6d\x3e\x7d\x99\x46\xeb\x16\x27\x8f\x7f\xe4\x1c\
+\xcf\xaf\x9f\x13\x00\x77\xfe\x95\x69\x2b\x97\x19\x09\x18\x25\xae\
+\x15\x84\x3f\xb7\x46\x3b\xb5\x00\x5f\x54\x79\x37\x37\x31\x7f\x8d\
+\x22\xd4\x47\x10\x52\x9e\xcd\x3c\xbc\xd5\xd6\x7c\x0a\x72\x94\xd1\
+\xc7\x1e\x85\x17\x2f\xbe\xd8\xf1\x07\x84\x49\x0b\xcc\x3c\x8a\xdb\
+\xce\x48\x02\xfa\xab\x97\xa4\xbb\x8e\x9e\x20\x01\x50\x3e\xa8\x13\
+\x90\x4e\x5a\xe9\x94\xd7\xbe\x16\xe6\x9f\x77\x1e\x3b\xb6\x46\x46\
+\x78\xac\x5f\x3d\xa1\xdd\xbd\xbb\xe6\x75\x8a\x98\xb3\x5e\x4d\xff\
+\x75\xc0\x5f\xaf\xf3\x88\x80\x30\x01\xd4\x62\xdb\x5d\x9e\x79\x88\
+\x45\x26\x79\xf4\x9c\xb3\x9d\x63\x1e\xfe\x73\x09\x80\x7a\x7e\xd2\
+\x02\xca\xc2\x07\xc0\x4d\x00\xdf\xaf\x23\x8b\x3a\x1f\x17\x66\xcd\
+\x59\x9d\x97\xf5\x46\xbf\xff\x98\x77\x4e\x21\x51\x34\x6b\x07\xb6\
+\xdf\x1e\x4a\x43\x43\x2c\x53\x70\xc5\x2d\x37\xc3\x2b\x3f\xf8\x81\
+\xa7\x48\x42\xdc\x9c\x85\x04\xf0\x95\x64\x45\x82\x92\x99\x00\x10\
+\xfc\xf3\xb1\x92\xc7\x21\xe5\xea\xbd\xe4\x2d\xdd\xfe\xeb\x3c\xde\
+\x4f\xf3\xf7\xd5\x96\x2e\xcd\x04\x78\xed\xd8\xbd\xee\x39\x19\xd4\
+\xf9\x34\xfe\x06\xdb\xc9\xfc\xb3\xa0\x4e\x2b\x16\x09\x33\xa0\xc9\
+\x48\x81\x19\x08\x6e\x35\x78\xb0\xf0\x8a\x2b\xd3\x34\x7b\x6e\xf2\
+\xef\xb3\xf9\x68\x44\x99\x4a\x2b\x41\xce\x9c\x7f\x08\xfe\x4a\xa9\
+\xcc\xfd\x00\x44\x00\x00\xe1\x93\x86\x88\x1a\xc2\xdb\x5c\xa3\x9d\
+\xb3\x8e\x2f\x88\xd1\x28\x0c\x1d\xc0\x6b\xf8\x2b\x68\x36\x21\x16\
+\x1e\xdc\x69\x27\x30\x70\x4f\x2b\x4e\x2f\xfe\xf4\xa5\x89\xf2\x03\
+\x7c\x42\x13\x89\x6e\x83\x24\xb0\x2a\x6d\x05\xec\xbe\xb2\x14\x26\
+\xf9\x63\xd9\xa4\x19\x4c\x52\xa7\x2a\xce\xff\xd8\xc7\x98\x06\x40\
+\x8d\x5b\x5b\xbc\xd8\x1d\xdc\xd3\xe5\xce\xba\x44\xc7\x09\xee\x59\
+\xe6\xfd\x37\x29\x04\xd8\x6c\x30\x12\xa8\x37\x28\x0a\xd0\x60\x04\
+\xc0\x7a\x36\x25\x14\xbc\xfd\x67\x3f\x97\xb6\xe9\x73\x91\x87\xff\
+\xeb\x2c\xe7\x58\xf6\xee\x64\xff\x33\x07\x20\x39\xc1\xca\x15\x46\
+\x04\x25\x65\x64\x60\x5a\x75\x5e\xf9\x10\x7a\x98\x47\xf8\x2f\x72\
+\x4c\x47\xe2\x84\xa4\xa0\x69\x50\x1a\x1c\x64\x2b\x54\x0d\xec\xb4\
+\x33\x9b\x6f\x70\xec\xe9\xa7\x60\xd1\x85\x17\x32\xad\x37\xa5\x7c\
+\x1e\x09\x20\xd3\x60\x90\x4c\x04\x80\xe0\xdf\x06\x78\xc6\x5f\x39\
+\x4d\xf9\x49\xbb\xed\x06\x5b\x5e\x74\x11\xf3\x1c\xd3\xc8\x3e\x67\
+\x46\x95\x36\xc7\xde\x03\xf5\xa6\x28\x9f\x87\xbf\xc1\x16\xdf\xd9\
+\xc2\x01\xd8\x40\xe0\xd7\x1b\x75\x46\x02\x16\x73\x04\x12\xfc\x2d\
+\xe7\xb7\x45\xe7\xed\xf0\xb9\x5c\xc3\xc2\x89\xe5\xe1\x0f\x9e\xe5\
+\x3c\x8b\x21\x93\x80\x48\xe5\x35\x0d\x04\x7f\x99\x6d\xdc\x19\x68\
+\xba\x51\x80\xb0\xc7\xd7\x06\xaf\xc6\xbb\x48\x91\xcd\xa7\xad\xce\
+\x6b\x00\x3e\x72\x3e\x02\xd2\x8a\x50\xe3\x25\x5f\x00\x73\x0a\x52\
+\x68\xf0\xb6\x9f\xc3\xcb\xdf\xfe\x76\xe2\x76\x17\xf7\x4d\x63\x8e\
+\x77\xd8\x2d\xc3\xe4\x21\x59\x09\x80\x72\xfd\x4f\x49\x5a\x8e\x86\
+\x87\xd2\xe0\x9e\xed\xae\xbb\x0e\x2a\x33\x66\xb0\xf5\xf9\x88\x00\
+\xfc\xd2\xae\xd8\x7b\x9a\xf2\x45\x68\x24\x0e\x01\x08\x07\x60\x9d\
+\x32\x20\xeb\x35\xc7\x17\xe0\xe6\x02\xb8\x45\x77\xfc\xfc\x17\x93\
+\x36\x7f\xae\xf2\xaf\xb3\xfe\xd3\x73\x3f\x2c\x02\x60\x1a\xcc\xf1\
+\x47\xc0\x97\x24\x50\x62\x73\xec\x1b\xe1\x26\x40\x41\xea\x7c\x58\
+\xf8\xaf\x25\xe0\x75\xc3\x8f\x3a\x80\x0f\xe9\x60\x28\x31\xc8\x44\
+\x4d\xa0\x7f\xdb\x6d\xa0\x3c\x65\x2a\x9b\xc8\x66\x29\x9a\x71\xc3\
+\x8f\xc4\x87\xf6\x63\x80\x7a\x33\x12\xc0\x09\x90\x52\x52\x13\x00\
+\x82\x9f\x16\xf5\x78\x08\x34\x97\xf0\xf6\x9f\xb4\xe9\x7b\xdf\x0b\
+\xd3\x8f\x3c\x92\xa9\xfc\x63\x4b\x96\x30\xc7\xc8\x86\xa6\xce\x27\
+\xbd\xa6\xf4\xf0\x73\x13\xa0\x09\xb5\x5a\x5d\x68\x00\x0d\x46\x02\
+\xd2\x3c\x70\xae\x89\xbb\x9d\xbf\x74\x95\x4e\xf3\x17\x26\x0f\xfd\
+\xe7\x7b\xc1\x33\x0f\x00\x88\x21\xc0\xa6\x0b\x7e\x49\x00\xea\x2c\
+\x41\x4a\x03\x84\x1e\xe6\xa1\xce\x47\xbf\x8b\xec\xea\xbc\x94\x56\
+\x80\x0f\xbb\x1e\xad\x43\xc8\x9c\x82\x3b\xee\xc4\x7c\x03\x63\xcf\
+\x3e\x03\x2f\x7e\xea\x53\x81\xa8\x40\x02\x70\x1e\x80\x24\xf0\x07\
+\xfd\xd3\x53\x5d\xc3\x2b\x48\x00\x34\xb3\x6f\x24\xf3\xc4\xb1\x42\
+\x75\xb3\xcd\x60\x9b\xab\xae\x62\x5a\x00\x39\xfd\x5a\x26\xfb\x84\
+\x7d\x9f\xa7\xb3\xae\xd5\xdf\xc2\xea\xd5\xd5\x48\x62\xeb\xf6\xde\
+\xb3\xab\x01\xd8\xac\xc7\x6f\xa0\xea\x5f\x23\x1f\x00\x12\x01\xcb\
+\x08\xa4\x24\x20\x31\x3b\x90\xac\x6b\x97\xab\x3b\xbb\xd0\xec\x3f\
+\xdf\xf7\x1e\xf7\x51\x0c\xee\x07\x90\x49\x40\x64\xff\x57\x29\x0a\
+\x50\xe6\x8e\x40\x4e\x00\xa1\x8f\x9f\x8b\x3a\xdf\xb2\x7c\xe0\x9d\
+\x41\xcb\x32\x51\x93\x8f\x04\xef\x45\x5f\xa3\xa0\xdf\x3d\x39\xbd\
+\x29\x22\x40\x49\x42\xf4\x4e\xc9\x0c\x58\xfd\x8b\xd4\x09\x42\x7f\
+\xc5\x6d\xef\xdd\x52\x8c\x13\x48\x45\x00\x08\x7e\x5a\xcf\xef\x6f\
+\x6a\x79\x2d\x35\x40\xc8\x96\x97\x5f\x0e\x83\x0b\x17\xb2\x25\x98\
+\xfd\xf1\x7e\x8f\x6c\x40\xbd\xbb\xf8\x22\xa2\x6e\x65\x27\x73\x00\
+\x28\x0a\x40\x51\x11\xa1\x05\x30\x02\xa0\x29\xe2\x6c\x57\x53\x20\
+\xd9\xf5\x9a\xaf\x26\x68\xf9\xfc\xe5\x81\xf7\xbc\xdb\x79\x00\x03\
+\xdc\x34\xe0\xb2\x08\x01\x56\xb1\x87\xab\x88\x5c\x00\x67\x58\x70\
+\x6c\x3b\xe9\xab\xf3\x49\xca\xc7\x6a\x17\x3a\x13\x90\xc4\xfe\xe6\
+\x92\x6b\x14\xa4\x05\xd0\xb0\xe1\xea\xdc\x79\x6c\x29\x32\x0a\x7d\
+\x2f\xa6\x01\x43\xab\x53\x3b\xf5\x4f\x45\x02\xf8\x5e\xd2\x42\x69\
+\x09\xe0\xff\x10\xf0\x47\xa7\x29\x3b\x65\xff\xfd\x61\xf3\xb3\xcf\
+\xe6\x21\x3f\x52\xfd\x3d\xed\x98\x01\xf0\x79\x3a\xeb\x34\xcb\xc4\
+\x9f\x17\xe3\x73\x88\x53\x1b\x6d\x3e\xf9\x47\xb3\x89\xbd\x3f\xcd\
+\x7c\xc4\x48\xa0\xe6\xa6\x03\xdb\xde\x32\xbb\x7e\xb9\xc3\x04\xf0\
+\xee\x77\x79\x7f\x44\x06\x88\xfc\x7f\x04\x3f\x69\x00\x55\x24\x80\
+\x92\xc8\x06\x34\x8c\x90\x1f\x5c\xe7\xd4\xf9\xb8\xf9\x08\x12\xf7\
+\xee\x9a\xd7\x74\xbe\x27\x92\x9c\x36\x8d\x65\x0a\x52\x7e\x80\xd9\
+\x3f\x00\xab\x6f\xbf\x2d\x95\x43\x90\xdd\xb3\x61\x3f\xb8\x6b\xcd\
+\xde\x35\x71\xb9\xa4\x05\xfe\x54\x36\x5f\x0d\x7c\xd6\xd2\xe4\x37\
+\x89\x0f\xbb\xed\xd7\xbe\xc6\x56\xee\x25\xf0\x47\x2f\xd6\x99\x41\
+\x9d\x2f\xd2\x59\x17\x7b\x6e\x7c\x2f\xa2\x53\x5e\x9a\x00\x96\x18\
+\x02\x4c\xde\xff\xb1\x31\x1e\x05\x60\x43\x82\x7d\x4e\x40\x8a\x08\
+\xec\xfe\xd5\x6b\xd3\xbc\x8a\xdc\xe4\xef\x67\x9e\xc1\xbd\xff\xf2\
+\x51\x48\xfd\x27\x12\x28\xa1\xed\x5f\x29\x43\x1f\xf9\x00\x2a\x9c\
+\x04\x0c\x23\x3a\x0f\xa0\x13\xea\xbc\x94\xe0\xac\x40\xf9\x69\x14\
+\xd1\xbf\x53\xe4\x80\x09\x13\x58\x58\xd0\xc0\x3d\x2d\x3a\xd2\x5c\
+\xb3\x06\x16\x5d\x74\x21\xd4\x17\xb5\x1e\xf6\x6f\x18\xa1\xbf\xd3\
+\x83\x91\x04\x7e\xd7\xb2\xb0\x5a\x4f\x92\x93\x49\x90\x00\x7e\x8d\
+\xbb\x43\x93\x96\x23\x99\xf1\xa6\x37\xc1\xec\xd3\x4f\x67\x4b\x74\
+\xd3\x6c\xbe\xfe\x46\xc9\xaa\xce\x07\x56\x77\xf1\x14\xcf\xa6\xce\
+\x07\xeb\xd6\x00\x7c\x02\x73\xc0\x89\x8e\x5b\x3c\x09\xa8\xc1\x9c\
+\x80\xbc\xf7\xaf\xc9\x84\x20\xe9\x28\x55\xea\xda\xe3\x6b\xd7\xa5\
+\x79\x15\xb9\xc9\x5f\xcf\x38\x1d\x94\x3c\x60\x66\x0a\x96\xc4\x38\
+\x00\x52\xfd\x49\x03\x20\x33\xa0\x2c\x42\x83\x6c\x50\x4c\x46\x67\
+\x5f\x16\x75\x9e\xdd\xa6\x4e\xef\xae\x7d\xcd\x98\xdf\x48\x44\x79\
+\xe7\x9a\xd8\x16\x95\x4d\x66\xf0\xf1\x02\x5b\x6c\x01\xa5\xa9\xd3\
+\x60\xf8\xfe\xfb\x61\xe9\x17\xc3\x43\xbb\x11\xa0\x57\xe5\xff\x90\
+\x00\xde\xd4\xea\x24\x4f\x9d\x49\x4e\x46\xf0\x1f\x00\x7c\x39\xef\
+\xc4\x42\xab\xf6\x6e\x7b\xed\xb5\x8c\xf1\x6a\x2f\xbe\xe8\x64\xbc\
+\x85\x4a\x82\xd8\xbd\x11\x5a\x3c\xbb\xb3\xce\x5b\x77\x6b\xb5\x31\
+\xee\x3e\xc3\xca\x47\xae\x4a\x63\x8b\x2c\xc0\x66\x83\xd9\xff\x44\
+\x00\x63\xb5\x31\x96\x13\xc0\x52\x84\xd5\x7b\x44\xb2\xd8\xeb\xeb\
+\xdf\x48\xf3\x3a\x72\x93\xbf\x9c\xfe\x76\x65\xb5\x0d\xee\xe4\x2b\
+\x89\x71\x00\x7d\x15\x02\x7f\x95\xe7\x02\xa0\x46\xc0\x08\x20\xf2\
+\x5d\x64\x07\x9f\xb7\xbc\xb7\x4c\x51\xe1\xbf\x96\xc3\x9b\x5b\x5c\
+\x93\x42\x82\xa5\x29\x53\xc0\x40\x73\x69\x60\xc7\x1d\xd9\x34\x62\
+\x4b\x3e\xff\x39\x18\xfd\xd7\xbf\x74\x00\xef\x17\x1a\x2a\xba\x1d\
+\x92\xc0\x93\xba\x05\x92\x12\xc0\xad\xb8\xd3\x62\x18\x7f\xc5\x34\
+\x9f\xff\x8c\x37\xbf\x99\x8f\xf2\xf3\xaf\xa3\x96\x30\x76\xef\xaf\
+\x3b\xb3\xb3\x0e\x34\x01\x9f\xa2\xe7\xd2\x09\x19\xf1\x12\xde\xd9\
+\x80\xa4\x06\x30\x36\x26\x1c\x81\xcd\x06\xcb\x0e\xe4\xf5\x71\xa2\
+\xa0\xa3\xbd\xbf\x71\xbd\xce\xeb\x28\x4c\xee\x7b\xfb\xa9\xbe\xf7\
+\x61\xf0\x41\x40\xd8\xab\x51\xef\xdf\x47\x7e\x80\x8a\x20\x00\xd5\
+\x04\xc8\x31\xf6\x1e\xf6\x5e\x52\x03\x5e\xc3\x8f\x63\xc4\x94\x4f\
+\xa3\x51\x90\x13\x90\xc2\x81\x95\xd9\xb3\xa0\x32\x67\x0e\x8c\x3e\
+\xfe\x38\x2c\xbe\xf8\x22\x84\x73\xaa\x75\x41\xae\x41\x02\xf8\xa0\
+\xee\xc9\xda\x04\x80\xe0\xdf\x0c\x77\x34\x86\xb1\x94\xb4\x22\x0a\
+\x7b\x50\xef\x4f\x5e\x4f\xe6\xf8\x4b\xe8\xec\xcb\x1b\xf0\xde\xba\
+\x93\x3b\xeb\x32\xc7\x88\xa3\x1c\x5f\xf4\x8f\x34\x01\xb0\xc7\xa7\
+\x9e\x9f\x08\x60\x8c\x92\x81\xea\xc2\x04\x10\xf3\x01\xca\x01\x2f\
+\x7b\x7f\xe3\x06\xe8\xa4\xfc\xf9\xb4\x53\x9c\x14\x60\x31\xf3\x37\
+\xd3\x00\x2a\xe4\x04\x24\x1f\x40\xb5\xca\x34\x01\x32\x09\xcc\xb0\
+\x3c\x80\x84\xe0\x8b\x2a\x13\x9c\x67\x31\x3f\x8d\xc2\x88\xd1\x2e\
+\x32\x69\x14\x86\x2d\xb4\x80\x09\x50\x9e\x3e\x0d\xc8\x80\xea\xdf\
+\x7e\x21\x18\xd8\x76\xcb\xae\xfa\x22\x0c\xff\xfd\x6f\x69\x5e\x09\
+\x4d\x21\xbe\x39\x92\xc0\x6a\x9d\x93\x93\x10\x00\xcd\xfe\x78\x51\
+\x9a\xc2\xd3\x8f\x39\x06\x66\x9d\x72\x0a\xd4\x16\x2d\x62\xde\xff\
+\xb0\x46\x8a\x02\x7c\x5e\xea\xbc\x5b\x77\x76\x67\x5d\x54\xf9\xc8\
+\x18\xb1\x66\xcf\x23\x35\x00\x02\xba\xcc\x01\x18\x1d\xe3\x26\x00\
+\x65\x05\xaa\x0b\x84\xc8\xaa\xf6\xb9\xe1\x46\xcd\xb7\x50\x8c\xfc\
+\xe9\x94\xb7\xb9\x93\x81\xc8\xb9\x00\x90\x00\x28\xfe\x4f\xc0\xe7\
+\x66\x40\x85\x69\x04\x2c\x19\x48\x6d\xa5\x0c\x4e\xd4\x58\xc0\x67\
+\xd4\x28\x0c\xad\xde\x5d\xf3\x9a\x81\xeb\x45\x68\x01\x9b\x6d\x2a\
+\xf2\x03\x86\xa0\xba\xc5\x02\x18\x7d\xf8\x5f\xb0\xe4\x32\xfd\xb9\
+\x1e\x7c\x58\x3c\x67\x97\x9a\xad\x95\x23\xae\x85\xe1\x3f\x95\x59\
+\x1e\xe7\x33\x78\xf2\x3c\xed\x3b\x92\x17\xc0\x17\xbf\x35\xcd\xf2\
+\x83\x3f\x02\x8a\x75\x86\x37\x64\xc2\x69\xb7\xfd\x12\xe3\xc5\xd5\
+\x02\x7c\x86\xd8\x7d\x20\x64\x94\xa6\xe7\x91\x80\x66\x87\x3c\x07\
+\xa0\xd9\xe0\x3e\x80\xd1\xb1\x51\x24\x00\x34\x03\x1a\x62\x4e\x00\
+\x5b\xde\x87\xcd\xfe\xdf\xf7\x5b\xe9\xc2\x46\x79\xc9\xbd\x6f\x3b\
+\xd9\xf3\x23\xa2\x63\xa6\x01\x30\x02\xa8\x42\x3f\x6a\x00\x8c\x00\
+\xc4\xd4\x60\x61\x26\x40\xd6\xf7\x92\x55\xa3\xd0\x03\x7c\x5c\x47\
+\x14\x77\x4d\x3b\xe4\xf2\xc1\xdf\x08\x01\xbf\x44\x61\x41\xfc\x5b\
+\xdf\x82\x2d\x99\x49\x40\x04\x30\xf6\x54\x74\x9a\x7f\x0c\x78\x9f\
+\xc5\x6d\x6b\x24\x81\x96\x36\x84\x16\x01\xfc\xb9\x6c\x86\x4c\xf4\
+\xa9\x27\xd3\x0e\x3f\x1c\x66\x9d\x7e\x3a\x8c\x61\xef\xaf\x2e\x95\
+\x94\x68\xda\x6d\xbf\x44\x79\x71\x49\x35\xf6\x74\x30\xe9\xd5\x79\
+\xed\x18\x71\xc6\x69\xac\x64\x79\xa7\x3d\x84\x06\x40\xe9\xbf\xa3\
+\xc2\x07\x30\x26\x22\x01\x4d\x67\x6a\x70\xdb\x29\xfd\x9a\x6f\x07\
+\x96\x5e\x68\xab\xfc\xf1\xe4\x93\x94\x1f\x91\xeb\x04\xa4\xb0\x5f\
+\x3f\x69\x00\x64\x02\x54\x5d\x0d\x20\x20\x49\xcd\xac\xcc\xe1\xbf\
+\x16\x4e\xd8\x56\xd7\x8c\xf3\x03\xe9\x00\x3e\xea\x19\x49\x6b\x9a\
+\x3b\x17\xf7\x68\x2a\x0d\xf4\x43\xdf\xd6\xdb\xc2\xfa\x7b\xff\x08\
+\x2f\x7d\xd5\xcd\xf4\x4c\x18\xb2\x3b\x01\x09\xe0\xe6\x56\x27\xe9\
+\x12\x80\xb6\xf3\xcf\x5b\xbb\x01\x5b\x5f\x7d\x35\xcb\x7b\x6e\xd0\
+\x24\x1f\x39\xc4\xde\x43\x5f\xa6\x91\xbf\x3a\xef\x3c\x42\x88\xb3\
+\x2e\xbc\x5e\x9d\xeb\x79\xcb\xfb\xe7\xac\x77\x4c\x00\x31\x13\x10\
+\x01\x7f\x64\x74\x94\x3b\x03\x1b\xdc\x09\x48\x8e\x5e\x59\x8c\xf6\
+\xfb\x7d\x37\x71\xf2\x57\xae\x72\xcf\x49\x6f\x76\x66\xe9\xe4\xa3\
+\x01\x0d\xb6\x16\x40\x9f\x08\x01\x32\x12\x40\x4d\x80\x8d\x08\x34\
+\xcc\xe8\xf9\x00\x0a\x0b\xff\xa9\x8e\x3b\x08\x3f\x27\xd5\x35\xd3\
+\x68\x27\xf1\xbf\xb7\xf2\xf4\xe9\x50\x9a\x3a\x95\x1d\x57\xe7\xcd\
+\x67\x6d\xb5\xe4\xc2\x4f\x42\x7d\xd9\xb2\x34\xaf\xe6\x0f\x48\x00\
+\x07\xb4\x3a\xa9\x25\x01\xfc\xb9\x85\xf3\x2f\xae\xe2\x49\xaf\x7a\
+\x15\x6c\x76\xce\x39\xbc\xf7\x8f\xb0\xfd\x03\x12\x97\x17\xa0\x03\
+\xf8\x0c\xea\x3c\xab\x5b\xa7\x77\xd7\xbc\xa6\xbf\x7c\xab\xa1\xb0\
+\xdc\x09\xc8\x4d\x00\x22\x80\xd1\xb1\x31\xb6\x8d\x60\xdb\xb1\x21\
+\xc1\x2c\x0c\x28\xcf\xe7\x13\x83\xec\xf7\xbd\xef\x27\x79\x2d\xb9\
+\xcb\x3d\x27\x9d\xe8\xb6\x9d\xf0\x02\xd2\xd8\x7f\xca\xff\x1f\x20\
+\x13\xa0\x0f\x37\x22\x80\x92\x62\x02\x14\x1a\xfe\xd3\x01\x7c\x36\
+\x8d\x42\x5f\x3b\xd1\xf1\x6b\xb8\x1f\x68\xf2\x54\xb2\xff\x29\x5c\
+\x42\x8e\xc1\xea\x82\x05\xb0\xe6\xf6\xdb\x61\xc5\xf7\x53\x6b\x79\
+\xaf\x42\x12\xf8\x7b\xdc\x09\x3a\x04\x10\x70\xfe\xe9\x56\x36\xf7\
+\xfc\xf3\xa1\x7f\x8b\x2d\xd8\xbc\xfe\x91\xd2\xa2\x77\x0f\x69\xb1\
+\xdc\xd4\x79\x59\x26\x7c\xde\x38\xcd\x9e\x47\x03\xf0\x76\x92\xf2\
+\xd2\x04\xa8\x35\x98\xf3\x6f\x58\x90\x40\x5d\x46\x01\x6c\x4b\x89\
+\x02\x00\xec\xff\xfd\x1f\xea\xbc\x9a\xc2\xe4\xee\x13\x8f\x77\x8e\
+\xe5\x7c\x80\x8e\x09\x80\x1a\xc0\x00\xcd\x87\xe7\x19\x11\x18\xd1\
+\x16\x29\x9c\x75\xea\x39\x46\xa0\x59\xf3\xd3\x28\xf4\xb4\x13\xcd\
+\xdf\x5b\xe4\x6f\x84\x7f\x5f\x99\x3d\x9b\xf9\x03\xa8\xa1\x88\x0c\
+\xec\xd1\x11\x58\xfc\x89\xf3\x83\xa1\xf3\x16\x22\xea\xbd\x7e\xe7\
+\x9a\xfd\x2e\x8d\xf3\xc2\xe5\xcf\xc2\xf9\x07\x11\xce\xbf\xb8\xc2\
+\xe5\xa1\x29\xb0\xd5\xd5\x57\xf1\x79\xfd\x35\x3c\xff\xd1\x8d\x0c\
+\x11\xe7\x80\xe6\x8f\x24\x78\x7e\x6a\xc0\xb7\x70\xf6\xa5\x5e\x91\
+\x46\x7c\xb4\xc5\xbd\x5b\x62\x2e\x00\xd6\xfb\xa3\x09\x30\x32\x56\
+\xe3\x03\x82\x2c\xb9\x52\xb0\x5b\xd7\x01\x3f\xfc\x11\x74\x52\xee\
+\x3a\xfe\x38\xe7\xb9\x0d\x31\x1a\xb0\x24\x26\x03\x19\x40\xfb\x7f\
+\x40\xf8\x00\xaa\xf8\x59\x4e\x17\x1e\xdb\xc6\xb1\xe1\x3f\xef\x3b\
+\x2a\x6a\x32\x8f\xa8\x8c\xcf\x24\xea\x7c\xd4\xbd\x18\x2d\xca\x98\
+\xfd\x7d\x5c\x0b\xa0\xb6\x9a\x88\x5a\xc0\xe6\xf3\x60\xc5\xff\x7e\
+\x1f\xd6\xdc\xd6\x7a\x12\xe0\x88\x25\xc5\x66\x21\x09\x34\x12\x94\
+\x71\xc5\xef\xfc\x4b\xe2\x84\x98\x71\xc2\x09\x30\xf5\xb0\xc3\x9c\
+\xde\xbf\xa5\xc3\x2f\x24\x3f\x24\x4d\xec\x3d\x32\x46\x9c\xb3\x3a\
+\x1f\x71\x2b\x9a\x8e\xac\xf0\xf3\xe9\xc8\x12\x6b\x02\x10\xe0\x47\
+\x51\x03\x18\x1d\xe5\x5a\x40\x5d\x4e\x0c\xea\x0b\x03\x1e\xf4\xa3\
+\x1f\x27\x78\x2b\xf9\xcb\x9d\xc7\x1e\xe3\xfe\x2e\xd8\x6c\x40\x42\
+\x03\x60\x26\x40\x85\x99\x00\x44\x02\x15\x96\x09\xc8\x97\x0b\x4b\
+\xda\xbb\xcb\xd3\x8a\x9c\xcc\xc3\x88\xab\xcb\xf7\x59\x0b\xf0\x91\
+\xe1\xca\xd6\x65\x68\x06\x61\xbe\xd4\xb8\x01\x7d\x5b\x6e\x09\xf5\
+\xa5\xcb\x50\x0b\x38\x2f\xd0\xf6\x9a\x78\x3c\x0c\x09\xe0\x8e\xa8\
+\x3f\xc6\xd6\x71\x5f\x5a\xe7\x1f\xca\x82\x2b\xf9\x64\x95\x16\xf6\
+\x60\xa1\xa0\x48\x09\x78\xed\x18\x71\xce\xea\x7c\xcb\xf2\xb1\x2a\
+\x64\xc4\x35\x42\x9e\x91\x6b\x00\x16\xd4\xea\x35\x96\x03\x30\x32\
+\x36\x0a\xc3\xa3\x6a\x14\x40\xf8\x01\x84\xca\x70\xd0\x8f\x6f\x4a\
+\xf3\x7a\x72\x93\xbb\x8e\x75\x7f\x1e\x72\x09\xf0\x12\xcb\x03\x10\
+\x1a\x00\x9a\x00\x03\x62\x48\x30\x27\x00\xb5\x29\xe2\xdb\x38\x7c\
+\x11\xd5\x0c\xce\x3a\xd0\x04\x7c\x4a\x75\x3e\xf4\x9e\x5b\x68\x04\
+\x61\xc7\xe5\x4d\x36\x81\xca\xcc\x59\x5c\x0b\xa0\x79\x03\xa6\x4d\
+\x87\x65\x97\x5d\x02\xb5\x67\x9e\x49\xf3\x8a\xae\x45\x02\x78\x5f\
+\xd4\x1f\x23\x09\x00\xc1\x4f\x2b\xfc\xd2\x78\xdd\x44\xce\x3f\x92\
+\x81\x05\x0b\x60\xee\xc7\x3f\xce\x9c\x7f\x1e\xc0\x17\xa0\xce\xb7\
+\x6e\xd8\xd6\x0d\x9e\x25\x76\x1f\x38\x29\xa6\xe7\xf1\x03\xde\x9f\
+\x47\xce\x35\x00\x77\x46\xe0\x11\xec\xf9\x87\x47\xd0\x04\xa8\x71\
+\x1f\x40\xa3\xd9\x54\x34\x00\x5e\xe6\x75\x37\xb5\x8c\xf4\x14\x2a\
+\x77\x1e\xf3\x46\x17\xb0\x42\xbd\x2f\x4b\x0d\xa0\xaf\x02\x83\x44\
+\x00\x55\x3e\x1e\xc0\x6b\x02\x24\x7c\xaf\x19\xc3\x7f\x86\x26\xf8\
+\xb2\x82\x37\x71\xf8\x2f\x70\x7d\x3e\x6d\x58\xff\xc2\x85\xfc\xf7\
+\x81\x8c\x59\x5d\xb0\x25\xac\xfd\xd5\xaf\x60\xe5\xff\x7a\xa7\x11\
+\xd7\x14\x0a\x21\x6c\x8a\x24\x60\x85\xfd\x31\x8e\x00\x4e\xc3\x5d\
+\xaa\x2c\x93\xd9\x67\x9e\x09\x83\x3b\xee\x08\xcd\xd5\x4a\x36\x62\
+\x4e\xea\x7c\x7c\xc3\xfa\x6e\xa4\x93\xea\x7c\x48\xcf\x15\xba\xba\
+\xac\xe7\x12\x62\x59\x70\x5a\x13\xb1\x56\x63\x3e\x80\x61\x61\x02\
+\x90\x46\xe0\x31\x01\x44\xc1\xd7\xdf\xfc\xd3\x44\xef\x26\x6f\xf9\
+\xed\x1b\x8f\x12\x47\x08\x6e\xa6\xa7\x1b\xcc\xe3\x4f\xb9\xff\x13\
+\xfa\xb8\x06\x40\xa1\xc0\x6a\x59\x35\x01\x22\xda\x38\xc7\xf0\x9f\
+\xa1\x09\xbe\x24\xea\x7c\xe8\xf5\x93\x96\x89\x78\xf7\xb2\xbc\xfc\
+\x8d\xf4\x6d\xb3\x0d\x8b\x04\x90\x50\x82\x10\xe5\xd0\x2c\x3e\xef\
+\x5c\x16\x25\x4a\x21\x07\x20\x01\x84\x4e\x19\x16\x47\x00\xe4\x5e\
+\x7e\x4b\x92\xab\x30\x27\x50\xa9\x04\x5b\x7e\xe9\x2a\x68\xac\x58\
+\x11\xba\x9c\x77\x52\x75\x3e\xbe\x61\x23\xce\xcf\x39\x76\x9f\x56\
+\x9d\x0f\x5f\x39\x26\xfa\x9a\xd2\x09\x48\xd3\x81\xd7\x84\x13\x70\
+\x78\x64\x04\x09\x40\x98\x00\x62\xc9\x30\xc7\x65\x82\x07\x07\xff\
+\xf4\xd6\x24\xaf\x28\x77\xf9\xcd\xd1\x47\x3a\xf7\x6f\xf0\x01\xff\
+\x50\x26\x0f\x36\xd9\xff\xd8\x93\x4d\x20\x02\xa0\x6c\x40\x8a\x02\
+\x18\xbe\xd5\x81\x72\xcc\xe6\xe3\xd7\x0f\x6f\xd7\xf0\xf3\xfd\x4f\
+\xa2\x03\xde\x88\xf3\x63\xcb\x44\x01\x3e\xfa\x37\x42\x9f\xcb\x9b\
+\xcc\x84\xca\xe6\x9b\xf3\x73\xc8\x84\xda\x6c\x33\x78\xe9\xca\xcf\
+\xb0\x81\x42\xba\xa2\xb4\xf4\x55\x3b\xd5\xec\x0f\xb5\x38\xc7\x15\
+\x04\x3f\xa9\xfd\x94\xb7\x3b\x35\xf6\x02\x21\xa5\x27\xed\xb5\x17\
+\xcc\x7c\xfb\xe9\x4a\xe8\xaf\x83\xea\x7c\x0a\x67\x5f\x5a\x75\xde\
+\x69\x93\xa8\x17\xae\x61\xf2\x30\x02\x10\x33\x02\x13\x01\x8c\x8c\
+\x72\x13\x40\x3a\x01\xeb\x44\xa8\xb6\xb2\x3c\x18\xca\x21\xb7\xfe\
+\x1f\x74\x52\x7e\x7d\xe4\xe1\xce\x31\xcb\xf3\x23\x02\x60\xb3\x01\
+\x91\x0f\xa0\x82\x04\xd0\xcf\x1c\x81\x55\x31\x35\x78\xd0\x09\x08\
+\x5a\xbf\x85\xa8\x04\xb0\x40\x99\x94\xce\xba\xd0\xcf\x39\xa8\xf3\
+\xfe\xf2\x46\x4c\x79\x8f\x06\x83\xa0\x1f\xd8\x65\x17\xf1\x90\x06\
+\x12\xc0\xe6\x30\x7c\xcf\xdd\xf0\xca\x77\xe2\x95\xf2\x88\x1e\xfd\
+\x05\x24\x00\xfd\x48\xde\x7d\x7c\xa1\xcf\x80\xca\x60\x68\xb8\x1d\
+\x67\xbf\xf7\x7d\xd0\x3f\x7f\x1e\x34\x87\x87\x7d\x0d\x13\xd1\xc8\
+\xed\x50\xe7\x35\xfd\x0d\x49\xd4\x79\xa7\x4d\x74\x00\xaf\x49\x70\
+\x96\x2d\x35\x80\xa6\x18\x08\x34\x06\xeb\xc8\x07\x30\x36\xea\x38\
+\x01\xe5\x80\x20\xe9\x52\x39\xb4\xd3\x04\x70\xc4\xe1\xce\xbd\xb0\
+\xf9\x00\xa9\x97\x47\x13\xa0\x4f\x38\x01\x27\xf6\xf7\x73\x27\x60\
+\x85\xe7\x01\x98\x91\x83\x81\xbc\x6d\x13\x09\xf8\xa2\xd4\xf9\x54\
+\x1a\x41\xbc\x3a\x0f\xa0\x0f\x78\xdf\x83\xb0\x5d\xdf\x76\xdb\xf1\
+\x9c\x00\x14\xbe\xb7\x61\xd1\xb9\x1f\x01\x68\xba\x29\xfe\x09\x22\
+\x73\x7b\x23\x09\xfc\xc5\xff\x65\x14\x01\x5c\x8e\xbb\x8f\x39\x8d\
+\x9e\x40\xb6\xf8\xcc\x67\xa0\xb9\x76\x5d\x78\x83\xc5\x36\x2c\x44\
+\x7c\x9f\xa7\x3a\xaf\xd7\xf3\xc4\x85\x99\xb4\x16\xb6\xd0\x20\xb8\
+\x60\xb3\xd8\xce\xb5\xa5\x09\x30\x3c\x3a\xca\x09\x00\xf7\x63\xcc\
+\x04\x10\x3e\x00\x99\x34\x80\xff\x1c\xf6\x7f\xb9\x2c\x12\x9b\x5a\
+\x7e\x75\xf8\x61\x0a\x01\xe0\x7f\x26\xcf\x04\xa4\x51\x80\x83\xc2\
+\x04\x20\x47\x60\x25\x60\x02\xc4\x00\x41\x03\xf0\x85\xa9\xf3\xad\
+\xc2\xd5\xa1\xa7\x45\xff\x46\x92\x00\xde\x7f\x5c\x9e\x39\x13\xfa\
+\x44\x4e\x00\x8d\x15\x20\x2d\x60\xf9\x17\x3f\xcf\x46\x0a\xa6\x90\
+\x2b\x90\x00\x3e\xe6\xff\x32\x14\xde\xf7\x57\x4c\x5a\xe9\x77\x97\
+\xa4\x57\xa8\x6e\xba\x29\x6c\x76\xf6\x7f\x43\x63\xe5\x4a\x8d\x86\
+\xd5\x68\xe4\xa2\xd4\xf9\xb8\x9e\x23\x32\xb4\x13\x5d\x3e\x31\xe0\
+\x23\x42\x51\xd2\x04\x60\x93\x81\xd4\x39\x01\xac\x1f\x1d\x65\x8e\
+\x40\x1a\x17\x20\x33\x01\xc1\x76\xd7\x07\x7c\xc3\x6d\x99\xd6\x9a\
+\xcf\x2c\xbf\x3c\xec\x10\x25\xbf\x9f\x47\x02\xd8\x64\x20\x08\xf8\
+\x41\xa6\x01\xf0\x28\x00\x9b\x14\x44\xa4\x02\xc7\x26\xf7\x18\x49\
+\x01\x5f\x90\x3a\x1f\xe7\xbb\x89\x02\xbc\x0e\xd8\x75\xaf\x89\x87\
+\x6c\xed\x80\xdd\x76\xe3\x9f\xa9\x5d\x67\xcd\x86\xe1\xfb\xff\x0c\
+\x2b\x6e\x48\x35\x09\xcc\x93\x48\x00\xdb\xf8\xbf\x0c\x10\x00\x82\
+\x9f\x3c\x0f\x2f\xa4\xb9\xc2\xb4\xa3\x8e\x82\x49\x7b\xef\x1d\x32\
+\xcf\x7f\x64\x4b\x76\x58\x9d\x8f\xe8\x79\x20\x06\xf0\x9a\xea\xbc\
+\x16\xe0\x7d\xcf\x68\x8b\x73\x89\x00\xd8\x40\x20\x61\x02\x0c\x3b\
+\x1a\x40\x93\x13\x80\xa8\x85\xca\x1f\x71\xfb\x2f\x93\xbd\xa4\x9c\
+\xe5\x17\x87\x1e\xa2\x3c\x9b\x4c\x04\xe2\x4e\xc0\x41\xb4\xfd\x27\
+\x92\x0f\xa0\xca\xe7\x05\x28\x9b\xa6\xaf\x77\x54\x00\x9f\x21\xf6\
+\x1e\x3c\xf6\xdf\x65\x16\x75\x3e\xf8\x8e\x13\x01\x5e\x53\xa3\x88\
+\x7a\xc6\xc1\xbd\xf6\x66\xfe\x00\x96\x13\x30\x61\x02\xd8\x63\x35\
+\x58\xf2\xf1\x8f\x41\x1a\xc1\xfb\xde\x75\xc7\x9a\xfd\xa0\xef\x3b\
+\xaf\x20\x01\xd0\x4a\x0f\xa9\xa6\x9a\xdd\xf4\xbf\x3e\x04\xa5\xc1\
+\x81\xe4\xbd\x7b\x5c\x63\x14\xa8\xce\x47\x55\x95\x97\x3a\xcf\x1b\
+\x58\xa3\x17\x93\xea\x3f\x28\x4e\x40\x04\x3c\x39\xff\xd6\x52\x14\
+\x60\x94\xfb\x00\x1a\x22\x0a\x00\xb6\x5b\xd7\x91\xbf\xfc\x95\xe6\
+\xdb\x29\x46\x6e\x3f\xf8\xf5\x62\x41\x10\xf1\xbc\x22\x11\x88\x9b\
+\x00\x15\xe6\x03\x18\x14\x73\x02\xd0\x68\xc0\x92\x1c\x11\xdc\xee\
+\xd8\x7b\x4c\x79\x5b\xa3\x7c\xfc\xcc\xc1\x3a\xf7\x98\x8e\xd4\xfa\
+\x69\x49\xf1\x19\x9b\xc8\xc6\x85\xf2\xf4\x19\xb0\xe4\xfc\xf3\xa0\
+\xb9\x7a\x25\xe8\x88\x0f\xe0\x17\x21\x01\x5c\x18\xf3\x77\x46\x00\
+\x14\x58\x3e\x46\xab\x76\xa5\x02\x0a\xff\xcd\xbb\xf8\x12\x36\xb5\
+\x71\x68\x23\xb7\x21\xf6\xce\x3f\x26\x04\xbc\x76\xf8\x4f\xc3\xd9\
+\x07\x31\xab\xcb\x6a\x92\xa2\x74\x02\x92\x06\x30\xcc\x34\x80\x11\
+\x66\x02\x8c\xd6\xdc\x30\xa0\x4a\x18\x9d\x26\x80\xdb\x0e\x7e\x1d\
+\xc8\x79\x00\xe8\xbe\xf8\xaa\x40\x25\xe6\x04\x1c\xac\xf6\xc1\x24\
+\x22\x80\xbe\x32\x9b\x1b\x90\x56\x0b\x32\x03\x26\x40\xeb\xf6\xce\
+\x33\xf6\x1e\xfc\xd8\x1a\xf0\x79\xa8\xf3\x11\x1f\x5a\x96\xaf\x6c\
+\xba\x19\xcb\x09\xe0\x37\x85\x5a\xc0\xd4\xa9\xb0\xf2\xc6\x1b\x60\
+\xf8\xaf\x01\x7f\x1e\x3f\x05\x62\xe5\x0f\x3b\xfa\x86\x08\x7b\xce\
+\x47\xf0\x53\x02\x32\xcd\xd7\x3d\x31\xaa\x86\xa8\x0b\x0c\xee\xb0\
+\x23\xcc\x38\xe9\x24\xb0\x84\xf7\xbf\x53\xb1\xf7\xf4\xd7\xd4\xfc\
+\xc1\x44\x00\x3e\xeb\x7c\x04\x3c\xbc\xc7\xeb\x61\xf3\x01\x92\x06\
+\x30\x4a\x04\x30\x0c\xeb\xc9\x07\x40\x89\x40\x4d\x4a\x03\xb6\x84\
+\xb9\xc0\xfd\x00\x47\xff\x3a\x32\xcd\xbb\x2d\xf2\xf3\xd7\x1f\xc4\
+\xf6\x72\x56\x40\x93\x85\x01\x85\x13\x90\x99\x00\x7d\x6c\xcf\x08\
+\x40\x86\x01\x5b\xb5\x79\xc1\xb1\xf7\xb0\x3f\x6a\x01\x5e\x33\xfc\
+\x97\x35\x7c\xa8\x8a\x31\x38\x01\xcd\x80\xbd\x1c\x6f\x3c\xad\x28\
+\x34\xfc\x97\xfb\x61\xe5\xf7\xbe\xab\xb4\xbb\xb6\x10\x38\x27\xef\
+\xa8\xcc\x14\xe4\x27\x00\x32\xe8\x02\xbf\x28\x9d\x8b\xcc\x78\xf3\
+\x49\xd0\xb7\xe5\x82\x60\xa6\x52\x16\x67\x9d\xef\xbc\xb6\xab\xf3\
+\x21\xa4\xd4\x12\xf0\x19\x34\x0a\xb6\x13\x26\x00\x27\x80\x51\x58\
+\xc3\x12\x81\xc6\xd8\xec\x40\x9e\x54\x60\x41\x18\x6f\xbc\xe3\xb7\
+\x1a\x6f\xa7\x38\xf9\xd9\x41\x07\x22\xa8\xe9\x4e\x0c\x67\x52\x50\
+\xca\x03\xe8\xab\x94\x61\x82\xa3\x01\x54\x99\x46\x20\x35\x00\x5d\
+\xf0\xd8\x10\x35\xed\xb6\xa6\xef\x25\x06\x7c\x91\x9a\x5a\xe4\x7d\
+\x15\xe3\x6f\x08\x5e\x32\x78\xde\xc4\xfd\x0f\x60\xe9\xc1\xac\x79\
+\x91\x58\xad\xe1\x11\x58\x76\xd1\xa7\x74\x5f\x91\x5f\x76\x47\x02\
+\xf8\x87\xfc\xe0\x27\x80\xcb\x70\x77\x5e\x9a\xf5\xc2\xc8\xfe\x67\
+\xce\x8a\xa8\x87\xcc\xe4\xac\x8b\x6b\xd8\x88\x6b\xe4\xa0\xce\x47\
+\x3f\x8b\xce\x35\xf5\x34\x0a\xff\x04\x24\xd2\x04\xa0\x49\x40\x38\
+\x01\x8c\xb2\x6c\xc0\x31\xe6\x03\xe0\x51\x00\xca\x17\x90\x03\x9e\
+\xde\xf4\x9b\x44\x0b\xc1\xe4\x2e\xb7\x1e\xb8\xbf\x6b\x06\x8a\xf9\
+\x00\x28\x15\x98\x11\x40\x1f\x11\x40\x1f\xf3\x01\x50\x3a\x30\xcf\
+\x03\x88\x68\x47\xd0\x04\x7c\x9c\xef\xa5\x05\xf8\xc2\x07\x17\x41\
+\x6c\x99\xe0\xb1\xc6\xf5\xe2\xca\x7b\x4e\xd1\x2b\x3f\xb0\xeb\x6e\
+\x6c\x80\x10\x7b\x06\x31\x40\x68\xc9\xc7\xce\x05\x6b\xfd\x3a\x48\
+\x21\xef\x43\x02\x70\x7c\x7c\x1e\xac\xff\xa5\x62\xfe\x02\x77\x87\
+\x27\xae\x12\x6f\x8a\x06\xff\x58\xf8\x63\x0d\x3e\x4c\x8e\xce\xba\
+\x98\xf2\x79\xa8\xf3\xe1\xf5\x42\x64\x5d\x69\x34\x8a\xb8\xd5\x85\
+\x5c\x13\xa0\xc1\x9c\x7e\xeb\x90\x00\xd6\x0e\xf3\x50\xe0\x98\x58\
+\x1d\x88\x8d\x15\x70\x2a\xb6\xe1\x98\xdf\xdd\xd5\xea\xed\x14\x2a\
+\x3f\x3d\x60\x3f\xef\x7c\x00\xb4\xda\x8d\x70\x02\x12\x01\x4c\x14\
+\x66\x00\x85\x05\xcb\x62\x81\x50\xfe\xe4\x2e\x28\xf3\x74\xd6\xa9\
+\x9f\x63\x01\x5f\x94\x3a\x9f\xb0\x77\x0f\xfb\xec\x77\x38\x56\xe6\
+\xcd\x67\x83\x83\xa8\x81\xe9\x2f\xb4\xb2\xf0\x2b\xdf\xf8\x3a\x8c\
+\xfe\xe3\x01\xdd\xd7\xe4\xb6\x35\xc0\x0d\x3b\xd4\xec\x77\xfa\xbe\
+\xe3\x82\x04\xb0\x14\x77\xb3\x12\xd5\x8a\x52\x9d\x3d\x1b\x66\x9e\
+\xf1\x4e\xb1\xbe\x79\x1b\x9c\x75\x81\xcf\xe9\xd4\xf9\x44\xd7\x2c\
+\x2a\xfc\xa7\x9c\xc7\x20\x2d\x96\x06\x27\xc0\xaf\xc7\x9e\x9f\x4c\
+\x80\xf5\xa3\xdc\x04\xa0\x09\x41\x68\xb4\xa0\x3a\x1a\xb0\xe3\x04\
+\xb0\xff\x6b\x9d\xa9\xc0\x58\xd2\xaa\x61\x38\xab\x02\x11\x01\x4c\
+\x46\x0d\x60\x82\x24\x00\x61\x02\x18\x9a\xe0\xd1\x55\xe7\xa3\xca\
+\x47\x56\x96\x42\x9d\x8f\x5e\x05\x48\xa3\x77\x0f\x3b\x2f\x06\xf0\
+\xfe\x3a\x68\x30\xd0\xe0\xde\xaf\xe6\xe7\xb2\x89\x42\x26\xc2\xba\
+\x3b\xef\x84\xd5\x37\xb7\x1e\x0a\x1e\xa2\xcd\x3f\x82\x04\xb0\x63\
+\xe0\xef\x08\xfe\xd9\xc0\x87\xff\x26\x12\xaa\x60\xe2\xab\xf6\x84\
+\xc9\x68\x0b\x82\x15\x05\xc4\x82\x9c\x75\x81\xeb\x40\xeb\x32\x39\
+\x6a\x14\x51\xe1\xbf\xf8\x78\x7f\xf4\x0f\x4e\xda\xf7\x9c\x00\x6a\
+\x5c\x03\x20\x02\x40\xcd\x8a\xa2\x00\x0d\xb9\x42\xb0\x52\xd7\xb1\
+\x77\xa5\x5a\xa9\x2d\x37\xb9\x65\xbf\xd7\x38\xc7\x72\xe1\x0f\xe9\
+\x03\xa0\x24\xa0\x49\x42\x0b\x20\x27\x20\x23\x80\x90\xf6\x0a\xb6\
+\x65\x72\x67\x5f\xe4\xf9\xb1\xe5\xc3\xaf\x19\x5c\xbe\x2d\x3f\x75\
+\x3e\xa8\x05\xb6\xae\x83\x16\xd4\x99\x74\xf0\x21\x20\x6f\xca\xa8\
+\xf6\x41\x7d\xe9\x12\x58\x7e\xe5\x15\x81\x3a\x34\xcc\x77\x52\x20\
+\xa7\x22\x09\xac\xf1\x9c\x8f\x04\x40\xaa\xff\x2f\x5a\x97\x0f\x5e\
+\x64\xca\xa1\x87\xb1\xe1\xbf\xa1\x0f\xd2\x81\xd8\x7b\xbb\x35\x8a\
+\xf8\xd5\x85\x74\xae\xaf\x68\x00\xc2\x04\xe0\x1a\x00\x77\x02\x12\
+\x11\x30\x02\xa0\xc5\x41\x6c\x77\x01\x11\x92\xe3\x7e\x7f\x37\x74\
+\x52\x6e\x7e\xed\xbe\x6e\x3b\x50\xef\x84\xfb\xb2\x98\x12\x9c\x80\
+\x2f\x35\x00\x46\x00\xa6\x9b\x0a\x9c\xb8\x77\x0f\xfb\xdc\xaa\x8c\
+\x66\xf8\x30\x7a\xc9\xaf\xec\xea\x3c\xf8\xc9\x24\xac\x7c\xec\x75\
+\xf9\xf1\xa4\x23\x8e\x02\xa3\x54\x96\x0d\xcd\xe6\x09\x58\xf2\x91\
+\x73\x92\xaf\xee\xcb\xe5\xe0\x1d\xc4\x2a\xc2\x2a\x01\xd0\x9c\x43\
+\x97\x85\x9d\xdd\xea\x22\x33\x4e\x3c\x91\xe5\x29\x07\x5a\xb9\xdd\
+\xce\xba\x54\xd7\x4c\xae\x51\x18\x3a\xbd\x7b\xdc\xf5\x23\x9e\x51\
+\xce\xf8\x4b\x3d\x3d\x01\x7e\xfd\xe8\x08\xac\x1e\x1e\x61\x3e\x80\
+\xd1\x7a\xcd\x49\x04\xe2\x51\x00\x6e\x47\x9f\x70\xf7\x3d\xd0\x49\
+\xf9\xc9\x6b\xf6\x51\x9c\x80\x5c\x0b\x60\x04\x50\xe6\xf3\x01\x4c\
+\x1e\xe8\xe7\x04\x50\x2a\xb3\xef\x39\x01\xb4\x5b\x9d\x0f\x27\x68\
+\x5d\xc0\x27\x51\xe7\xb5\x00\xaf\xfd\xfb\x77\x0f\x27\x1c\x78\x10\
+\x94\x86\x86\xdc\x70\xe0\xe0\x20\x2c\x46\x02\xb0\xb1\x83\x48\x21\
+\xe7\x23\x01\x5c\xee\xb9\x4f\x24\x80\xff\xc5\xdd\x49\x61\x37\xdf\
+\x4a\x66\x9d\xf9\x6e\x30\x27\x0c\x66\x03\x5f\x9e\xce\xba\x14\xd7\
+\x6c\xa5\x51\xb4\x5e\xb9\x28\xaa\x6c\xfc\x35\xfd\x6a\xa6\xc5\x08\
+\x80\x66\x04\xae\xc3\x5a\x61\x02\xd0\x36\x2a\x86\x03\xf3\xd1\x80\
+\x9c\x84\x68\x7f\xe2\x3d\x7f\xd4\x7a\x47\x45\xc9\x4d\xfb\xee\xcd\
+\xac\x7f\xee\xc1\xe7\x3d\x3c\xad\x0b\xd8\x2f\xa2\x00\x93\x07\xfa\
+\x98\x13\xb0\xaf\x2c\x08\x40\xeb\x1d\x69\x9e\x93\x40\x9d\x97\xe7\
+\x47\xaf\x4e\x0c\x91\x65\x74\xee\x27\x1d\xe0\xf5\x7f\xbf\xe4\x03\
+\xa0\x39\x01\xe4\x95\xcc\xc1\x01\x58\xfe\xd9\x2b\xa1\xf6\xdc\xb3\
+\x90\x42\x6e\x45\x02\x38\xd6\x73\xdf\x7f\xad\x98\x34\xd3\xc0\x36\
+\x49\x6b\xa2\xd0\xdf\xec\xf7\xbf\x1f\xa0\xa9\xc4\xff\xb3\x80\xaf\
+\x4b\xc2\x7f\xe9\x01\xdf\x9a\xd4\x8c\x98\x32\x6c\x51\x10\x04\x39\
+\xd3\x00\x10\xf8\xab\x89\x00\x28\x0a\x20\x7c\x00\x4d\xe9\x03\x10\
+\xe5\x3b\x4e\x00\xfb\xec\xed\xb6\x16\xad\x0a\x24\xc2\x80\xcc\x04\
+\xa8\x56\x61\xa8\x1f\x35\x00\x34\x03\xfa\x4b\xae\x13\x30\xac\x6d\
+\xc2\xdb\x38\x9d\xb3\x4e\x2d\x13\x6a\xc7\x07\x2e\x99\x0d\xf0\x49\
+\xd5\xf9\xc0\x33\x68\x5c\xbf\x1f\x4d\xec\xbe\x85\xdb\x83\xcc\xbb\
+\xa6\xbc\x80\x55\xdf\xfd\x0e\x0c\xdf\x7f\x5f\xcb\x77\xa4\x8a\xb8\
+\xef\xa5\xdb\xd7\xec\x39\xce\x67\x04\x3f\x65\xfe\xad\x81\xe4\x9d\
+\x3f\x54\x91\x95\xa6\x1f\x77\x7c\x44\x23\x67\x50\xe7\xdb\x1c\xfe\
+\x0b\x4e\xe6\x98\x4d\x9d\x57\x4f\x31\x62\xda\xc5\x7f\x4d\xb6\x32\
+\xb0\x20\x80\x75\xc2\x07\xe0\xd7\x00\x1c\x9f\x01\xee\xde\xfc\xc7\
+\x7b\xa1\x93\xf2\xe3\x57\xbb\x59\x6a\xae\x09\x60\x32\x13\x60\x12\
+\x8b\x02\xf4\x33\x5f\x40\xbf\x74\x02\xfa\xc7\x97\x67\x50\xe7\xc3\
+\x80\xd7\x0a\xf0\x89\x16\x9b\xf5\x7d\x4e\xbe\x7e\x65\x42\xc0\xc7\
+\x90\x14\x85\x02\x07\xf7\xde\xdb\x1d\x9f\x8f\x5a\xd6\xf0\xef\xef\
+\x84\xd5\x3f\xbd\x05\xe2\x24\x06\xd0\xf3\x91\x04\x9e\x97\x04\xf0\
+\x5a\xdc\xa5\x32\x26\xc9\xf9\x37\x74\xd0\xeb\x20\x0b\xf8\x82\x0d\
+\x01\x91\x75\x15\xa7\x51\x64\x57\xe7\xa5\x18\x11\xf7\x18\xdf\x53\
+\x80\x27\x0a\x30\x52\xe3\x79\x00\xab\x47\x86\x85\x13\xb0\x26\x08\
+\xc0\x16\x27\x73\x1f\xc0\x49\xf7\xfe\x19\x3a\x29\x3f\xda\x7b\x4f\
+\xf7\xb9\x0d\x99\x0a\x6c\xc2\x00\x45\x01\xfa\x78\x26\x20\x39\x02\
+\xfb\xcb\x7c\x38\x70\xa4\x06\x90\x42\x9d\x67\xd7\x0c\xab\xcb\x53\
+\x6d\xf2\xde\xdd\x79\x9e\x56\x75\xa4\x54\xe7\xe3\xae\x19\x75\x4e\
+\x69\xfa\x0c\x98\x78\x88\x8c\x04\xf0\x3b\x1b\x7b\xec\x51\x58\x71\
+\xed\xd7\x42\xef\x59\x43\xde\x8c\x04\x70\x93\x24\x00\xd4\xe1\xe1\
+\xcb\xba\x25\xd5\x8b\x4c\xdc\x73\x4f\x98\x48\xcc\x14\xda\x26\x79\
+\xaa\xf3\xc9\x9d\x75\xf1\x1a\x45\x3e\xea\x3c\x6b\x0f\x1d\xc0\x6b\
+\xf8\x2b\x98\x6f\xcf\x26\x0d\x40\x10\x80\x34\x01\x46\xb8\x13\xb0\
+\x2e\x52\x81\x49\x0b\x90\xae\xb4\xb7\xfc\x29\x99\x0a\x98\xb7\xfc\
+\x70\xaf\x57\x79\x46\x02\x4a\x02\x20\x13\x60\x72\x1f\x37\x01\x26\
+\x32\x13\xa0\xe4\x3a\x01\x53\xaa\xf3\xe1\xed\x0d\xe1\xe7\xfb\xdb\
+\x3c\xf0\x0e\xc2\x01\xaf\xaf\x21\xa4\x57\xe7\x43\x6e\xbc\xf5\x31\
+\xb5\xe5\xf1\x27\xba\x1a\x00\xee\x9b\x2f\xbd\x04\x2f\x5d\x76\x69\
+\xda\x48\xc0\xb9\x48\x00\x57\x4a\x02\xf8\x06\xee\x22\x97\x10\x8a\
+\xbb\xc0\xa4\x7d\xf7\x85\xc1\xdd\x76\x6f\xd1\x40\xf9\xa9\xf3\xe9\
+\xc2\x7f\x9a\xec\x9c\x40\xa3\x30\x42\xca\x64\xf2\x17\xd0\x27\x91\
+\xe4\x43\x04\x40\x3d\x3e\xd9\xfe\xab\x86\x47\x18\x11\x8c\xd4\x5d\
+\x1f\x00\x38\xa1\x40\x80\x93\xef\xbb\x1f\x3a\x29\x3f\xd8\x73\x0f\
+\x4f\x2a\xb0\x69\xf2\x4c\x40\x72\x02\x4a\x13\x60\xb2\x6a\x02\x78\
+\xad\xe7\xf0\xb6\xf3\xb5\x93\x16\xe0\x13\x82\xbd\xe5\x79\xb1\x9d\
+\x4f\xd2\xf2\xc9\xaf\x69\xfb\x09\x06\xdb\x74\xca\x5b\xdf\xc6\xdb\
+\x43\xcc\x13\x68\x63\xa7\xb0\xf4\x23\xe7\x40\x4a\xf9\x32\x12\xc0\
+\x07\x24\x01\x90\x1e\xf9\x6a\xa7\xc1\x13\xd4\x32\xf9\xc0\x03\x61\
+\x60\xfb\x1d\x00\xda\xae\xce\x6b\xbe\xf0\x14\xe5\xa3\x01\xaf\xaf\
+\xce\x27\x79\x46\xd5\xa6\xb7\x45\x14\x80\x7c\x00\xab\x69\x2c\xc0\
+\xf0\x30\x9f\x14\x84\xc2\x80\x0d\x3e\x1a\xd0\xb6\xdd\x1f\xc8\xc9\
+\x7f\xee\x2c\x01\xfc\x10\x09\x40\x8a\xcc\x03\xa0\x75\x01\x07\x04\
+\x01\x90\x09\x30\x44\x33\x03\x33\x02\x50\xf3\x00\xa2\x41\x90\x34\
+\x51\x26\xac\x8e\x88\x0a\x34\xcb\xa4\xf8\xfd\x66\x8c\x62\xd8\x31\
+\xd7\x94\xed\x31\xe5\x94\x53\xd1\x16\x90\xcb\x74\xb0\x75\xd8\x61\
+\xc9\x7f\x7d\xc0\x33\x47\x60\x02\xf9\x29\x12\xc0\x71\x92\x00\x9e\
+\x33\x22\xd6\xff\x6b\x25\x43\x87\x1c\x0a\xfd\x5b\x6d\xd9\xba\xc1\
+\x8a\x52\xe7\x73\x74\xd6\xa9\x5f\xa4\xcd\xe6\x4b\x02\xf8\xe0\xbd\
+\x78\x7d\x00\x34\x10\x48\x9a\x00\x23\xf5\x31\x36\x18\x48\x6a\x00\
+\x96\xb8\xc7\xb7\xde\x17\x3e\x2e\xbc\x5d\xf2\x83\x3d\x76\x03\x5b\
+\x00\x9f\x84\x26\xfd\xa0\x9e\x5e\xfa\x00\x86\xa4\x0f\x40\x8d\x02\
+\x68\x00\x3e\xab\x3a\x1f\x7b\x4e\x46\x67\x5d\x20\x53\x30\xe1\x35\
+\xed\x16\x1a\x45\x54\x06\xe2\x10\x6a\x00\x46\x5f\x3f\xbf\x30\x45\
+\x02\xcc\x12\x2c\xfd\xe8\x87\xc1\x5a\xbf\x5e\xe3\x4d\xb9\x22\x38\
+\xf8\xaf\x0b\xc7\xec\xbd\xd8\xe1\xdf\x2a\x26\x8d\xe2\xe9\x4b\x54\
+\x8b\x90\x29\x47\x1c\x01\x7d\x73\xe7\xb5\x68\xe4\xee\x56\xe7\x59\
+\xa3\xe4\x94\xcd\x97\xe5\x59\x6c\x71\xdd\x9a\xd0\x00\xd6\x90\x13\
+\x70\x98\x47\x01\x46\xc8\x07\xd0\x70\x57\x07\x96\xe7\x9e\xf2\x97\
+\xbf\x41\x27\xe5\x7b\xbb\xef\x26\x16\x05\xe5\x1a\x80\x1c\x0b\x30\
+\xe8\x98\x00\xb8\xf5\x71\x27\x60\x45\xc9\x03\xd0\x8e\x9b\xc7\xb5\
+\x19\x80\xde\x79\x19\x9d\x75\x91\x80\xd7\xbc\x4f\x5b\x83\x60\xe2\
+\x93\x92\xf8\xf1\xe4\x93\xde\x02\xe6\xc4\x49\xee\x9d\xa0\xb9\xb5\
+\xfc\x93\x1f\x67\x6b\x70\xc4\x49\xc4\xc4\xbe\x4b\x91\x00\xe6\x18\
+\x08\x7e\x9a\x6f\x78\x15\xa4\x94\xa9\xc7\x1c\x03\xd5\x4d\x66\xfa\
+\x5b\x26\xa2\x21\xa3\x1a\xaf\xbd\xea\xbc\x2c\xd3\xd2\xcb\xab\x7b\
+\x4d\x7f\xf9\xc8\x25\xb0\xc3\x9f\x45\xfe\x40\x18\xa8\x69\x59\x30\
+\x47\x03\xe0\x4e\x40\xd7\x04\x70\x33\x01\x6d\x41\x18\xa7\xfe\xb5\
+\xb3\x04\xf0\xdd\xdd\x77\x65\x7b\x0e\x6c\x83\xad\xfd\xe7\x98\x00\
+\x55\x9e\x08\x44\x26\xc0\x00\x9a\x00\x15\x25\x0c\xd8\x55\xea\x7c\
+\xc0\xc1\xab\x01\xf8\xb8\xf2\x3a\x80\xd7\xd4\x48\xd4\x3f\x4d\x3e\
+\xfe\x04\x36\x30\xc8\x50\x09\xe0\xd2\x4b\xa0\xbe\x78\x91\xa7\xb4\
+\xe6\x4c\xde\x54\x73\x1f\x11\xc0\xb6\x78\xf0\x98\x56\x11\x79\x01\
+\xe5\x78\xda\x09\x27\x42\x79\xea\x94\x88\x86\x8c\xfc\xa0\xd5\x90\
+\x79\xa9\xf3\xb2\x4c\x11\xd9\x7c\xce\x71\x8b\x39\xeb\xc3\x00\x1f\
+\xbc\x7f\x0e\x6e\x46\x00\x63\xdc\x09\xb8\x9a\x91\x00\x1f\x0b\x40\
+\xdf\x37\x85\x06\x20\x8b\x9e\xf6\xb7\xbf\x43\x27\xe5\x3b\xbb\xee\
+\xe2\x3a\xa6\x81\xe7\x01\x70\x02\xe0\x51\x80\x29\x03\xfd\x48\x04\
+\xdc\x09\x58\xf1\x0f\x06\x0a\x6d\xa7\xce\xa8\xf3\x52\x22\x33\x05\
+\x93\xf6\xee\xbe\xf3\x0c\x0d\x82\x8a\xfd\xfd\xe2\xf1\xe4\x63\x8f\
+\x83\xf2\xac\x59\x9e\x48\xc0\xcb\x9f\xbd\x02\x6a\x4f\x3f\x9d\x78\
+\xfa\x7e\x21\x5b\x10\x01\xec\x8f\x07\xb1\x23\x4a\xe2\xea\x9e\x81\
+\x76\x09\xa5\x25\xa6\x0b\xff\x65\x03\x5f\x2b\x82\x29\x22\x9b\x4f\
+\x0b\xf0\x51\x60\x8f\x7d\x7e\x57\xad\xaf\x37\x9a\x8e\x13\x90\xf2\
+\x00\xd6\x32\x0d\x00\x09\x40\x0e\x06\x52\xce\x3d\xed\x6f\xc9\xc6\
+\x84\xe7\x2d\x2a\x01\x98\x4a\x18\x70\x10\x09\x60\x12\xf3\x01\x90\
+\x06\x10\x41\x00\x1d\x54\xe7\xa5\x24\x9e\x71\x48\x93\x60\xa2\x53\
+\x8e\x35\x7e\xbf\x11\xbf\xff\x49\x47\xbf\x89\x2f\x17\x26\x13\xaf\
+\x70\xff\xca\x35\x57\xc1\xd8\x23\x0f\x43\x4a\xd9\x9f\x08\xe0\x44\
+\x3c\x08\x2c\x32\xaf\x4b\x28\x9b\x9c\xfe\x0e\x16\xa2\x08\x6d\x95\
+\x5c\x3d\xe5\xed\xd7\x28\xf4\xcd\x14\xf5\x50\xf7\x5e\x82\xe5\x39\
+\xa8\xb9\x09\xc0\x08\x40\x98\x00\xab\xd9\x0a\xc1\x7c\x30\x50\xd3\
+\xf6\x0e\x06\x3a\xed\xef\xff\x80\x4e\xca\x77\x76\xdd\x99\xed\xa5\
+\x0f\xc0\x14\x13\x82\x70\x0d\xc0\x47\x00\xa6\x11\xcc\x04\x4c\xd2\
+\x4e\x19\xd4\x79\x29\x89\x66\x1c\x4a\x18\xfe\x8b\x4b\xf1\x0e\xfe\
+\x29\x19\xa9\x51\xb3\x4d\x3c\xfc\x48\xa8\x6e\xc9\x1d\xee\xb6\x68\
+\xc7\x55\xd7\x7d\x0d\x46\xfe\x9e\xda\x0c\x7c\x2b\x11\x00\x4b\x02\
+\x4a\xa5\x41\xe0\x8b\xde\xe4\xf4\xd3\x5b\x3f\x54\x8e\xce\xba\xb8\
+\x32\xb9\xa9\xf3\xb1\xe7\xf8\x6b\xd2\xb9\x97\x98\xb6\xf0\x45\x1a\
+\xb8\x13\x10\x09\x00\x4d\x80\xd5\xc2\x04\x58\x23\x08\xa0\xe6\xcc\
+\x09\x48\x83\xba\x6d\x56\xef\xdb\x1f\xf8\x27\x74\x52\xbe\xbd\xcb\
+\x4e\x6c\x6f\x88\x99\x81\x09\xe0\x55\x41\x00\x93\x44\x22\x90\x24\
+\x80\x6a\x44\x14\xa0\x65\xfb\xfb\x3f\x6a\xf6\xee\xfc\xbe\xf4\xdf\
+\x8d\x56\xef\xae\x7c\x8e\x03\xbc\xad\x51\x3e\x70\x21\x15\xf0\x21\
+\x11\x28\x46\x00\xdb\x6c\x23\xe6\x5e\x14\x04\xf0\xad\x1b\x61\xf8\
+\xde\x64\x49\xbc\x0a\xd6\x3f\x62\xfc\xbd\x62\x5e\x8c\x07\x9f\x4c\
+\x54\x83\x22\x4c\x03\x08\xa8\xc3\x1a\x0f\xeb\xfb\x5b\xe2\xde\xdd\
+\xff\x39\x63\x79\xe6\xbb\x0b\x59\xa4\x42\xab\x77\x0f\x94\x89\x02\
+\x7c\x7c\x79\xd7\x04\x68\x70\x27\x20\x12\xc0\x4a\x22\x80\xd1\x51\
+\xf6\x99\x4c\x83\xa6\x9a\x07\x80\xff\x9f\xfe\x8f\xce\x12\xc0\xb7\
+\x76\xe6\xf3\x40\x78\x34\x80\x52\x09\x06\xc5\x58\x80\xa9\x03\xfd\
+\x30\xb9\x5a\x71\x9d\x80\x51\x6d\x96\x22\xf6\x1e\x35\xdd\x57\xac\
+\x83\x2d\x85\x3a\x1f\x5a\xaf\xaf\x50\xe2\xde\x3d\xe4\x9a\x6e\x96\
+\x74\x78\xf9\x49\xc7\x1c\x0b\x55\x5a\x2a\x0c\x9c\xb0\x0b\xac\xfe\
+\xc1\xf7\x60\xfd\xef\xe2\x27\x86\x8d\xe9\xdc\xaf\x22\x02\xb8\x0e\
+\x0f\xde\x0d\x29\x65\xc6\xa9\xa7\xb2\x78\x64\xcb\x07\xee\x70\xf8\
+\x2f\xec\xfa\xa9\x01\xaf\x6b\x66\x24\x2c\x4f\x47\x94\xeb\x2f\x53\
+\x81\xa9\xf7\x5f\xc5\x08\x60\xcc\xd1\x00\xe4\xe2\xa0\xb6\x28\x70\
+\xfa\x3f\x1f\x84\x4e\xca\x8d\x3b\xed\xc0\xbd\xd2\xd4\xfb\x8b\x28\
+\x40\xd5\x89\x02\x54\x61\x8a\xd0\x00\x06\xc4\x84\x20\xa5\x90\xb5\
+\x01\xd3\xf6\xee\x52\x74\xa6\x18\x4b\xaa\xce\x07\xea\x4d\xe0\xac\
+\x0b\xbd\x48\xc8\xf5\x5a\x01\xde\x7f\xa1\xc9\x27\x9d\xec\x0e\x09\
+\x96\x04\xf0\xdd\x6f\xc3\xfa\xbb\xee\xf4\xb6\x07\x68\xcb\x4f\x88\
+\x00\x68\x81\xf9\x37\xe9\x97\xf1\x5e\x64\xfa\x5b\x4e\x76\xa6\x2c\
+\x4e\xeb\xac\x6b\xf9\xf0\x19\xcb\x7b\x00\x5f\x98\x3a\xaf\xef\xec\
+\x0b\x15\x01\x6a\x49\x00\xeb\xd1\x04\x60\x79\x00\x62\x44\xe0\xb0\
+\x18\x0d\xd8\xe4\x4b\x08\x73\x6d\x01\xff\x3b\xfd\x1f\x9d\x25\x80\
+\x6f\xed\xb4\xa3\xfb\x7b\x94\xf3\x01\x60\x4f\x4f\x1a\x00\xf9\x00\
+\xa6\xb0\x3c\x80\x0a\x7e\xe6\xa9\xc0\x8c\x00\xb2\x00\x3e\xce\xc1\
+\x96\x41\x9d\x0f\xd6\x9d\xde\x59\x17\x75\x4d\x6e\xfd\xe8\xfa\x3b\
+\x82\xdf\x4f\x39\xed\x74\x28\xcd\x9c\xe5\x80\x9f\x76\x2b\xbf\x71\
+\x1d\x0c\xff\xe9\xde\xb4\xe3\x01\xee\x25\x02\xf0\xa4\x01\x47\x49\
+\xd4\x05\x28\x0c\x68\xb2\xe5\xc0\x32\xa8\xf3\xb9\x3a\xeb\xbc\x1f\
+\x0d\xb0\xc2\x8b\x17\xa5\xce\xa7\x08\x6b\x39\x61\x3d\xdc\x2c\x96\
+\xed\xd7\x64\x80\x5f\xcd\xc6\x02\x8c\xb2\x70\xe0\x30\x0b\x03\x36\
+\x1c\x27\xa0\xf4\x03\x9c\xf1\xcf\x87\xa0\x93\x72\x03\x6a\x00\x6e\
+\x72\x0f\x5f\x17\xb0\x22\x12\x81\x18\x01\x50\x32\x10\x6a\x00\x14\
+\x15\x28\x1b\xaa\x06\x00\xc9\x01\x1f\xf7\xce\x93\x3a\x08\x55\x3b\
+\xde\x57\x28\x8b\xb3\xce\x53\x77\x1c\xe0\x53\x44\x40\xa6\xbe\xfb\
+\x3d\x60\x4e\x99\xc6\x35\x07\xd1\x8e\x2b\xae\xb9\x0a\x46\x1f\x48\
+\x1d\x0a\xfe\x0b\x11\xc0\x33\x78\xb0\x45\xd8\x5f\x75\x58\x65\x2a\
+\xda\x25\x34\x4b\x69\xeb\x46\x2a\xc6\x59\x67\xfb\x40\x69\x44\xbc\
+\x98\x76\xab\xf3\x01\x89\xfa\xb1\xdb\x6a\x7e\x82\x9c\x11\xa8\xc9\
+\x12\x7f\x48\xf5\x5f\x29\x9c\x80\x8c\x00\x2c\x31\x21\x88\x62\x02\
+\xbc\xe3\xc1\xce\x12\xc0\x8d\x3b\x6e\xcf\xf6\x86\xe8\x91\x4a\x8a\
+\x0f\x80\xe5\x01\xf4\xf1\x4c\xc0\xc1\x72\x89\x85\x07\x4b\xfe\xa5\
+\xc1\x22\xda\x29\x2e\x33\x4e\x37\xf6\x1e\x0d\xf8\xfc\xd5\x79\xe7\
+\x58\x27\x09\x4c\xeb\x7a\xc1\xeb\x4f\xfb\xc0\x87\xd8\x02\xa1\x82\
+\x01\xd8\xff\xaf\x5c\x71\x39\x8c\xfd\xfb\x11\xbd\x97\x15\x94\xbf\
+\x13\x01\x2c\xc6\x03\x77\x76\x90\x84\x32\xe5\xa8\xa3\xf9\x5c\x65\
+\x79\xaa\xf3\x2d\x9c\x75\x6a\x19\x07\xf0\x51\x60\x8f\xbd\x97\xe2\
+\xd4\x79\xf7\x30\x58\x97\xdb\xa3\x79\xaf\xc9\x34\x00\xe9\x04\x64\
+\x1a\x00\x12\xc0\xf0\x30\xf7\x01\x38\x26\x80\x25\x26\x5f\xb6\xd9\
+\xfe\x9d\x0f\xa5\x5a\x2b\x3e\x37\xb9\x7e\x87\xed\x99\xdd\x4f\x42\
+\x3e\x80\x92\x30\x01\x06\x04\x01\x4c\x25\x13\xa0\x52\x65\x3e\x01\
+\x49\x00\x61\xed\x94\x0a\xf0\x9a\xe0\x31\x0a\x50\xe7\x9d\x63\x43\
+\xa7\x2e\x8d\x6b\x86\x3c\xaf\xff\x77\x32\xfd\xc3\xe7\xa2\xb9\x5d\
+\xf1\x10\xc0\xf2\x0b\x3f\x09\xf5\x67\x9e\xd1\x7f\x61\xe0\xc1\xf9\
+\x3f\x8c\x07\x2a\x26\x2d\x05\xbe\x79\xa2\x1a\x14\x19\x3a\xfc\x70\
+\x28\x4f\x9d\x1a\xfd\xf0\x19\xd4\xf9\x60\x3b\x6a\x00\x5e\x97\x51\
+\x0b\x56\xe7\xfd\xdf\x1b\x21\xcf\xe2\x2f\xc3\x7d\x00\x16\x1f\x0c\
+\x44\x04\x30\x42\x51\x80\x51\x58\x33\x26\x4d\x00\x9e\x07\xe0\xac\
+\x10\x4c\x26\xc0\x83\x9d\x25\x80\x1b\x50\x03\xf0\x2c\x0d\xee\xe4\
+\x01\x94\x61\xa8\xca\x7d\x00\x43\x55\x41\x00\x8a\x09\x10\xe7\xa9\
+\x4f\xab\xce\xcb\x02\x51\x9d\x70\x16\x75\xde\x39\x4e\x02\x78\x4d\
+\xd3\xd2\xbb\x48\x4a\x74\xf9\x19\x9f\xf8\x94\x07\xfc\xa4\x75\x2d\
+\xfb\xe8\x39\xd0\x58\x12\x3f\x9b\x7f\x4c\xc7\xfe\x10\x11\xc0\x73\
+\x90\x72\x24\x20\xc9\xd0\x21\x87\x40\x79\xc6\x8c\x60\xe3\xe5\xa0\
+\xce\x3b\x0f\x10\xfb\x03\x49\xda\xbb\xeb\xdd\x4b\x5a\x75\xde\xbd\
+\xe7\xf0\xeb\xc5\x8d\x72\xb3\x05\xb8\xb9\x09\xc0\x35\x80\x55\x44\
+\x00\xa3\x41\x13\xc0\x12\xd5\x9e\xd1\x71\x0d\x60\xa1\xe3\x00\x34\
+\x0c\x3e\x1a\x50\x3a\x01\x87\xd8\x9c\x80\x48\x02\xb8\x57\x4d\x80\
+\xbc\xd4\x79\x59\xc8\x08\xfd\x3a\x5e\x9d\x0e\xbd\x66\xd8\x35\xd2\
+\x02\x5e\x83\xe0\x62\x01\xef\xbb\xdf\xd2\xb4\xe9\x30\xf5\xac\x0f\
+\x3a\xce\x3f\xb1\x0c\x13\x2c\x39\xeb\x3f\xc1\x5a\x15\x5c\x2a\x5c\
+\x53\x9b\x7f\x98\x08\xe0\x69\x3c\x58\xa0\x77\x7e\xf0\x02\x93\x0e\
+\x7a\x1d\x54\x28\x3f\x39\x07\x75\xde\xa9\x3b\xf2\x07\x92\xa3\xb3\
+\x2e\xb6\xc1\xa3\xee\x37\x46\xcd\xd4\x01\x7c\x8b\xeb\x49\x13\x80\
+\xe6\xff\x77\x09\x80\x87\x01\xd5\x28\x80\x5c\x19\x98\xea\x3e\xe3\
+\x5f\xa9\xd3\x40\x73\x11\x22\x00\xe9\x04\x34\x95\x4c\x40\x46\x00\
+\xc2\x07\x30\x54\xad\xb0\xcf\x7c\x4a\xb0\xd0\x66\x4a\x64\x0b\xb7\
+\x04\x7c\x6c\xa4\x28\x61\x99\x9c\xd4\x79\x12\x43\x97\xe0\x08\xf0\
+\x53\xa6\x42\x65\xfe\x7c\xdc\xb6\x80\xca\x16\x5b\x40\x69\xea\x34\
+\x4f\xef\x2f\x8f\x17\xbf\xeb\x74\xb0\xb1\x83\x48\x19\x05\x78\x94\
+\x08\xe0\x49\x3c\xd8\x4a\xe7\xec\xb0\x8b\x4c\xdc\xf7\x35\x50\x9d\
+\xab\x58\x10\x09\xd4\x79\xb7\x61\x34\x00\xdf\x05\xce\x3a\xf5\x12\
+\x71\x6b\xfc\x25\xba\xa6\xe7\xd9\x5d\x13\x60\xb8\xde\x80\xd5\x63\
+\x3c\x0a\x40\xfb\x91\x5a\x83\x0d\x13\xb6\x84\x96\x60\x8b\xa2\xef\
+\xec\x34\x01\x6c\xbf\x90\x99\x65\x86\xc8\x01\x70\x33\x01\xcb\x30\
+\x99\xf2\x00\xfa\xf8\x46\x3e\x81\xb2\xa1\xf8\x00\x34\x7b\x77\x29\
+\x06\xa4\x00\x7c\x2a\x8d\x20\x1f\x75\x5e\x56\xab\xb7\xba\x10\x02\
+\x7e\xfa\x74\x0e\xf6\x79\x08\xfa\x2d\x16\x80\x39\x34\xa4\x64\xfc\
+\x09\xd0\x2b\xb3\x2f\x37\x57\xae\x84\xda\x63\x8f\xc1\xca\x2f\x5f\
+\x15\xff\x7b\x8e\x97\xc7\x89\x00\x68\x24\xe0\xb6\x61\x7f\xd5\x61\
+\x95\x81\x9d\x76\x82\x81\xed\xb7\x0f\x36\x44\x0c\x28\xbd\x9d\x40\
+\x16\x75\x3e\x3f\x67\x1d\xff\x18\x7f\xcd\x64\xb3\x02\x25\x54\x13\
+\xc1\x67\x02\x50\x22\xd0\x18\x6a\x00\x94\x0b\x20\x9c\x80\x35\xb1\
+\x30\x88\x65\xcb\xe1\xc0\x36\x12\x40\x6a\x0f\x70\x2e\x72\xfd\xc2\
+\xed\x3c\xf3\x01\x48\x02\x60\x51\x00\x96\x08\x84\x5b\xa5\xc2\x09\
+\x20\xc4\x04\xc8\x17\xbc\xad\xdb\x99\xad\xf1\x67\xc4\xd4\xdd\xa2\
+\x7c\x2b\x7f\x45\x6b\xc0\xf3\xe3\xd2\x26\x33\x79\x0f\x2f\x01\x3f\
+\x61\xa2\x03\x72\xd6\x44\x4c\xd5\x77\x81\xdf\x5c\xb9\x02\x6a\x8f\
+\x3f\x0e\xb5\x47\x1e\x86\xb1\x87\x1e\x84\xc6\x4b\xcb\xf2\x78\x7d\
+\x4f\x11\x01\xfc\x1b\x0f\x16\xf2\xcb\x24\x97\x3e\x64\xad\x09\x7b\
+\xed\x19\x0b\x78\x59\x77\x37\xc6\xde\xc3\xcb\x83\x73\xcf\x99\x7a\
+\xf7\x24\xf7\x69\xbb\x51\x00\x9a\xf9\x87\x99\x00\x63\xc2\x04\x18\
+\x1b\x63\x1a\x81\x8c\x02\x48\xf0\x53\x14\xa0\xd3\x26\xc0\x0d\xa8\
+\x01\xa8\x51\x00\x36\x27\x20\xfe\x78\x07\xa5\x06\xc0\x36\x41\x00\
+\xfe\x3c\x80\xc4\xde\xfd\xe4\xea\xbc\x33\xb6\x3f\xa0\x42\xf8\x24\
+\xa1\x3a\xef\xfd\x8d\xc4\xdf\x33\x25\xca\x95\xe7\x6c\x8a\xdb\x1c\
+\xa8\x6c\x3e\x8f\x01\xdf\x18\x1c\x04\x4f\xcf\xee\x24\xf7\xf0\xe3\
+\xe6\x0a\x04\xfc\x13\x12\xf0\x0f\xe5\x05\x78\xef\x3d\x03\x3c\x63\
+\xfc\xa3\x62\x92\x17\x69\xc7\xb4\x95\x95\x51\x75\x99\x74\xd0\x41\
+\x81\x87\x8f\x05\x7c\x97\xa9\xf3\x6e\xc3\xe8\x6a\x04\x29\xd4\x44\
+\x8d\x3a\x5c\x13\xc0\x72\x13\x81\xb0\xf7\x27\x02\x58\x5f\x77\xa3\
+\x00\xea\x70\xe0\x33\x3a\xac\x01\xdc\xa0\x98\x00\x86\x58\x18\xc4\
+\xf1\x01\x20\xf0\x87\x04\x09\x0c\x94\x4d\x45\x03\x88\x69\xb7\x8c\
+\x1a\x81\x16\xe0\x35\x49\x39\x12\xf0\x31\xe5\x8d\x72\x09\x4a\xb3\
+\x66\x31\xc0\x57\x08\xf4\x9b\x6e\x06\x25\x72\x92\x0b\xa7\x5d\x00\
+\xf4\xa2\xdd\xdc\x1e\xfe\x11\xde\xc3\x2f\x7f\x29\xd7\xf7\x14\xd1\
+\xb9\x3f\x4f\x04\x40\xb9\xa4\x3b\xa7\xad\xd8\xa4\x5c\xef\x23\x8f\
+\x04\x48\xa3\x8e\x17\xa4\xce\xeb\x4e\xd8\xa9\xbf\xc6\x5f\xb6\x1f\
+\x52\x12\x92\x72\x35\x80\x1a\x6a\x00\x35\x6e\x02\x8c\xc9\x3c\x00\
+\x8b\x39\x01\x2d\x31\x10\x88\x7e\xa0\xef\xe8\x30\x01\xdc\x28\x4c\
+\x00\x12\x6f\x1e\x40\x89\x11\x00\x81\x7f\x88\xf2\x00\x64\x14\x20\
+\xb6\x5d\x93\xab\xf3\x52\x52\x01\x3e\x0a\xec\xbe\xeb\x47\x86\x2c\
+\x51\xdb\x29\x6f\xb2\x89\xd3\xbb\xd3\x9e\x52\x75\x0d\x36\x71\xa7\
+\xea\xac\x53\x6f\x12\xa5\x81\xda\xdc\x8b\x2f\xb2\xf8\x7d\xfd\xa9\
+\x27\xa1\xf6\xe8\x23\x08\xf8\xe5\xb9\xbe\x17\x4d\x6d\xfe\x45\x22\
+\x00\x9a\x51\x62\xb7\xc4\x17\x50\x5c\x9a\x53\x8e\x7e\x23\xaa\x02\
+\xe5\x60\x03\x05\x1a\x3c\xa2\x85\xfd\x36\x5a\x54\xf9\x88\xef\xb5\
+\x27\xec\x04\x9d\x59\x81\xf2\x51\xe7\x5b\x96\x0f\x79\x06\xd7\x04\
+\x68\xc2\x48\xa3\xce\x09\x80\x26\x06\xc5\xfd\xa8\x34\x01\x6c\xdb\
+\x19\x0a\x4c\x26\xc0\x3b\x1e\xee\x0e\x02\x10\x7d\x1b\x4f\x05\x36\
+\x78\x22\x90\x4b\x00\x15\x4e\x00\x86\xc6\x60\xa0\x98\xb6\xf4\x00\
+\x1e\x52\xbc\x27\x9d\xf0\x63\xc4\xef\xcf\xc0\xe7\xa1\xe9\xb8\x4a\
+\x33\x67\x42\x79\x36\x07\x7b\x79\xf6\x6c\xb6\x34\x1e\x2b\xaf\x82\
+\x5d\xed\xe9\xf1\x6f\xcd\xe5\xcb\xa0\xf6\xec\xb3\x50\x7f\xfa\x29\
+\xa8\x93\x5a\xff\xec\x73\xf8\x65\x23\xd7\xf7\x90\x32\x0a\xb0\x84\
+\x08\x80\x66\x13\xd8\x43\xe7\x6c\xc3\x08\x6f\xf4\x49\xaf\x7b\x3d\
+\x94\x26\x4f\x6e\x09\xbe\xd0\x17\x61\xeb\xad\xdf\x96\x1c\xf0\x8a\
+\x29\x92\xb3\xb3\x2e\xab\x86\x60\x47\xf4\x7c\xdc\x04\xb0\xd9\xd2\
+\x60\xd4\xe3\x53\xf8\x6f\xd5\x18\x99\x00\x35\x37\x0c\xe8\x31\x01\
+\x00\x4e\xef\x30\x01\x7c\x8b\x08\x00\xdc\xdf\xbe\x6a\x02\x4c\xae\
+\x94\x39\x01\x90\x0f\xa0\x14\x61\x02\xb4\x70\x8a\xaa\x6d\x94\x68\
+\x16\x5e\x7f\x9b\xeb\x00\x9e\xce\xa7\x7b\x24\xa0\xa3\xda\x4e\xf9\
+\x2d\xe4\xac\x2b\xcd\xd8\x04\x4a\x94\xec\x26\x26\xbe\x31\xc2\x54\
+\x79\xd1\x00\xd6\xda\xb5\x50\x7f\x0e\xc1\xfe\xcc\xd3\x30\xf6\xc4\
+\x13\xac\x87\xb7\x87\x87\x73\x6f\xf7\x94\x80\xf7\xcb\xb3\x44\x00\
+\x34\xb1\xfc\x5e\xa1\x17\x31\x6c\xad\x5a\x26\xec\xb9\x17\xb3\x77\
+\x42\x5b\x38\xe2\xa5\xc6\x2e\xe9\x94\x42\x9d\x8f\x04\x7c\xbb\x7a\
+\xf7\x98\xf3\xfc\x3f\xe4\xb0\x43\x79\xdf\x4c\x03\x40\x02\xa0\x28\
+\xc0\x9a\x5a\xcd\xf1\x01\x30\x02\xc0\xef\x9b\x4a\x04\x80\xf6\x6f\
+\xef\xb0\x13\xf0\xdb\xdb\x6f\xc7\xed\x7f\x02\xa8\x93\x09\x68\xc0\
+\x60\x89\x86\x03\x57\x98\x03\x90\x69\x00\xa5\x78\x13\x20\xac\x8d\
+\x0a\x9d\x76\x9b\xee\x75\x68\x88\x83\x9b\x80\xce\x00\xbf\x09\x03\
+\xbf\x33\xf7\xbe\x2f\xeb\xce\x0f\x7a\x0a\xc5\x35\x96\x2c\x86\xc6\
+\xa2\x45\x50\x43\xc0\xd7\x1e\x7f\x02\xac\x15\x2f\x17\xd6\xd6\x39\
+\x81\x5e\x95\xfb\x88\x00\x68\x79\xd9\xd7\xf0\xe7\xd5\x03\xbc\x5f\
+\xfa\x17\x6e\x0f\xfd\xdb\x6c\xe3\x34\xb0\xdb\xd6\x8a\xca\x96\x6b\
+\xef\xee\xfb\xa2\xcd\xce\xba\x56\xe7\xe8\x00\x3e\xd0\xf3\x88\x3f\
+\xb3\xb1\x00\x2c\x15\xb8\x01\xab\x6b\x35\x66\xff\x93\x09\x30\xd2\
+\x70\x4d\x00\x50\x48\xe0\xb4\x0e\xfb\x00\xbe\xb3\xfd\x76\x8e\xfa\
+\xcf\x40\x85\x3b\x96\x0a\x4c\x26\x40\x45\x64\x03\xe2\x7e\x80\xd6\
+\x05\x10\x4e\xc2\xa8\x36\x2a\x62\xda\x6d\x63\x60\x00\x4a\x53\xa6\
+\x40\x79\x68\x0a\x8b\xad\x53\x42\x0d\x03\xfc\xf4\xe9\x4c\xad\xf7\
+\xaa\xed\x00\x61\x71\x77\x7b\x64\x18\x1a\x4b\x97\x42\x63\xf1\x22\
+\xa8\x2f\x42\xdb\xfd\xf9\x17\xa0\xfe\xc2\x73\xac\x67\x2f\x00\x94\
+\x8e\xb4\xa1\xee\x9f\x19\xff\xac\x1a\x37\xe1\xc1\x09\x69\x2b\xa2\
+\x91\x80\x95\x39\x73\x60\x60\xc7\x9d\x78\xe3\x89\x85\x2b\xd8\x6a\
+\x25\x61\xe0\x49\x18\x7b\x0f\x7c\x28\x50\x9d\x0f\xf8\x1f\x34\xea\
+\x88\x04\xbb\xff\x63\x84\xa9\x63\x7b\x7e\xc8\xe0\x68\x00\x04\x78\
+\xc7\x04\x40\x22\x20\x42\x60\x1a\x80\x25\x4d\x00\x5e\xee\xd4\x0e\
+\x13\xc0\x77\x91\x00\x48\x1c\x1f\x80\x1c\x0b\x80\xbd\xa8\x34\x01\
+\x26\x95\xcb\x42\x03\x90\x3e\x00\x0d\xc0\xeb\xc6\xde\x29\xf4\x38\
+\x61\x02\x03\xb9\x39\x19\x01\x4e\x40\x9f\x32\x04\x65\xfa\x3c\x65\
+\x2a\x18\x7d\x7c\xb9\x0b\xc3\xf0\xa9\xec\x10\x04\xbe\x8d\x6d\x4e\
+\xeb\xed\x51\x6e\x7d\x83\x80\xfe\x02\x07\x7a\xd3\x37\xef\x7e\x51\
+\xc0\x2c\x12\xf0\x11\xf5\x7f\x9d\x08\xe0\x4b\x78\xf0\x5f\xd9\x6a\
+\xc6\x97\x40\x8b\x3e\x0c\x0c\x42\x69\xf2\x24\x30\x27\x4d\x86\xd2\
+\xa4\x49\x8c\x1c\xe8\x3b\xa3\xbf\x9f\x27\x37\xd0\x9c\x76\x44\x10\
+\x14\xcb\x26\x82\xb0\xc2\xc7\xea\xe7\x19\xfe\x4b\x05\x78\x8d\x30\
+\x53\xf0\x3e\x83\xe7\xb7\x02\xbc\xff\x1a\x32\xb4\xc7\x34\x00\x22\
+\x00\x34\x03\xb8\x0f\x60\x0c\x3f\x37\xf9\xea\xc0\xb6\x1c\x07\xc0\
+\xeb\x39\xa5\xc3\x04\xf0\x3d\x41\x00\x24\x64\x21\xb3\x59\x81\x71\
+\x23\xa7\xdf\x64\x54\xfd\xa7\x50\x3e\x80\x30\x01\x28\x3f\xa0\xa4\
+\xe6\x02\x6b\xaa\xf3\xb4\x1a\x8e\x39\x71\x02\x4b\x96\x21\xb5\xdd\
+\x44\x90\x97\xd8\x36\xc4\x33\xe6\x4a\x65\xb7\xb1\x1d\xa0\xf3\x37\
+\x60\x38\xa0\x77\x4e\x00\x6b\xdd\x5a\x68\xbc\xf2\x0a\x58\xcb\x5f\
+\x62\xe1\x36\xea\xdd\xeb\xa8\xc6\x93\x3a\xef\xf9\x4d\x3a\x25\x8a\
+\x93\x0e\xd7\x7d\x09\x11\xc0\x87\xf1\xe0\xb3\x85\xdd\x3c\x25\x37\
+\xe0\x0f\xa0\x34\x88\x64\x30\x19\x5f\xa0\x24\x87\xc9\x93\xc1\x1c\
+\x1c\xe4\xb3\x09\xd1\x0b\x97\xa4\x10\x45\x12\x39\xa8\xf3\x49\x17\
+\x7c\x88\x72\xd6\xf9\x0f\x3d\x80\xd7\x01\x7b\xc4\x35\x19\x01\xd0\
+\xc2\x20\x42\x03\xe0\x26\x40\x0d\xd6\xd6\xb8\x09\x50\x6b\xba\x73\
+\x01\x58\x82\xbc\xde\xd6\x61\x02\xf8\xfe\xc2\xed\x3c\x4e\x70\x36\
+\x25\x98\xc1\xc3\x80\x93\x64\x2e\x00\x11\x00\x6a\x05\xce\xac\xc0\
+\xf2\xd9\x69\x6c\x00\xaa\xe8\xe6\xe0\x04\x30\x26\x4c\x60\x3d\x39\
+\x01\xdd\x40\xa0\xf3\x63\xde\x89\x18\x32\xc2\xa4\x84\xd4\x0c\x9f\
+\xf3\x0d\x0c\x6f\xcf\x6e\xad\x5b\x07\x4d\x04\x79\xf3\xe5\xe5\xd0\
+\x5c\xbe\x1c\x1a\xcb\x50\x85\x5f\xb6\x8c\xed\x29\x77\x3e\x4e\x36\
+\x60\xc0\xfb\xe5\x2c\x22\x80\x93\xf1\xe0\x07\x1d\xb9\x79\xfc\x01\
+\x30\x72\xc0\x97\x5c\x46\x42\xa0\x48\x82\x31\x69\x88\x13\xc4\x84\
+\x41\x30\x49\x7d\x23\xcf\x2b\xa9\xbd\x82\x14\x38\x41\x70\x62\xb0\
+\x1b\xb4\x6f\x3a\x44\x41\x2a\x9c\x03\x26\x4f\xc8\xc8\x27\x19\x9c\
+\x75\xfe\x32\x86\x66\xef\xae\x75\x7d\x10\x0b\x83\xe0\xb3\x8c\xd6\
+\x9b\x9c\x00\x70\x5b\x23\x7c\x00\x0d\xc7\x09\xe8\x12\xc6\x5b\x1f\
+\xea\xac\x13\xf0\x07\xdb\x2f\xe4\xed\x60\x28\x26\x00\x79\xfd\x69\
+\x36\x20\x04\xf1\x14\xd4\x08\xa7\x20\xe9\x4f\xc0\x77\x5b\xc5\xe3\
+\x32\x03\x35\x07\x3b\xcb\x86\x43\xb2\x08\xf4\xd2\x61\xb1\x73\x35\
+\x35\x96\xd4\x75\x6c\x17\x6b\xed\x1a\x68\xae\x59\x03\xd6\xca\x95\
+\x1c\xe4\x2f\x2d\xe3\x20\xc7\xbd\x3d\x32\xa2\xfd\x0c\x5d\x06\xca\
+\x36\xd5\xcd\x7e\x43\x6f\x26\x02\xd8\x0f\x0f\xfe\xd0\x75\x37\x4f\
+\x2f\x5c\x98\x15\xf4\x43\x29\xe1\x46\xa4\x40\xc7\xf4\x9d\xc9\xf6\
+\x03\x68\x5e\x0c\xf0\x49\x12\x64\x0e\x35\x23\x09\xcb\x25\x06\x49\
+\x12\x6c\x2e\x3d\xcb\xa3\x61\x58\x52\xc3\x90\xa4\x22\xc1\x18\x13\
+\xa6\xf2\xa6\x91\xa7\x4f\x3c\x09\x5c\xc8\x06\x8f\x09\x30\x8a\xf7\
+\xbd\xa6\x5e\x67\x51\x80\xb5\xb8\xe7\x4e\x40\x4b\x0c\x04\x72\x17\
+\x13\x3b\xf9\xa1\xce\x6a\x00\x37\x1d\x76\x08\x98\xd8\x43\x53\x2f\
+\x6d\x96\x91\xcc\x91\xd0\x2b\xa8\xd5\x4d\x18\x1c\x80\xc9\xa8\x9e\
+\x4f\x43\x5b\x7c\x68\x68\x32\x0c\xe2\xfb\x2a\xe3\xf7\xa5\x92\xe9\
+\xbc\xfd\x28\xe0\xdb\xb5\x31\x16\x4e\xb3\x08\xdc\xab\x57\x43\x73\
+\xd5\x2a\x36\xe4\x95\xbc\xee\x64\x8f\xd3\xde\x5a\xbf\x2e\xd3\x7d\
+\x6f\x40\x76\x7c\x02\x09\xfc\x5e\x0f\x20\x02\x98\x8f\x07\xcf\x16\
+\x7d\xf3\x85\xd5\xcd\x4c\x8c\xaa\x43\x10\xcc\x4e\x64\x44\x31\x81\
+\x93\x04\x99\x19\x44\x18\xfd\x7d\x42\x95\x34\xbc\xf6\xa2\xbc\x3b\
+\xe9\x9b\xb2\x5c\x82\xe0\xe4\x21\xc9\xa2\x21\xcc\x12\x4e\x2e\xdc\
+\xd9\x29\x1c\x52\xd2\xf1\xc9\x75\x78\x71\x2c\xb4\x16\x71\x2c\xbf\
+\x67\xa4\x61\xd9\xca\xf7\x52\xbb\xa1\xbf\x71\x15\xbf\x8e\xdb\x28\
+\x5e\x67\x2d\xd3\x00\xea\x8c\x08\x18\x01\xd8\x96\x53\x14\x44\x38\
+\xfd\x2d\x1d\x5e\x18\xe4\x96\x13\x8e\xe7\x6a\xbd\x58\x18\xd4\x44\
+\x80\x57\x91\x04\x08\xf0\x04\xfc\x69\x53\xa6\x32\x22\x18\xa8\x96\
+\xa1\x84\xed\x69\x8e\x21\xb8\x47\x86\xc1\x1a\x1e\x66\x5e\x74\xb2\
+\xc7\x09\xe0\x4d\x04\x38\xf5\xe4\xd6\x8a\x95\xec\xef\xb9\xff\x4c\
+\x0a\x6c\x83\xee\xad\x3b\x3e\xaa\x87\x75\x6f\x67\x3c\x58\x35\xa8\
+\xfb\x1c\xcb\xfb\x39\xba\x8e\x09\xc9\xdc\x40\xb5\x94\xd9\x9c\xb8\
+\x67\x8e\x49\xfa\x5c\xed\x63\x7e\x08\x3a\x36\xf0\xd8\xec\xab\xb2\
+\xcf\x26\xf9\x26\xd8\x1e\xcd\x10\xd4\x30\x98\xa3\x49\x26\x82\xb0\
+\x7f\x7c\x77\xe0\x57\x5b\x0d\xf5\xbc\x38\xd5\x56\xda\xb3\x7c\xae\
+\x3c\xb9\x2e\xc0\xf0\xd8\x28\xac\x59\x37\x0c\x2b\x51\xcd\x5d\xbb\
+\x6e\x3d\x0c\xa3\x26\x50\x6f\xd4\xc5\xb4\xe0\x82\x6b\x70\x3b\xee\
+\x67\xff\x57\x70\x4b\xc7\xcb\xcd\x87\x1d\xca\x52\x5b\xd9\xd6\xac\
+\x83\x89\xfb\x12\x6a\x30\x03\xb8\x4d\x40\xd2\x9c\x58\xc7\x6d\x74\
+\x18\xfa\xeb\x35\x20\xfa\xf5\xaf\x0d\xd8\x8b\x3d\x71\xf7\xd6\xdd\
+\x12\xf0\x7e\x99\xc2\xbe\x43\x12\xa0\x39\x85\x66\x6f\xb8\x0d\x93\
+\xbd\x6e\x96\xdf\x4d\x84\x40\x1e\x69\x46\x20\x7d\xcc\x3b\xcd\x3c\
+\xd4\x44\x1a\x4c\xc3\xa8\xf0\x24\x12\x72\x6e\xe1\x9e\xad\x97\x40\
+\x2a\x2f\x6d\x78\x6c\x18\x26\xfb\xbb\x61\xf2\xef\x0c\x71\x2e\xfb\
+\x1b\xee\x6d\x76\x6c\x72\x1f\x00\x82\x7d\xcd\xf0\x28\xac\x5a\xbf\
+\x1e\xf7\x48\x00\xd8\x73\xd6\x6b\x0d\x24\x00\xbe\x3a\x10\xd7\x1c\
+\x6c\x38\xf6\xb6\xdb\x0b\x6c\x99\xd6\xf2\x83\xd9\x9b\x80\xa2\x47\
+\x31\x80\xb3\x28\x00\xee\x27\xe2\x17\x93\x71\xa3\x05\xad\xfb\x71\
+\x4f\x04\x50\x4a\x7b\xa1\x9c\xde\x63\x37\xd5\x9b\xbd\xfe\xc4\x80\
+\x57\x65\x6c\xab\x31\xf6\x5a\x18\x01\xfc\x05\x77\x7b\x76\x53\xc3\
+\x6c\xac\x75\x53\xcc\x83\xb2\xc4\x47\xf1\xdd\x92\x95\xbb\x46\xec\
+\xc7\x6c\xfa\xde\x86\xa6\x38\x4f\xbe\xfa\xb7\x2c\xc9\x77\xd4\x58\
+\x52\xf9\xdf\x39\x33\x9d\xe7\x56\x09\xa0\x1f\x38\x01\x0c\xf9\x08\
+\xc0\x4c\x7d\xa5\xa0\x6c\x7c\x80\x27\x89\x06\x7d\xc2\x7a\x9f\x47\
+\x02\x98\x2f\x09\xe0\x16\xdc\x1d\xdb\xdb\x0d\xd3\x99\xfb\xce\xbb\
+\x6e\x49\x00\x35\x7c\xcf\x6b\x70\xbf\x4e\x10\xc0\x88\xcd\xbf\x67\
+\xf3\x00\x28\xe7\x9f\xd4\x61\x02\xf8\x91\x9f\x00\x28\x0f\x00\xf7\
+\x52\x03\x98\x44\x5a\x00\xd0\x42\xf4\xd9\x09\xa0\x97\xde\x63\x7e\
+\x75\x67\xea\xe5\xe3\xe4\x3e\x24\x80\x7d\x24\x01\x5c\x83\xbb\xb3\
+\xda\xd9\x28\x45\xd6\xdf\xbd\x2f\xb3\x75\xdd\x92\x00\xa8\xc7\x5f\
+\x8b\xfb\xb5\x82\x00\x46\x05\x01\x58\xca\xc0\x21\xda\x3a\x4d\x00\
+\x3f\x46\x02\x50\xbc\x1c\x7c\x4e\x40\xdc\x53\xfe\x9d\x6a\x02\xa4\
+\x21\x80\x5e\x7e\x8f\xe9\xa5\x30\xc0\xfb\xe5\x9b\x48\x00\x67\x4a\
+\x02\xf8\x28\xee\xae\xe8\xee\x86\xe9\x4c\xdd\xed\xbe\x67\x95\x00\
+\x08\xf8\x6b\x05\x11\x8c\x39\x04\xe0\x4d\x85\x3d\xb1\xc3\x04\xf0\
+\x13\xa1\x01\xb0\xe7\xa1\x28\x00\x70\xa0\x4b\x13\x80\x34\x00\x5a\
+\x36\x46\xd7\x04\xe8\xc5\xf7\x98\xad\xfe\xb6\x01\xde\x2f\x67\x21\
+\x01\x7c\x85\xd5\xff\x50\xd5\x38\x11\x77\x3f\xee\x9e\x46\xd9\x30\
+\xeb\xd6\xa9\xd7\xaf\x01\x90\x09\xb0\x1e\xb8\x09\xd0\x04\x49\x00\
+\xe0\xfc\x7b\x42\x87\x09\xe0\x66\x49\x00\x62\xba\x6a\x95\x00\x26\
+\x68\x10\xc0\xc6\x07\x78\x92\xdc\xec\xf8\x2c\xf7\xbc\xdf\x96\x63\
+\xf0\x47\x49\x00\xb4\x2e\xc0\x73\x9d\x6f\x98\xf6\xd7\xdb\x6d\x75\
+\x13\xc0\x09\xe8\xd2\x09\xb8\x4e\x71\x02\xd6\xc1\x55\xfd\x65\xe5\
+\xc7\x2d\x5a\x5a\xe0\xdd\xb7\x96\x9f\x6e\xe6\x06\x8f\xa4\x29\xa0\
+\x9a\x00\x2a\x01\x50\x04\xa0\x17\xa3\x00\xd9\xeb\xee\x4c\x2f\x1f\
+\x53\x2f\xdd\xd0\x64\x24\x80\x75\xce\x39\x0f\xa5\x0c\x05\x76\x6f\
+\xa3\xf7\xe6\x3d\xab\x04\x40\x3d\xff\x5a\xb1\x27\x02\x68\x1a\xfc\
+\x6f\xea\xcf\xe9\xd8\x2e\x20\x00\xf5\x99\x09\xe0\xd4\xd3\x13\x01\
+\x4c\x10\x3e\x00\x95\x00\x7a\x21\x0a\x90\xbd\xee\x8e\xa9\xf5\xba\
+\x75\x3f\x81\xe0\xdf\xd6\x73\x3e\x12\xc0\x4f\x71\x77\x4c\xb1\x0d\
+\xd3\x99\xba\x7b\xe9\x9e\x25\x01\x30\x1f\x00\x56\xbe\xde\xe6\x1b\
+\x0d\x5f\x71\x7d\x00\xae\x1c\xd3\x61\x02\xb8\x35\x82\x00\xa4\x09\
+\x30\x51\x71\x02\x66\x25\x80\xee\x55\xeb\xbb\x1e\xf0\x7e\xf9\x11\
+\x12\xc0\x5b\x3c\xe5\x91\x00\xce\xc3\xdd\x65\x3d\x70\xf3\x1d\xad\
+\xbb\xc8\x7b\xb6\xc5\x05\xa4\x0f\x80\x7a\xfe\x75\x82\x00\x28\x55\
+\xb3\x21\xce\x51\xc3\x80\x6f\xea\x30\x01\xfc\x4c\x21\x00\x99\xe5\
+\x2f\x09\x60\x50\x98\x00\x13\x20\x7d\x18\xb0\x7b\x7f\x23\x5d\xa7\
+\xd6\x27\x91\xf3\x91\x00\x2e\xf7\xd4\x87\x04\xf0\x7a\xdc\xfd\xb6\
+\x07\x6e\x7e\xc3\xaa\x5b\x1d\x1e\x2f\x36\x47\x03\x00\xa1\x01\x00\
+\x37\x09\xbc\x4e\x40\x2e\x6f\xec\x30\x01\xfc\xdc\xe7\x03\x20\x80\
+\x53\x4f\xdf\x2f\x80\x3f\x51\xec\x75\x4d\x80\xee\x7d\x8f\x3d\x0d\
+\x78\xbf\x1c\x81\x04\xf0\x4b\x4f\xfd\xff\xaa\x1a\x94\xaf\x41\xab\
+\x0c\xe6\x69\xa6\xf5\x5a\xc3\x14\x5f\x77\x0b\xcf\x4c\x14\x01\x8c\
+\x45\x10\xc0\x51\x2f\xc6\xaf\x0c\x5b\xb4\xdc\xbe\xf9\x1c\xf7\xd9\
+\xc0\x8d\x02\xf4\x09\xe0\x4f\x10\x3e\x80\x28\x13\xa0\x7b\xdf\x63\
+\xcf\xa9\xf5\xad\xeb\x75\x2b\x9e\xb3\x60\x14\x96\x06\xae\x85\x24\
+\x40\x83\xcb\x77\xe8\xca\x9b\x2f\xa8\xde\xa2\xeb\x66\xf5\x6b\x5e\
+\xc0\x4f\x00\xd2\x04\x18\x86\x68\x02\x38\xb2\xc3\x04\xf0\x8b\x10\
+\x02\x20\xa0\xf7\xf9\x34\x00\x49\x00\x45\x46\x01\xe4\x3d\xa4\x97\
+\xae\x08\xcf\xe5\x5b\x77\xb0\xf2\x65\x08\x7e\x47\x6d\xf3\x13\xc0\
+\xf5\xb8\x3b\xa3\x6b\x6e\xbe\xc7\xea\x75\xea\x4f\x79\x01\x95\x00\
+\x6a\x8a\x06\x30\x2c\x3e\x37\xc0\x1b\x06\xa4\xfd\x11\x1d\x26\x80\
+\x5f\xfa\x08\x40\x86\x01\xab\x8a\x06\x40\x4e\xc0\x4a\x01\x63\x01\
+\xe4\x35\xd3\xcb\x06\xa5\xd6\xf3\xba\x5b\x57\xfe\x4b\x24\x80\x23\
+\x42\xef\x05\x09\xe0\xbd\xb8\xfb\x5a\xc7\x6e\xbe\x57\xeb\xce\xa9\
+\x72\x3f\x01\x90\x06\x30\xac\x68\x00\x61\x04\x70\x78\x87\x09\xe0\
+\x57\x21\x04\x50\x05\x4e\x00\xb4\xfa\xdd\x44\xb1\xcf\x23\x0a\x20\
+\xaf\x91\x5e\x36\x68\xb5\x5e\x57\x2e\x47\x02\x38\x3f\xf4\xbe\x90\
+\x00\x68\x85\xa0\x07\xda\x76\xf3\x05\xd5\x5b\x78\xdd\x05\x55\x2e\
+\x7f\x9e\xa4\xe6\x8f\x85\x10\x80\xdf\x04\xa0\xfd\x61\x2f\x2c\x2e\
+\xf0\x49\x5b\xcb\x1d\x73\x37\xf5\x0c\x07\x56\x35\x00\x02\xfe\x04\
+\xa1\x09\x54\x33\x10\xc0\x46\x14\x9e\x6b\x5d\x6f\xf6\x8a\x0f\x45\
+\x02\xf8\x4d\xe8\x7d\x22\x01\xd0\x3b\x5a\x23\xde\x5d\x6f\x35\x4c\
+\x51\x37\x2c\xeb\x2f\xe8\x02\x6a\xb5\xf2\xe7\x2a\x35\x00\x02\xfe\
+\xfa\x16\x04\x70\x68\x87\x09\xe0\xb7\x73\xdd\x05\x61\x54\x27\xa0\
+\x6a\x02\x0c\x88\xcf\x65\xd0\x7b\x4f\xe3\x76\xbc\xaf\xee\xfc\x2a\
+\x27\xab\x72\x3a\x12\x40\x2d\xf2\xbe\x1f\xae\x1a\x77\xe3\x6e\xff\
+\x6e\x6f\x98\x0d\x01\xf0\x7e\x91\xea\x3d\x81\xbc\x2e\x92\x7f\xc8\
+\x09\x48\xd3\x5b\x8e\x89\xef\xfc\x04\xe0\xaf\x3b\x66\x95\x82\x4c\
+\xf7\x6c\xc4\xfc\x4d\x3d\xe6\xf3\x01\xf0\x4c\x40\x69\x02\x50\x4e\
+\x80\xd4\x00\x8c\x16\x75\x24\x97\x8d\xd2\x8e\x4f\x2b\xb7\x22\xf8\
+\x3d\xc3\xfe\xc3\x08\xe0\x42\xdc\x5d\xd0\x95\x0d\x53\x64\xdd\x05\
+\x56\xae\x5b\xb5\x6a\x02\x50\xde\xff\x98\xe8\xfd\x87\x05\x19\xa8\
+\x04\x60\xb7\xa8\xc3\x7f\x9c\xe5\xf1\x0c\x8d\xbf\x49\x92\x60\x99\
+\x80\x02\xf4\x03\x4a\x0e\x80\x9c\x0d\xc8\x80\xde\x04\x7c\x91\x75\
+\xb7\xf1\xb7\xf7\xde\x2d\x46\xe1\xba\xd8\x67\x42\x02\xd8\x05\x77\
+\xff\xec\x74\xa3\x14\x5e\x77\x17\x00\x3e\x4a\x48\xd5\x27\x87\x5f\
+\x5d\x19\x09\x48\x1a\x00\xe9\x6d\x96\x30\x05\x48\xc2\xa0\x90\x57\
+\xaf\xaf\xfb\x3c\x2a\x01\xb0\x10\xa0\xc1\x9d\x80\x4c\x03\x10\xea\
+\x3f\x39\x00\x2b\xe0\x66\x0a\x26\x93\x71\xc0\xe7\x78\xcf\x73\x91\
+\x00\x5e\x6c\x79\x3e\x92\xc0\x13\xb8\xdb\xba\xed\x0d\x53\x5c\xbb\
+\xf0\xfa\x3b\xa0\xd6\x27\x15\xd5\x0c\x20\x3f\x00\x81\x9e\xb4\x80\
+\x51\x25\x1d\xb8\x29\x30\x61\x45\x94\x6f\xd7\xf3\xa9\x29\xc0\x24\
+\x25\xd1\xd3\x13\x01\x0c\x48\xef\x3f\x78\xd5\x7f\xbd\xb6\x1a\x57\
+\xeb\x0b\xb8\xe7\x07\x11\xfc\xbb\x6a\x95\x47\x02\xf8\x0c\xee\xce\
+\x6d\x4b\xc3\x14\x59\x77\x17\xf7\xf2\xad\x44\x9d\x1a\x8c\xf9\x00\
+\x04\x01\xd4\xc5\xf7\x96\x32\x33\x70\xcc\x6a\x03\xb9\x3e\x6b\xd8\
+\x33\xb3\x5e\xdd\x70\x9d\x7f\x72\x28\xf0\x80\xe1\x82\xbf\x02\x2e\
+\xf8\xc3\xdb\x6d\x1c\xf0\x6d\xb8\xef\xcf\x20\x01\x9c\xa7\x55\xd7\
+\x23\x55\x83\x96\x0b\xbf\xbf\x8b\x6e\x5e\xaf\xee\xee\x6b\xf4\xc4\
+\xf5\xca\x65\xb6\x2c\x70\x7d\x01\x35\xe1\x03\x18\x53\xb5\x00\xf1\
+\x77\x3b\x86\x08\x8c\x88\xef\xe2\x44\xe7\x7c\x15\xcc\x2a\xf8\xe5\
+\x30\xe0\xaa\xf0\x01\xd0\x71\xc5\xf0\xaa\xfe\x46\xe8\x55\xda\xd3\
+\xd6\x45\xd6\xdd\x03\xbf\xbd\xfd\x91\x00\xee\xd1\xae\x1b\x49\xe0\
+\x79\xdc\xcd\xed\x92\x9b\x0f\xaf\xbb\xfb\x1b\x3d\x55\xdd\xaa\x33\
+\x50\x46\x04\xc8\x14\x20\x12\xa8\x09\x12\x90\x7e\x02\x95\x04\x92\
+\x48\x52\x62\xf0\x9f\xc3\x6c\x7e\xc3\x4d\xfd\x95\xaa\x7f\x9f\xec\
+\xf9\x81\x13\x01\x5b\x30\x54\x5c\x51\x27\x92\x90\xb7\xf4\xe2\xef\
+\xaf\x80\x6a\x69\x8c\xcf\x26\x48\x00\x4d\xff\x1f\xe2\x08\x20\xd5\
+\xaa\xc1\xe3\x76\x7c\xf6\xba\xfd\x5a\x00\x01\xbd\x21\x80\x2f\x49\
+\x80\x34\x83\xa6\x72\x0e\x5f\x56\xac\x3d\xf7\x2e\xd7\x01\x74\xbc\
+\xfe\xe0\x26\xff\xb0\x90\x1f\x3b\xb6\x9d\xc4\x9f\x30\x13\x62\x5c\
+\xad\x6f\xcf\x3d\x8b\xfa\xff\x77\xfe\x28\x9c\x9c\xe8\xda\x48\x00\
+\x94\x0b\x70\x77\xa7\x1f\xa0\x17\x7b\xf9\xbc\xea\x95\xce\x40\x19\
+\x11\x20\x2d\x80\x45\x03\x6c\xc5\x17\x00\xae\x33\xd0\x4a\x75\x95\
+\xe4\xcf\xa1\x9a\x00\x2e\x01\xd8\x1e\x02\xf0\xe7\xfe\xe7\x3d\x06\
+\xa0\xd5\x3d\xe6\x52\x77\x0f\xfe\xf6\x22\xea\x7e\x3b\x12\xc0\x77\
+\x12\xdd\xc7\x23\xb4\xc6\x33\x00\xa5\x99\xcd\x6a\xeb\xcd\xf7\x60\
+\xa3\xe7\x5d\xaf\x6a\x02\xd0\x71\x43\x05\x3f\x70\x32\x60\x5a\x01\
+\x78\x13\x83\x24\x01\x18\x11\xf5\xe5\x75\xef\xd2\x8e\x97\xea\xbd\
+\x63\x02\x28\xea\x3f\xed\xcb\xbe\xe4\x9f\xbc\xda\x69\x23\xb6\xe3\
+\xd3\xd4\x4d\x8a\xe2\x1c\x24\x80\xe5\x89\xcb\x22\x09\xd0\xc0\xa0\
+\xf7\x8e\xab\xf5\xed\xab\x5b\x3a\xee\xa2\x7a\xff\x7a\x88\xfa\x0f\
+\x10\x0f\xf2\xbc\x12\x82\x64\x4d\x81\xf0\x9f\xd8\xa4\x19\x20\x09\
+\xa0\xa2\xc4\xff\xb3\x5c\xbb\x17\x3b\x9c\x36\xa8\xf5\xba\xf2\x33\
+\x04\xff\x31\xa9\xea\xf9\x77\xd5\x38\x14\x77\xbf\xce\xfd\xe6\x7b\
+\xb0\xd1\xdb\x55\xb7\x9a\xe6\x4b\x20\xaf\x2b\xb6\x7f\x14\xf8\xd5\
+\x72\xf9\x4a\x78\xad\x6a\x38\x4f\x1e\x4b\x4d\xc0\xef\x0b\x90\xa6\
+\x40\x12\x2d\xa0\x17\x01\x5f\xf8\x7d\xa7\x2f\x7a\x34\x12\xc0\x6d\
+\xa9\xea\x45\x02\xa0\x77\xb7\x0c\xb7\x69\x99\x6e\xbe\x07\x1b\xbd\
+\x53\x2f\xd3\xdf\xfb\xcb\x10\xa0\xaa\xfa\x17\x07\x7e\xbd\xf0\x9c\
+\x11\xf2\xbd\xf4\x07\xd0\x26\x4d\x01\xfd\x3c\x80\x71\xb5\xbe\xa0\
+\xba\x29\x92\xb7\x00\x09\x20\xd2\x3d\xd4\xf2\x3a\x48\x02\xdf\xc4\
+\xdd\x3b\x13\xdd\xfc\xc6\xdd\xe8\xa9\xeb\x8d\xeb\xfd\xa5\xe3\x4f\
+\xc7\xe6\x57\xeb\xd2\xbb\x62\xb2\xfb\x36\x22\x3e\x4b\x9f\x00\x4b\
+\x06\xf2\x44\x04\xdc\x68\x40\xde\xfe\x80\xc0\xbd\x8d\xff\xf6\x54\
+\xb9\x00\xc1\x7f\x71\xa6\xeb\x22\x01\xec\x81\xbb\xbf\xb5\x7c\x80\
+\x71\xb5\x3e\x73\xbd\xfe\xf8\xff\x98\x4f\xfd\x0f\x9b\x10\x44\xa7\
+\x3e\x9d\x6f\x93\xdc\x77\x5c\x72\x90\x3a\x1c\x58\x4d\x06\x52\xb5\
+\x80\xbc\xa5\x17\x7f\x7b\x45\xd6\x2f\xea\xa5\x3e\x64\xfe\xbc\x51\
+\x58\x94\xf9\x1e\xfe\x1d\x32\x44\xb8\x17\x1b\xbd\xdb\xeb\x56\x09\
+\x40\xaa\xff\x44\x00\xd2\xf9\x27\x17\x05\x49\x46\x00\xf9\x66\xdd\
+\xc5\x0d\xe7\x95\x00\x57\x23\x02\x44\x02\x79\x13\xc0\x78\x2f\xaf\
+\x55\xf7\xcf\xe6\x8d\xe6\xb4\xce\x07\x12\xc0\xf1\xd8\xe8\x3f\xe9\
+\xb5\x86\xe9\xb5\x97\xa9\x02\x5b\x25\x80\xa8\xa1\xc0\xb4\xdf\xe5\
+\x99\x17\x0a\x7c\xca\xd6\xf2\xd0\x82\xcd\x43\x87\x04\x57\x14\x13\
+\x40\x5d\x13\x20\x55\xd8\x71\x1c\xf0\x69\xea\x3e\x7a\x5e\x8c\xf3\
+\x2f\xd1\x3d\x3e\xda\xc7\x66\x0a\x7a\x12\xb7\x2d\x36\x80\x86\xe9\
+\xaa\x7a\xd5\xfa\xa3\x34\x00\xb6\x2a\x50\xc4\x30\xe0\x9d\x3a\x4c\
+\x00\x0f\x2b\x04\x20\x9f\x43\x12\x80\xea\x08\x4c\x42\x00\xbd\x0a\
+\xf8\x22\xeb\x4f\x58\x2f\x73\xfe\xcd\x1b\x6d\x9d\x1b\xa6\x5d\x2f\
+\x92\xc0\x7f\xe3\xee\xf3\xdd\xd4\x28\x1b\x5a\xdd\x71\x26\x80\x97\
+\x00\x5c\x0a\xd8\xe1\xe9\xce\x12\xc0\xbf\xb7\x9c\x1b\x4a\x00\x72\
+\x56\x20\x5d\x0d\xa0\x17\x4d\xca\x22\xeb\xcf\x58\xef\x05\xf3\x5a\
+\x38\xff\x12\x5f\x07\x09\x60\x08\x77\x34\x99\xc0\xc4\x1e\x6e\x98\
+\xae\xae\x3b\x8c\x00\xf8\x20\x20\x9b\xcd\x01\x10\xa6\x01\x6c\xdf\
+\x61\x02\x78\x34\x84\x00\xd4\x69\xc1\xa2\x34\x80\x5e\xed\xe5\x7b\
+\xa0\x6e\x2d\xe7\x5f\xaa\x6b\x22\x09\x5c\x8d\xbb\x0f\xb4\xb3\x61\
+\x7a\xa0\xc1\x73\xab\xdb\x25\x00\xdb\xc9\x00\x94\xe9\xbf\x0d\xc5\
+\x09\xa8\xd6\xbd\x5d\x87\x09\xe0\x31\x0d\x02\x60\x4e\x40\x23\xbd\
+\x0f\xa0\x95\x74\xdb\x7b\xec\x70\xdd\x5a\xce\xbf\x54\xf7\x80\x04\
+\x40\xb3\x04\x3d\x06\x7c\x24\x68\xaf\x35\x4c\x97\xab\x83\x76\x40\
+\x03\x90\x29\xc0\x35\xf0\x4e\x02\xa2\xca\xb6\x1d\x26\x80\x27\xb6\
+\xf4\x8e\x18\x97\xc3\x84\xd9\x02\xa1\x86\x9b\x19\x98\xc5\x09\xe8\
+\x97\xee\x7e\x8f\x9d\xbb\x6f\x21\x07\x22\x01\x68\x0d\xe2\x4b\x75\
+\x4f\x8f\xf5\x19\x3f\xc3\xdd\x1b\xf3\xbc\xe3\x5e\x24\x93\xec\x75\
+\xdb\x91\xdf\xc8\x3c\x80\x9a\x42\x00\xd4\xfb\x37\x43\x08\x60\x9b\
+\x0e\x13\xc0\x93\x0a\x01\x48\xb5\x5e\x3a\x01\x19\xf8\xc1\xbb\x26\
+\x40\xfa\xc1\x47\xc5\x49\x8f\x03\x5e\x95\x3b\x10\xfc\x87\x15\x7a\
+\x8f\x48\x00\xce\x2a\xc2\x69\xa5\x7b\x41\x59\x64\xdd\xad\xe3\xf1\
+\xfe\x30\xa0\xd4\x00\xea\xe0\x66\x01\xaa\xd5\xd0\xe1\x56\x4f\x3d\
+\x5f\xe0\x13\xb7\x96\x67\xb6\x9e\x17\x78\x0e\x69\x02\xc8\xe9\xc1\
+\xd4\xf1\x00\x59\x92\x8d\xf2\x92\x5e\xad\x5b\x43\xf6\x41\x02\xb8\
+\xaf\xf0\xfb\x45\x12\xa0\x59\x83\x77\x29\xfc\x42\x1d\xac\x37\x7b\
+\xfd\xc9\x13\x70\x54\x0d\x40\xa6\x02\x4b\xf0\x3b\x04\xe0\x3b\x7f\
+\xcb\x27\x3b\x4b\x00\xcf\x86\x11\x80\x00\xbf\xb3\x69\x68\x00\xbd\
+\x0a\xca\x0e\x03\x5e\x95\x9f\x23\xf8\x13\x6b\xe6\x69\x09\xe0\x14\
+\xdc\x7d\x37\xf7\x8a\x8b\xba\xe1\xb6\xd5\x9f\x2d\xcd\xd6\x4f\x00\
+\x72\x1e\x00\x39\xf6\x3f\x2c\x0a\xb0\xa0\xc3\x04\xf0\x5c\x08\x01\
+\xc8\xd9\x81\xe5\x34\x61\xe5\x10\x02\xe8\xee\xf7\xd8\xfe\x7a\x33\
+\x0a\xfd\x24\x5e\x85\x04\xf0\x40\xd2\x82\x69\x09\x80\xca\xd1\xa4\
+\xa1\x7b\xb6\xa3\x61\xba\xb7\xee\x7c\xd3\x6c\xfd\x26\x40\x53\x31\
+\x01\xa4\x63\xd0\x2f\xf3\x3b\x4c\x00\xcf\xfb\x09\xc0\xf0\xce\x0f\
+\x40\x5b\x39\x67\x27\x60\x98\x74\xef\x6f\xa4\x2d\xf2\x13\x04\xff\
+\x89\x6d\x7d\xb6\xc7\xfb\xf4\xa7\x0c\x6b\xdb\x4d\x15\x5e\x77\xb1\
+\xb3\xd9\xfa\x47\x03\x36\x94\xa9\xbf\x9a\x10\xae\x01\xcc\xeb\x30\
+\x01\xbc\xb0\x8d\x4b\x00\xea\xf4\x5f\x6c\x58\x30\x88\xe9\xc2\x8c\
+\xbc\x56\x05\x72\xa5\x7b\x7f\x23\x6d\x17\xea\x1b\x76\x46\x02\x78\
+\xa4\xed\xcf\x8a\x24\x40\xe3\x03\x8e\xcf\xfa\x04\xdd\xab\x0e\xb6\
+\x77\xfa\x6a\x95\x00\x68\x63\xb6\xbf\xed\x9d\x06\x1c\xc0\xeb\x0b\
+\xd8\xfc\x89\xe7\x72\xbe\x8b\x64\xb2\x78\xdb\xf9\xce\xb1\x9f\x00\
+\xe4\x64\xa1\xe5\x1c\x08\x60\x1c\xf0\x91\xf2\x3d\x04\xff\xa9\x1d\
+\x79\x76\x24\x80\xad\x70\x47\xcc\x53\x6d\xdb\x45\x0b\xaf\xbf\x73\
+\xab\xcb\xaa\x04\xa0\xe6\x02\x48\x02\x08\x9b\xff\x7f\xd3\x0e\x13\
+\xc0\x52\x85\x00\xc2\xe6\x05\x60\xbe\x80\x94\x04\xd0\x8b\xe6\x42\
+\x9b\x85\x7e\x1a\x3b\x20\x01\x3c\xd1\xb1\xb6\x40\x12\xf8\x1c\xee\
+\xce\x29\xfc\x42\x85\xd5\xdd\x5d\xab\xd2\x48\x80\x4b\x95\xbf\x11\
+\x42\x00\xaa\xcc\xee\x30\x01\x2c\x8b\x21\x00\x55\xfd\xd7\x21\x80\
+\xee\xfd\x8d\x74\xad\x5c\x8f\xe0\x7f\x57\x96\x0a\xf2\x20\x80\x29\
+\xb8\x23\x06\x9a\x91\x6b\xc5\x85\xdd\x74\x77\xaf\x4a\xe3\x9f\x12\
+\x4c\xa6\x00\xab\xf3\xff\xab\x32\xab\xc3\x04\xf0\x92\x8f\x00\xd4\
+\x21\xc1\x72\x50\x90\xea\x00\xf4\xa7\x0d\x17\x25\x1b\x28\xe0\x55\
+\x21\xff\xf0\xb6\x48\x00\x99\x7e\x00\xb9\xb4\x13\x92\xc0\xfb\xb1\
+\xa2\x2f\x17\xf9\xb4\xbd\x62\xc7\x67\xad\x5b\x25\x00\xa9\x01\x48\
+\x6d\x40\xd5\x00\x64\xd6\xdd\x26\x8f\x77\x96\x00\x5e\x56\x08\x40\
+\x3e\xb3\x9f\x00\xd4\x10\x60\x2f\xae\x0f\xd0\xa5\x72\x19\x82\xff\
+\xe3\x59\x2b\xc9\xa5\xdd\x9e\xe8\x63\x93\x87\x3e\x84\xdb\xc2\xbc\
+\x9e\xae\x17\x7b\xf9\xbc\xea\x95\xbd\xbd\x4c\xff\x65\x1a\x80\x11\
+\x6e\x02\x4c\x7f\xec\xd9\x82\x9e\x46\x4f\x56\x6c\xb7\x45\xa0\x0d\
+\xd4\xa9\xc1\x54\x02\xc8\x13\xfc\x1b\x21\xe0\x55\xa1\xf1\x38\xbb\
+\x22\x01\x8c\x65\xad\x28\xb7\x76\x44\x12\x38\x0a\x77\x3f\xef\xcc\
+\x8d\xf4\x36\xe0\xfd\x4f\x62\x8b\xca\x9b\xd8\xcd\xab\x26\x80\xed\
+\x3b\x8f\x64\xda\xa3\xcf\x14\xf4\x74\x7a\xb2\x72\xe1\x02\xcf\x67\
+\xb5\xa7\x57\x35\x00\xc7\x3c\xc8\xb0\x7e\xd9\x46\x0e\x7a\x29\xd4\
+\x80\x07\xcc\x0b\x59\xe8\x33\x8d\xe4\xda\xa6\x48\x02\xb4\x86\xc0\
+\xa1\xc5\x5f\xb8\xb7\xd4\xfa\x60\x25\x46\xe4\x57\xaa\xa7\x9f\x08\
+\xa0\x19\x47\x00\xf8\xf7\xa9\x8f\x3c\x55\xe0\xd3\xb6\x96\xd5\x3b\
+\x6c\xe5\x6d\x17\xdb\xf6\x10\x40\x29\x2d\x01\xd8\xf6\x38\xe0\xc3\
+\xe5\xab\x08\xfe\xf7\xe7\x55\x59\xde\x04\xb0\x23\xf0\x19\x84\xfb\
+\xf2\xbf\x58\xe7\xc2\x73\xe9\x0a\x1a\x2d\xff\x16\x38\x05\xbf\xf0\
+\xa7\x03\x5b\x6c\x2e\x40\xc3\x25\x00\x85\x28\x48\x86\x1e\x7e\xb2\
+\xc0\xa7\x6f\x2d\x6b\x77\xda\x9a\xdf\xba\xf2\x7a\x0c\xf6\x9f\xf4\
+\x03\xb8\x8b\x84\xda\x61\x2b\x04\x2b\x84\x60\x44\x7c\x0f\x31\x65\
+\x36\x32\xa1\xa1\x9f\x3b\x22\x01\xac\xcd\xab\xc2\xdc\xb1\xf3\x84\
+\x32\x75\x58\x2f\xf6\xf2\x89\xeb\x8d\x02\xba\xff\x7b\xfc\xac\x7e\
+\x65\xa8\x57\x52\xfe\x60\x2b\x00\x6f\xe2\x39\x72\x2a\x70\xcb\xe0\
+\xf0\x91\xf9\x02\x72\x91\xae\xc9\x0f\x3e\x56\x50\x4b\xe8\xc9\xba\
+\x5d\xb7\x63\x78\x74\x3c\xfc\x78\x9f\x44\x06\xdc\xe6\x37\x1c\x02\
+\x70\xc6\x00\xa8\x80\xf7\xa8\x34\xee\x07\xdb\xff\xee\xed\x16\x9f\
+\x5b\x7d\xbf\xe1\x88\xd6\x44\x9f\x49\x24\x77\x1c\x3d\xc9\xc7\x09\
+\x90\x29\x70\x48\xb2\x92\x3d\xa0\xd6\x87\x81\x3d\x04\xe8\x72\x6f\
+\x86\x9d\xe3\x9b\x13\x8b\xcd\xf0\x43\x3d\xbf\xc1\x49\x81\x13\x80\
+\x21\x1c\x81\x06\x9b\x1d\x48\xe6\x05\x18\xb6\xe1\xa8\xfe\x06\x70\
+\x2d\x61\xe2\x03\x0f\x17\xd8\x32\xad\x65\xfd\x1e\x3b\xb9\xcf\x69\
+\xbb\x6b\x07\x1a\xb4\x3c\xb8\xcd\x49\x80\x8e\x9d\xf5\x01\x1d\xd5\
+\xde\x16\xff\x73\xc2\xb0\x3d\x2a\xbf\x1d\xf4\x78\xda\xea\x24\xe7\
+\xb6\xe7\x3b\xff\x39\x01\xd9\x30\x88\xe1\xfb\x08\xfe\x53\xf2\xae\
+\xb4\x10\x5c\x21\x09\x6c\x86\xbb\x07\x21\x76\x49\xb1\x2e\x07\x7c\
+\x2b\xb0\x2b\x40\x97\x3d\x99\x21\xe7\x49\x32\xd4\xbf\x8b\x7f\x94\
+\xf3\x9d\x72\x20\xa7\xf8\x62\xe8\x17\x0a\x32\xff\xbb\x45\xa4\x40\
+\xf6\x3f\x56\x6e\x29\xe7\x31\x5c\x18\x4e\xc5\x30\xf8\x97\x7f\x16\
+\xd8\x52\xad\x65\x64\xaf\xdd\xc4\x91\x7a\xf7\xbc\x41\x28\x0a\x50\
+\x02\x22\x42\x41\x0c\xb2\x9d\x1c\x30\xdb\xfc\x89\x6c\x7f\xaf\x2f\
+\x09\x40\x05\xba\x32\x1b\x8a\xed\x3b\x86\x18\x62\x08\x03\x7f\xef\
+\x11\xc2\xcb\xc0\x33\xfe\x96\x67\xae\xc9\x27\x85\xe1\x0c\x49\xe0\
+\x04\xdc\xdd\xe4\xfd\xb6\x8b\xed\x78\x4d\xc0\x3b\xaa\xbb\x61\xb8\
+\xb8\x56\xc1\x2d\xbe\x97\x7f\xf4\x9c\x0f\x1c\x26\x86\x42\x0e\x52\
+\x03\x70\xca\x80\xdb\xcb\xd3\x41\x43\xa8\xfe\x16\x80\x6b\x41\x2b\
+\xb7\xd5\xff\xa7\x96\x8b\x36\x15\x2a\x63\xaf\x7d\x95\x83\x27\x53\
+\x02\x5c\xd9\x2a\xca\x3b\x30\x6c\x2f\x40\x3d\x04\x20\x35\x00\x89\
+\x6d\xdb\x06\x43\x01\xb4\x03\x70\x95\x0c\x6c\xdb\x47\x12\xe0\x21\
+\x0e\x7b\xc3\x21\x84\x53\x11\xfc\xdf\x2b\xa2\xe2\x42\x1d\xad\x4f\
+\xf6\xc1\xf5\xb8\x3b\xa3\xdd\x17\xcf\x0c\x78\x05\xec\x0e\x40\x45\
+\xc5\xac\x97\x0f\x00\x5e\xc2\xdc\x61\x04\xaf\x06\xe0\x03\xb8\xa1\
+\x10\x84\xad\x16\x63\x26\x80\x50\xef\x99\x06\x60\xb3\xde\x5e\xce\
+\x05\x28\x27\x04\xb5\x95\xba\xab\x77\xdf\x5f\x50\x2b\xea\x49\xfd\
+\xc0\x7d\x3c\xaa\x3f\xa3\x29\x9b\xb7\x9c\x93\xfd\x67\x08\x27\xa0\
+\xad\x68\x08\x02\xa8\x74\x9e\xc4\x34\x27\x00\x9b\x03\xd7\xd1\x0a\
+\xb8\x89\x10\xec\xf5\x6d\xf7\x5c\x10\x04\x12\x49\x0e\x10\x4d\x0a\
+\xdd\x4f\x08\xbf\x40\xf0\x1f\x59\x54\xe5\x45\x13\x00\x4d\x21\x4e\
+\x3a\xea\x96\x45\x5f\x2c\x15\xe8\x7d\x3d\xbc\x07\xf0\x12\x63\xa6\
+\xe1\x01\x7a\xe0\x3c\x89\xde\x10\xa0\x7b\x35\x02\x70\xcb\x8b\x73\
+\x4c\xc3\x75\xe6\x81\x42\x00\xf2\x5c\x0b\x7f\x88\x96\x20\x09\xd6\
+\x3f\x3a\x5e\x33\xa7\x42\x28\xff\x2e\x97\x70\x70\x6a\xb1\x0e\xde\
+\x1f\x54\xf5\x5c\x92\x94\xf4\x03\x98\xb6\x9a\x00\x24\xc0\xec\x51\
+\xf1\x15\x5f\x80\xfc\x2a\x0e\xc4\x0e\xe8\x7d\xc4\x20\xeb\x52\xea\
+\x73\x48\xc1\xaf\x29\x28\x1a\x87\x0d\x36\xc4\x6a\x08\x9d\x25\x03\
+\x52\xfd\x77\x47\x02\x78\xb1\xa8\x0b\x14\x1e\x6a\x7d\xaa\x0f\xb0\
+\x8b\x60\x49\x0b\xa5\x8e\xdc\x78\x8c\x93\x8e\xd7\xe3\x05\xa9\x1f\
+\xf0\x2a\xc8\x6d\x50\x00\x2c\xcf\x89\x05\xba\xf2\x9d\x54\xfd\xfd\
+\xda\x83\x38\x76\x34\x00\x95\x0c\xc4\x4f\x9d\x65\x01\xda\x92\x00\
+\x0c\x8f\x0f\xa0\xf4\xab\xbb\x0a\x7a\x73\x7a\x62\x1f\xfe\x3a\xf0\
+\xd8\xe7\xa0\x9a\x00\x9c\x00\x0c\xe9\xec\x00\xf0\x10\x80\xad\x00\
+\x36\x8c\x14\xbc\x80\x07\x2f\x29\x84\x10\x83\x63\x22\xd8\x52\xab\
+\x90\x26\x86\xed\x00\xdd\x43\x0a\x00\x1e\x02\x69\xa9\x1d\xb4\x97\
+\x0c\xc8\xef\x7b\x38\x82\xff\x37\x45\x5e\xa4\x2d\xb9\x16\x48\x02\
+\x17\xe2\xee\x82\xb6\xde\x68\x84\xc3\xce\x71\xda\x49\xcf\x3b\x7d\
+\x36\xa3\x01\x6f\x78\x7a\x77\xd1\xfb\x2b\x80\x37\x0c\x3f\x01\xa8\
+\x75\x45\x7c\x86\x30\xc2\x70\xcf\x75\x22\x02\xa0\x26\x06\xb9\xcf\
+\x63\x19\xae\xe3\xd0\xf8\xf9\x1d\xed\x78\x85\xd1\xcd\x7c\xb4\x9c\
+\x84\xd6\xb5\xe1\xdd\xa7\xb4\x25\x67\x89\xef\x62\xc0\xae\x7a\xfe\
+\x95\x5e\x3e\x08\x70\xf0\x12\x83\x04\xba\x00\xb7\xff\x7c\x5b\x1c\
+\xcb\x48\x03\xd7\x1c\x5c\x42\x50\x37\xdb\x76\xb5\x0a\xaf\xcf\x01\
+\x20\x56\x4b\x28\x46\x3e\x86\xe0\xbf\xa2\xf0\xf7\xd7\x8e\x27\x41\
+\x02\xa0\xde\x9f\xb4\x80\x7d\xda\x72\x63\x21\x3d\xad\x04\xbd\x6a\
+\xc7\xdb\x02\xf0\x6e\xcf\x6c\x8a\xef\xc0\x43\x08\xb6\xef\xd8\x08\
+\x01\xbb\xda\xe3\x3b\x24\xe2\x31\x0f\xc0\xa3\x31\xa8\x75\xd8\x7e\
+\xbf\x80\xa1\x02\xde\xf6\xf4\xf8\x8a\xa5\xcd\xca\x58\x3f\xbd\xbd\
+\x1d\xaf\x30\x52\x4a\xc7\x1d\xe5\xe0\xd2\x01\xb8\xe1\x86\xfa\x0c\
+\xa1\xb9\x18\xc2\xc6\xe7\x7e\x00\x6f\xaf\x6b\xf8\xb5\x81\x40\x8f\
+\x0e\x21\xc4\xe0\x3b\x37\x84\x14\xdc\x8d\x3e\x5b\x0a\x19\x70\x0d\
+\x81\xfb\x0c\xdc\xef\x03\x26\x43\x2b\x32\x28\x8e\x08\x6e\x46\xf0\
+\x9f\xd0\x8e\xf7\xd7\x16\x02\x20\x41\x12\xa0\x9c\x51\x72\x59\x0f\
+\x15\x76\x33\x7e\xd0\x4b\xb0\x99\xe0\x01\x2a\x07\xa0\xe9\xe9\xe5\
+\x6d\xf6\x51\xb8\xad\xe4\x79\xec\xa3\x19\x0b\x78\x97\x3c\xfc\xbe\
+\x80\x20\xd0\x03\xe6\x82\xe7\x7e\xf9\x39\x32\x17\xc0\x21\x05\x9b\
+\x87\xfd\x6c\xc5\xf7\x00\xca\xdf\x9b\x3f\xba\xb5\x5d\xaf\x30\x54\
+\x4a\x6f\x39\x96\x3f\x82\xe8\xc5\x0d\x85\x0c\x9c\xde\xdf\xf6\xd9\
+\xfe\x51\x4e\x3e\x80\x00\xb0\x43\x89\x21\x00\x50\x3b\xe4\x6f\x61\
+\x5a\x82\x45\x3c\xe0\x6a\x0b\x4c\x43\xb0\xc0\xb0\xe4\xf5\xdd\x32\
+\xb6\x65\x45\x90\x41\x88\x4f\x21\x5f\x79\x14\xb7\xbd\xf3\xcc\xf6\
+\x8b\x93\xb6\x11\x00\x09\x92\x00\x25\x07\xfd\x02\x78\x88\x38\xdf\
+\x8b\xfb\xc0\x6f\x32\x00\x4b\x20\x9b\x0a\x18\x4d\x05\xf4\x8a\xca\
+\xef\xb7\xfd\x4d\x3f\x60\x7d\x80\x37\x82\x80\xe7\xa4\xc2\x9f\xcc\
+\xf6\x93\x00\x28\xe6\x82\x73\xbf\xf2\x3c\xf0\x12\x96\xd4\x00\x24\
+\x71\xd8\x8a\x16\xe0\x10\x05\x27\x86\xfa\xf7\x0b\x5b\xb5\x5d\x4b\
+\xfa\x4e\x79\xb3\x0b\x32\x26\x2e\xc8\x9d\x4c\x07\xdf\xdf\x5c\xcf\
+\xbf\xb7\x37\xf7\xa8\xe6\x00\x21\xce\x3b\x57\xa5\xf7\x92\x82\xe5\
+\x05\xa9\x5a\xaf\x15\x46\x08\xfc\x7b\x87\x08\x2c\xf7\x7b\xc3\x12\
+\x9a\x80\x20\x07\xa7\xac\x65\x79\xaf\x5f\x0c\x09\x10\xe8\x09\xfc\
+\x8f\xb6\xeb\xfd\xb5\x95\x00\x48\x9e\xee\x63\x33\x98\x7c\x23\xf7\
+\x07\x31\x5d\x60\x31\x20\x9a\x02\xf8\x0c\xfc\xa6\x50\xdf\x4d\xa1\
+\xe9\x8b\x9e\xde\xf4\x01\x5e\xed\xc9\x4d\xc3\x29\x17\xb0\xf3\x0d\
+\x97\x50\x6c\x05\xb8\xae\x59\xe1\x82\xdd\x0e\xe9\xf5\x55\x3f\x83\
+\xe1\x03\xb6\x3c\xc7\x43\x00\xf2\xb9\xd4\x34\x1b\xc5\xb9\xe8\x29\
+\x0b\xce\xe9\xc1\x57\x9c\xe6\x6d\xdb\xea\xce\xfd\x60\x78\xfe\xce\
+\xbf\x67\xea\xbe\x04\xb7\x1b\xd7\x73\xc0\xed\xd5\x00\xdc\xe3\x80\
+\xe7\xdf\xd3\xa3\x83\x07\xbc\x86\xf2\xbd\xaa\xb2\xc7\x6a\x01\x9e\
+\xcf\x96\x43\x00\x76\x80\x24\x80\x03\x5d\x82\x9f\x69\x0b\xdc\x44\
+\xe0\x9a\x82\xc5\xff\x6e\x29\xe4\x00\x90\x27\x01\x9c\x88\xe0\x6f\
+\x2b\xa3\xb7\x9d\x00\x48\x90\x04\x2e\xc3\xdd\x79\x99\x6e\xdc\x88\
+\xf8\x42\x21\x00\xbb\x54\x42\xac\x97\x18\xf0\xe9\xb3\x21\xc8\x40\
+\x02\xde\xf6\xf7\xf4\xa6\x6b\x1a\x48\x6d\xc1\x0d\x64\x87\x68\x10\
+\x0e\x71\x80\xa7\x07\x0f\xf4\xfe\x8a\x99\xe0\x75\x0c\x82\xef\x6f\
+\x7e\xed\x40\x9e\x0b\x3e\x90\x0b\x5f\x83\x4f\x4b\x90\xaf\xd4\xdb\
+\x36\x86\x67\x17\xff\x13\x88\xf8\x21\xdb\xc1\xbf\xcb\x20\x9a\xe7\
+\x24\x3b\x7c\x6f\x48\x1b\x1e\x54\xd0\xab\xe0\x09\x01\xbc\x87\x18\
+\x14\xa0\x87\xa8\xf8\x86\x24\x04\xcb\x4f\x08\x56\x00\xe0\x9e\x5e\
+\x5d\x12\x82\x24\x03\x55\x5b\x70\x40\xce\xff\x4e\xe0\xb7\xad\x26\
+\x1e\x37\xd9\x24\x0d\x05\x10\xc0\x95\x08\xfe\x73\xf3\xa8\x28\x89\
+\x74\x8a\x00\xe8\xba\x3f\xc4\xed\x24\xed\x1b\xd5\xb9\x53\x07\x6c\
+\x26\x07\x66\xb9\x8c\x7b\x24\x00\xd3\x64\x7b\x22\x00\xa6\xf6\x9b\
+\xaa\x53\x4f\x9c\xeb\xd8\xfa\xaa\x0f\xc0\x25\x04\xb5\x6e\xe9\xe8\
+\xb3\x8d\xa0\x93\x10\xd4\xf3\x01\x20\xcc\x64\xf0\xe6\x0f\xc8\x63\
+\xf0\x38\x0a\xbd\x4e\x42\xf0\x9e\x17\x46\x00\x6a\xfb\x84\x10\x80\
+\xed\xf9\x3a\x42\x2b\xf0\xfc\x8e\xed\x90\x72\x76\xf8\xb9\x71\xc0\
+\x57\x81\x1c\xd0\x00\x14\x07\x9f\x12\xa6\x8b\xb6\xf9\xc5\x3d\x29\
+\x9f\x0d\xdb\x05\xa3\xd7\x89\x07\x0e\x01\x78\x9d\x7c\x96\x93\x59\
+\xe5\x92\x81\xe5\x80\x9f\x7f\x07\xac\xa7\x67\xe6\x80\x00\xbe\x43\
+\x00\xcd\x86\x97\x00\xf2\x01\x3f\x2d\xb5\xf7\x06\x24\x80\x66\xe6\
+\x9a\x12\x4a\x47\x08\x80\x04\x49\xa0\x5f\x3c\xf8\x6b\x22\x6f\x2e\
+\xe9\xdd\xc9\x02\x04\x78\x02\x62\xb9\xc4\x09\x00\x35\x01\xa3\x54\
+\x06\x28\x99\x8c\x04\x24\xe0\xf9\x79\x0a\x68\xa3\x7c\x06\x8a\x23\
+\xd1\x70\x22\x05\x8a\x0f\x41\xd6\xc1\xae\x1d\xf4\x13\xa8\x26\x42\
+\xc0\x6f\xe0\xc9\x1a\xf4\x12\x82\x6b\x5e\x80\x03\x78\x0e\x48\x55\
+\xeb\x70\xfd\x05\xec\x6f\x86\x72\x0e\x80\x08\x29\x82\x38\x36\xb4\
+\x5f\xba\xa3\x4f\xd8\x0a\x11\x38\x16\x80\x2d\x32\xfb\xdc\xe4\x1d\
+\x43\x46\xd1\x65\x4f\x0b\x6e\x24\x40\xe2\xdd\x00\x05\x9c\x1e\xc0\
+\x43\xbc\xea\xef\xd8\xe4\x61\xea\x3d\x80\xda\x9b\x1b\x96\x5b\x86\
+\x46\x51\x30\x82\x10\x8e\x3f\x35\x12\xa0\xda\xf4\x0c\xe8\x20\x48\
+\xc0\xf9\x1e\xf7\x4d\xb9\x35\x18\x11\xb0\x7d\xa3\x29\x08\xc3\x52\
+\xee\x3b\x93\xd0\xc2\x0e\xb4\xaa\xcf\xcb\x59\x2b\x4a\x23\x1d\x23\
+\x00\x12\x24\x01\x9a\x48\xf4\xcf\xb8\xb1\x59\x25\x12\x03\x3e\xf4\
+\x89\x14\x2d\x00\x01\x0f\x64\x06\x94\x2a\x6c\xcf\x08\x81\xf6\xf4\
+\x3d\x69\x04\xd2\x57\x10\xe8\xed\xdd\xef\xb9\x89\x60\x3a\xea\xbe\
+\x6d\x78\xb5\x02\x37\x64\x68\xba\xe0\x8f\x00\xbc\xea\x24\x04\x23\
+\x6c\x73\x81\xeb\x27\x03\x69\x1e\xd8\x8a\x29\xe1\x02\xda\x70\x7b\
+\x6a\x43\x69\x07\xf1\x51\x6a\x10\x0e\x11\x44\xfe\x04\xec\xc0\xb7\
+\x12\xd4\x1e\x85\x5f\x49\xc2\xb1\x9d\x6b\x48\x3f\x80\xed\x05\x3b\
+\xc8\x1e\x1b\xbc\x20\x56\x2b\x08\xb3\xd9\x21\xf8\xbd\x27\x9c\x17\
+\xe2\xe4\x93\x1a\x80\xec\xe9\x6d\xe5\xef\x8c\x0c\x2c\xb7\x8c\x43\
+\x10\x96\xdb\xfb\x3b\x6a\x3f\x03\x7b\x93\x6d\x36\x6d\x04\x7a\x0b\
+\x7b\xfe\x06\x12\x40\xd3\x52\x9c\x8e\x99\xc1\x4f\xa0\x3f\x30\xed\
+\xa2\x1e\x79\x48\x47\x09\x80\xe4\x99\x7e\xd8\x0e\x77\x7f\xc2\x6d\
+\x6a\x7e\x4f\x25\x3d\xf6\x5c\x0b\x00\xd6\xfb\x23\xe0\x2b\x15\x30\
+\xe8\x33\x9a\x06\xa6\x59\x46\xcd\x80\xf7\xf6\xcc\x37\x00\xc2\x6f\
+\x20\x4c\x02\xc7\x89\xa7\x02\x5b\x00\xdf\x94\x6a\xbf\x62\x4a\xc8\
+\xf2\xaa\xa3\xd0\x13\x22\x34\x23\xc0\xee\x73\x14\x9a\x8a\x76\xe0\
+\xa4\x05\x2b\x76\xbd\xa1\x38\x08\x65\x6f\xef\x8d\x2c\x84\xbc\x5a\
+\x05\xfc\xc1\xbf\x29\x5f\x87\x98\x00\xce\x27\xcf\x8f\x5d\x35\x05\
+\x5c\xb0\x1a\xca\x79\x86\x02\x64\xb7\x68\x30\xce\x1e\x70\xde\xc5\
+\x91\x82\xc7\x91\x07\x1e\x67\x5e\x00\xf0\x3e\x13\xc1\xb0\x7c\xa6\
+\x82\xa5\xda\xff\xb2\xf7\x17\xf6\x3d\xed\x09\xec\x0d\x0e\x7a\xda\
+\x2c\xec\xfd\x0d\x24\x82\x1c\x55\xff\x55\xb8\xbd\x3e\xcd\x7a\x7e\
+\x79\x4a\xc7\x09\x80\x04\x49\xe0\x40\xe0\x73\x08\x24\x5a\x60\x24\
+\xf6\x41\x84\x2a\xcf\x48\x80\x54\xff\x72\x85\x01\x1f\x18\x09\x94\
+\xc5\x67\x93\x9b\x06\xa4\xe6\xe3\xb9\xa6\x29\xed\x79\xd3\xd1\x08\
+\x1c\x73\xc1\xb1\xfb\xcd\x08\xdf\x80\xe1\xd1\x00\x24\x91\xf8\x13\
+\x87\x0c\x05\xdc\x6a\x84\x41\xed\xd9\x83\xbe\x01\xc5\xa7\x00\xee\
+\x80\xa1\x00\xf0\x7d\xbd\x7f\xe0\x38\xec\x73\x9c\xf8\x7f\xe4\x76\
+\x18\xf0\xfd\x9f\x15\x53\x00\xfc\x3d\xb9\x28\xa0\xaa\xef\x3e\x52\
+\x50\x7b\x71\xa9\xfa\x7b\x3c\xfe\x12\xc8\x51\x61\x3e\xc5\x14\x70\
+\x01\x0f\x8e\xed\x6f\x2b\x7b\x6e\x1e\x08\x07\x9f\x2d\x7a\x7d\x02\
+\x79\xb3\x0e\x50\x47\x02\xa8\xd7\x05\xf8\xeb\x08\x7e\xcb\xd5\x0e\
+\xb2\x83\x7f\x1d\x6e\x87\x22\xf8\xff\x9c\xb5\xa2\xac\xd2\x15\x04\
+\x40\x82\x24\x70\x1a\xee\xbe\x9d\xeb\x8d\xfb\x48\x00\x10\xf4\x4c\
+\x0b\xa8\x22\xf8\x2b\x55\x41\x04\x65\xa1\x21\x70\x50\xdb\xe2\x7c\
+\xd7\x31\x08\x0e\xe8\xdd\xb0\xa0\x29\x34\x00\x70\xfd\x08\xe0\x35\
+\x1d\xfc\x11\x00\x49\x2c\x86\x24\x0a\x15\xdc\x4e\xc2\x92\x37\xfc\
+\x17\x4c\x33\x06\x0f\x19\x80\xa2\x21\xf0\x8f\x7e\x42\xc8\x00\x7e\
+\x29\x51\x24\xa0\xaa\xf7\xfc\x28\xf8\x77\x95\x24\xfc\xea\x7d\x64\
+\xd8\x0e\x1c\x8f\xbc\xd3\x7b\x87\x79\xff\x25\x51\x58\xae\xad\xcf\
+\xc1\x09\xde\x30\x9d\xe3\xe0\xb3\x04\x27\xb9\x9f\x41\x00\xdf\xa0\
+\x5e\xbf\xa9\xf4\xf6\xf5\x1a\x40\x0d\x8f\x91\x00\x18\x19\xc8\x9e\
+\x3f\x1f\xf0\x8f\xe0\x76\x04\x82\xff\xf7\x59\x2b\xca\x43\xba\x86\
+\x00\x48\x90\x04\xce\xc2\xdd\xd5\x51\xf7\x95\xea\x66\x55\x12\x60\
+\xe6\x00\x27\x01\x93\x48\xa0\xda\xc7\xc9\x80\x88\x41\xf8\x0b\x40\
+\x44\x0c\x9c\x78\xbf\xc8\x27\x60\x80\x35\x15\xdb\xdf\xd1\x0c\x84\
+\xa3\xd0\x74\x7b\x74\xd7\x49\xe8\x33\x19\xc0\x88\x1c\x5d\x18\xe5\
+\x24\x74\x08\x02\x7c\x11\x05\xf9\x6c\x62\x2f\xc7\x0f\x04\x9e\xdd\
+\x77\x9c\xa4\x0d\xbd\x16\x41\x54\xc8\x4b\x0d\xdd\x41\x80\x00\xa2\
+\x01\xef\x3f\x27\xc4\xc1\x67\xf1\xef\x55\x02\xe0\xbd\xb5\xef\x5c\
+\x07\xe0\x21\xa1\x3f\x55\xc5\x77\x40\x0c\xdc\xa9\x67\x71\x1b\xdf\
+\x40\xbb\xde\x6e\x20\xd0\x6b\x08\xf8\xda\x18\xef\xf9\x6b\x1c\xfc\
+\x1e\xa7\x5f\x76\xf0\xd3\x62\x1e\x6f\x42\xf0\xff\x2a\x6b\x45\x79\
+\x49\x57\x11\x00\x09\x92\xc0\x3b\x71\xf7\x75\xe0\xd0\xcb\xe9\x29\
+\xa5\x7a\x5e\x72\x48\xa0\x54\x45\x6b\xa3\x4a\xda\x00\x91\x00\x1d\
+\x73\x4d\x80\x69\x04\x22\x42\xe0\xf4\xee\xa6\xa9\xd4\xe1\x77\x14\
+\x9a\x4c\x13\x50\x09\xc2\x10\x7b\xdb\xf0\xf7\xe2\xa2\x4c\x58\xef\
+\x2e\xaf\x23\xc1\x6e\xfa\x08\x40\x05\xbe\x7c\x26\xd5\x14\x30\xc0\
+\xcd\x4f\x00\x35\x1a\x00\xae\xc9\x00\x8a\x77\x3f\xe6\xd5\x3b\x0e\
+\x3d\xa5\x8c\x21\x54\x7b\x69\xe7\xab\x7b\x4f\xdc\x3e\x82\x08\x02\
+\xaa\xbf\x1f\xf4\x61\x19\x7b\x96\x02\x68\x55\x95\xb7\xdd\x54\x5d\
+\x4e\x12\x96\x98\x36\xd9\x8a\xe8\xfd\xc5\x79\x32\xae\xdf\x14\xea\
+\xbc\x54\xf7\xa9\xb7\xaf\x51\xaf\x8f\xe0\x67\x24\x50\x2b\xa2\xe7\
+\xa7\xe9\x1d\x29\xd1\xa7\xb3\xb9\xdb\x3e\xe9\x3a\x02\x20\x79\xb6\
+\x1f\x4e\xc6\xdd\x77\x40\xa4\x0c\xe7\xf2\xa0\x4e\xcf\x4d\x61\x41\
+\xf2\x09\x54\x19\xf0\x49\x03\x30\xfa\xfa\x18\x11\x30\x42\xa8\x88\
+\x88\x01\x39\x0d\x8d\x92\x20\x02\x15\xfc\x8a\x56\x20\xcc\x03\x53\
+\x01\xb7\x5f\xd5\x77\x34\x10\x70\x8f\xbd\x3d\xbc\x1a\x72\xf4\x6d\
+\xfc\xc6\x43\x4c\x01\xc5\xac\xb0\xe5\xb3\xc9\xef\xbd\xa0\x57\x47\
+\x16\xaa\x51\x86\xb8\x9f\xb3\x0b\x7c\xe9\xbd\x17\x61\x3d\xdb\x4b\
+\x06\xaa\xfd\xaf\x46\x01\xc2\x9c\x79\x1e\xef\x7f\x68\x38\x4f\x84\
+\xfa\x24\xd8\x18\xae\x2d\x85\x0c\x94\x0c\x3e\xe9\x1b\xb0\x5c\x32\
+\xf0\x38\x07\xc3\xbe\x17\x21\x3e\x19\xce\x63\x1e\xfe\xba\xd2\xeb\
+\x8f\xb9\xe0\x87\x26\x6e\x0d\x11\xf7\xcf\xc7\xe1\x47\xf4\x74\x0a\
+\x82\xff\x87\x79\xfd\x9e\xf3\x92\xae\x24\x00\x12\x24\x81\x63\x70\
+\xf7\x23\xc8\xdb\x31\x28\x01\xca\xa2\x01\x04\xf8\x2a\x33\x07\xec\
+\xfe\x7e\x30\xfb\x88\x04\xaa\xec\x3b\xa8\x50\x12\x91\xf0\x0d\x94\
+\xc4\xc4\xd6\x25\x37\x8b\x90\x4d\x77\x29\xd3\x8d\xa5\xba\xaf\xaa\
+\xf8\x4e\x2e\x81\xe2\x2c\x34\x95\xeb\x1b\xde\xf3\xfc\x80\x57\x73\
+\x0c\xd4\x28\x81\xc7\x77\xe0\x29\xc3\x9f\x38\x3c\x85\x18\x5c\x62\
+\x88\x8e\x07\x04\x48\xc1\x09\xef\x39\xfe\x3b\x6f\xf8\xce\x1b\xfb\
+\x0f\x3a\xfc\xdc\xbc\x7e\x6f\x26\x9f\xcc\xb7\x0f\x25\x04\xcb\xa7\
+\xca\x7b\x3c\xfb\x96\x73\x8e\x6a\x16\xf0\x0c\x40\x61\xd3\x3b\x1a\
+\x81\x5a\x9f\x88\xe7\xd3\x31\x81\x9f\x39\xf8\x6a\xbc\xa7\x47\xe0\
+\xc3\xe8\x28\xee\x09\xf4\x75\x66\x0a\x38\x6a\x7f\x3e\xe0\xa7\x0a\
+\xce\x44\xf0\x5f\x9f\xb5\xa2\x22\xa4\x6b\x09\x80\x04\x49\xe0\x0d\
+\xb8\xbb\x05\xb7\x81\xdc\x1e\xc6\x71\xd0\x29\xd1\x01\xea\xf5\xfb\
+\x90\x08\xfa\xfa\x51\x1b\xa0\xad\xea\x12\x41\x99\x27\x11\x31\xbf\
+\x40\xc9\xa7\xfe\xfb\x52\x8b\x5d\x40\xab\xa6\x03\xb8\x20\x07\xd7\
+\x3c\x70\x81\x0f\xce\xf9\x8e\xc9\x60\xba\xe9\xc6\xea\xf8\x01\x27\
+\xc5\x58\x49\x25\xf6\x4e\x68\x22\xcf\x57\x8e\xc3\x52\x04\x75\x1a\
+\xca\x0e\x1c\x80\xc2\x04\xe0\x66\xf0\x81\x57\x6d\xf7\xa9\xf7\x5e\
+\x07\x9e\xfa\x37\xcb\x9b\xba\xeb\x09\xed\xa9\x19\x7c\x96\xf2\x1d\
+\x04\x09\x42\xc6\xf1\xa5\x46\x20\x49\x40\xfe\xbd\x29\x33\xf8\x9a\
+\x3c\xac\x47\xbd\x3e\x81\x7f\x94\x7a\xfd\x51\xb0\xc6\x04\xf8\x99\
+\xc7\xbf\xee\x9a\x07\xf9\x80\x9f\xe4\x03\x08\xfe\x42\xd7\xcd\xcc\
+\x22\x5d\x4d\x00\x24\xcf\xf2\x10\x21\x2d\x39\x36\x31\xb7\x9b\x57\
+\x1d\x70\x04\x44\xb2\xfb\xcb\xc2\x14\xe8\xef\x63\x44\x60\xa2\x46\
+\x00\x8e\x7f\xa0\xc2\xb5\x81\xb2\x00\x37\x91\x81\x69\x88\x88\x01\
+\xaf\xc3\x56\xc1\xef\x03\xbe\xa3\x05\x98\xbe\xd0\xa1\xe9\x6a\x02\
+\x01\x0d\xc2\xe7\x24\x54\xc3\x86\xea\x08\x42\x4f\xb2\x50\x18\x19\
+\x78\xd2\x84\xc3\x08\x20\x42\x07\xf0\xe7\x04\xd8\x21\x7b\x15\xf4\
+\xbe\x2c\x3e\x39\xb2\x4f\x82\xdc\x0e\x21\x04\xc7\xc9\x67\x29\x76\
+\xbe\x72\xec\x25\x83\x30\x22\x50\x49\xc0\xcd\x03\x60\x39\x01\x2c\
+\x61\xa7\xc9\x55\xf9\xa6\x0b\x7c\x8b\xf5\xf8\x63\xb8\x1f\x11\x24\
+\x30\xc6\x33\xfc\xe8\xef\x72\xb0\x0f\x40\x5e\x0e\x3f\xea\xf9\xbf\
+\x93\xb5\xa2\x22\xa5\xeb\x09\x80\x04\x49\x80\x26\x12\xa1\x61\xc4\
+\x53\x72\xbd\x61\x25\x42\x60\xc8\x08\x01\xf5\xfc\x48\x02\x30\x40\
+\x26\xc1\x00\x3b\x66\x5a\x41\x45\x68\x0a\x26\xcf\x26\x64\x7e\x04\
+\x09\x6a\x0f\xf0\x0d\x8f\x76\xe0\x9e\x23\xe6\x1e\x50\x1c\x8b\xb6\
+\xa2\x8d\x18\x82\x30\x9c\x3c\x04\xbf\xed\x6f\x4a\xc7\x9e\xfa\x37\
+\x08\x8d\x16\xf8\x09\xc0\x90\x0e\x42\xf5\x6f\x2d\xc5\xf6\x6a\x01\
+\x8e\x4a\x1f\xf6\x37\x5f\x18\x4f\x7a\xec\x9d\xc9\x36\x20\x90\xa0\
+\x03\xb6\x1b\xba\xe3\x5f\x29\xe3\xef\x05\x98\x99\xa3\x4f\x3a\x03\
+\xd5\x9e\xdf\x52\x9d\x7b\xd2\x19\xd8\x74\xce\x61\x91\x02\x99\xc9\
+\x57\xab\xf3\x70\x5e\x6d\x54\x00\x7e\x84\xab\xfc\x8c\x04\xc8\xde\
+\x17\xce\xbe\xfc\xec\x7d\x92\x95\xb8\x1d\xd7\x2d\xa1\xbe\x38\xe9\
+\x09\x02\x20\x79\xae\x1f\x76\x07\x9e\x2c\x34\x23\xd7\x07\x0f\x35\
+\x09\xaa\xcc\x0c\x30\x06\x88\x00\x50\x13\xa0\x7d\x95\x9b\x06\x8c\
+\x08\x98\xc6\x40\xd9\x84\x26\x1b\x67\xe0\x80\x3f\x14\xf8\xa6\x57\
+\xdb\x70\x22\x04\x41\x47\x21\x80\xe2\x6c\xf4\x45\x01\xbc\x61\x47\
+\x5f\x28\xd0\x43\x16\xe0\x31\x1f\x1c\xe0\xab\x29\xc6\x9e\x06\x08\
+\x91\x10\xe0\x1b\xfe\xcf\x8a\x4a\xef\x09\xcb\x01\x84\x38\xf8\xfc\
+\xbd\xba\x9b\x90\xe3\xa8\xee\xbe\x90\x9e\xc7\x7e\x57\xec\xf9\x30\
+\x22\xe0\x5a\x03\x4f\xe4\xb1\x65\x1a\x2f\xd9\xf2\xe4\xd4\x23\x90\
+\x93\x9a\x3f\x3a\xc2\xb7\x11\xae\xf2\x93\x0f\xa0\x20\x95\x9f\x16\
+\x6b\x3c\x0a\xc1\xdf\xd9\x25\x9b\x34\xa5\x67\x08\x80\x04\x49\x60\
+\x07\xe0\x9a\xc0\xbc\x5c\x1f\x56\x82\x42\xa6\x05\x8b\x4c\x41\x32\
+\x09\x80\x99\x04\x03\x8c\x0c\x00\xc9\x80\x69\x04\x14\x31\x70\x32\
+\x0a\xcb\x4c\x2b\x00\x53\x8d\x0e\x98\x5e\xf0\x9b\xa6\xcf\xf6\x77\
+\xb3\x04\xc1\x51\xfd\xbd\xe3\x12\x98\x93\xd1\x21\x08\x37\x7c\x18\
+\x1a\x31\xf0\x4f\x5e\x22\x9e\xc9\x71\x0c\xca\x48\x80\x12\x11\x70\
+\x3d\xfd\x86\xd2\x3e\xb6\xf8\x0e\x1c\xc0\xdb\x22\x0a\x10\xb4\xf1\
+\xc1\xcd\xce\x8b\xc8\xcb\x77\xec\x7d\xcb\xed\xf5\x55\xb5\xdf\x96\
+\xaa\xbc\x25\x33\xfb\xa4\xb7\x5e\x09\xd9\xa9\xde\x7b\x09\xfc\xa6\
+\x92\x95\xe7\xfc\xbd\xa9\x24\xf2\xd4\x79\x48\x8f\x3c\xfb\x23\x1c\
+\xf4\x16\x82\xdf\x10\xf6\xbe\xd5\xe0\x19\x7e\x39\xab\xfc\x24\xf7\
+\xe2\x76\x4c\xa7\x06\xf6\xa4\x91\x9e\x22\x00\x12\x24\x01\xd2\x00\
+\x28\x9c\x72\x70\xee\x0f\xa7\x6a\x03\xe4\xfc\x73\xb4\x01\x04\x7b\
+\xff\x00\x37\x0b\xfa\x07\xb9\x66\xa0\x84\x0e\x19\x61\x88\xd1\x86\
+\x7c\x10\x52\xc9\x9b\x13\x60\x9a\x8a\x8d\x2f\xc0\x6f\xba\x0e\x45\
+\x43\x09\x35\x3a\xd1\x05\xc3\x0c\x75\x14\x4a\x33\x41\x75\x1a\x7a\
+\xd2\x87\x95\xa8\x81\x9b\xa1\xc8\x5b\x43\x9a\x02\x01\x9f\x80\x47\
+\x6c\x67\x17\x36\xc2\x0f\x1c\xf5\x1e\x02\x31\x7a\xef\x60\x1c\x17\
+\xd0\x1e\x07\x9f\xad\xf4\xda\xe0\x02\xde\x56\x32\xf4\x0c\xc7\x91\
+\xe7\x9d\x88\xc3\x21\x01\xa9\xe2\x4b\x32\x10\x59\x7c\x76\x5d\x84\
+\xf1\x46\x45\x8f\x3f\xa2\x6c\xc5\x79\xf9\xa5\x50\xc4\xea\x74\x04\
+\xff\x68\x5e\x15\xb6\x43\x7a\x8e\x00\x48\x90\x04\x28\x2e\x47\x93\
+\x8a\x7c\x34\xf7\x87\x09\x71\x10\x1a\x34\x9a\x90\x1c\x81\xa4\x01\
+\x20\xf8\x4d\x24\x03\x63\x90\x34\x02\x41\x04\x7d\x5c\x23\x30\xcb\
+\x34\xc0\xa8\x2c\x46\x1c\x96\xbc\x04\x60\x98\x3e\xf0\x47\xf8\x0c\
+\x54\xf0\x93\x98\xfe\xef\x5c\xb3\xc1\x99\xdc\x14\xcc\x80\x56\xe0\
+\xf1\x11\x80\x0a\x7c\xff\x2c\x44\x3e\x11\x36\xbe\xcc\xd3\x67\x67\
+\xda\xca\x67\xb9\xf7\x8f\xc2\x03\xef\x60\x1c\x47\xbd\x57\x7b\x71\
+\x87\x08\x7c\xde\x7a\xd5\xb1\x27\xd5\x79\x65\xe6\x9d\x00\x01\x34\
+\x65\x06\x1f\x1f\xb0\xe3\x24\xef\xb0\x1e\x1e\xf1\x37\x3c\x02\xd6\
+\xc8\xb0\x50\xfb\xe9\xbb\xba\xb0\xf5\x1b\x41\x27\x63\x3e\x72\x39\
+\x6e\x1f\x47\xf0\xe7\x56\x61\xbb\xa4\x27\x09\x40\x0a\x12\xc1\x09\
+\xf8\x00\x37\xe0\xe1\xa4\xdc\x2b\xf7\xf9\x06\x64\xa4\xc0\xec\x13\
+\x4e\xc1\xc1\x41\x80\xc1\x01\xa1\x11\xf4\x73\x12\xa8\x56\xc1\x44\
+\x8d\xc1\x76\x4c\x03\xd3\x49\x2a\x72\xc0\x5d\x32\x1d\x30\xab\x66\
+\x81\x6b\x36\x28\x4e\x40\xcf\x67\x65\x54\xa2\x18\xb5\x28\xeb\x74\
+\xe6\x2d\x60\x9c\xe1\x35\x13\xbc\xb9\x04\xe2\xef\xf2\xf9\xa2\x24\
+\x2c\xbf\xdf\x0e\x57\xef\x0d\x91\x81\x67\x2b\xd9\x76\x6e\xb6\x9e\
+\x24\x02\x1f\x09\x04\x6c\x7a\x37\xdb\xce\x96\x6a\xb9\x24\x89\xa6\
+\x88\xf1\x0b\xd0\xb3\xef\x1a\x34\x32\x8f\xe7\xec\xf3\x1e\x7f\x8c\
+\x01\xdd\x1a\x1d\x66\xe0\xe7\x3d\x3e\xef\xf5\x79\x56\x5f\x83\xa7\
+\xfb\xe6\xdf\xeb\x53\x76\xdf\xfb\x10\xf8\xdf\xcc\xfd\xf7\xd7\x26\
+\xe9\x69\x02\x20\x79\xbe\x1f\x16\x02\xcf\x15\x58\x98\x7b\xe5\xaa\
+\x6f\xc0\x33\xaa\xb0\xc2\x7d\x01\x04\xfc\x01\x61\x12\x08\x87\xa1\
+\x41\xfe\x02\x91\x61\x48\xe6\x03\xcb\x28\xa4\x08\x03\x85\x11\x4d\
+\x9e\x54\x64\x3b\x6a\xbf\x32\xc8\xc8\x34\x5d\x72\x50\x1d\x86\xce\
+\xdf\x25\xc8\xd5\x74\x63\xc5\x37\xa0\x0e\x59\x96\x1a\x02\x9b\xc7\
+\x00\xc0\x99\x4f\x20\x74\x20\x51\x88\x78\x9c\x79\xae\x57\x9f\xf3\
+\x81\x9a\x6b\xef\xcb\xcb\x97\xdf\x09\x30\x7b\x92\x73\x9c\x10\x9e\
+\xf8\xbb\x3a\x20\xc7\x52\xec\xfd\xa6\xfa\xb9\xe9\x26\xf0\x10\xe8\
+\x91\x00\xc8\x7e\x67\xea\x7c\xad\xc6\x42\x79\x40\x4e\x3d\xec\xed\
+\xc9\xc6\x67\xe0\x97\x49\x3d\x44\x0e\xaa\x93\x2f\x5f\x5b\x9f\x64\
+\x35\xf0\xd4\xde\x42\x17\xee\x28\x5a\x7a\x9e\x00\x48\x90\x04\x48\
+\x03\xb8\x11\xb7\xe3\x0b\xb9\x80\xdf\x2c\x60\xa0\xae\x72\x47\x60\
+\x7f\x95\xf9\x07\x68\x33\xc9\x51\x38\x38\xc0\x49\x40\x86\x0f\x91\
+\x0c\x6c\xe1\x30\xe4\x63\x0d\xc4\x64\x24\x32\xb1\x88\x81\xb5\xe4\
+\x89\x10\xb8\xe0\x57\x7a\x7e\xe9\x47\x30\x94\x0c\x44\x67\x84\xa2\
+\x57\x73\xf0\x24\x26\xa9\x24\xa1\xe6\x09\xa8\xe3\x03\xe8\x2b\xd7\
+\xf4\x0f\x26\xed\xf8\xf2\xf2\x6d\x4f\xde\xbe\x6a\xe7\xab\x4e\x39\
+\x57\x1b\xf0\x03\xde\xab\xce\xfb\xd4\x7e\x99\xb8\x43\x03\x75\x1a\
+\x16\x9f\x88\xa3\x2e\x46\xe6\x91\x73\x8f\x92\x77\x84\x8d\xef\xa8\
+\xfa\x23\x1c\xf8\x40\x0e\x3e\x96\xd0\x53\x73\x23\x02\xf9\xab\xfb\
+\x24\x34\x8c\x97\x16\xec\x7c\xaa\x90\xdf\x5b\x1b\x65\x83\x20\x00\
+\x29\x48\x04\x34\xa9\xe2\xa7\x21\xe7\x65\xc8\x1c\xf1\x11\x01\xa9\
+\xfa\xa6\xf0\x0f\xb0\x34\x62\x16\x25\xf0\x6a\x05\x2c\xa1\x88\x11\
+\x41\x1f\x4f\x2f\x26\xcd\x40\x4e\x50\x22\x9d\x85\x2c\xb7\x80\xef\
+\x5d\x92\x91\x04\x50\xe2\x39\x00\xcc\x8f\x50\x72\x4d\x07\x55\x33\
+\x50\xc9\x42\x82\x5f\x71\x32\x06\x48\x41\x31\x03\x54\x58\x48\x6f\
+\x3f\x17\x25\x59\x47\xda\xed\xb6\xda\x6b\x7b\x09\x20\xd0\x93\x8b\
+\xcf\x2a\x01\x70\x7b\xbe\xe9\x9e\x2b\x26\xe1\x70\x92\x76\xd8\xb0\
+\xdc\x26\xb7\xd7\x65\x0c\x5f\xf4\xf8\xcc\xa3\xef\x38\xf7\x10\xf4\
+\xc3\x23\x6e\x68\x6f\xb4\xc6\xc9\xc1\xb1\xf3\xdd\x61\xc4\x39\x03\
+\x9f\xe6\xec\xbb\x14\xb7\x4b\x3a\x31\x7f\x5f\x11\xb2\x41\x11\x00\
+\x09\x92\x00\xad\x3d\xf0\x03\xc8\x29\x5f\x20\x20\x9e\x30\x9b\x48\
+\xf0\x11\x8e\x42\x93\xa5\x14\x0b\x67\x21\x69\x05\x44\x04\xcc\x59\
+\x38\xc8\x88\x80\x7d\x2f\x32\x0b\xe5\x7c\x04\x5c\x2b\x28\x39\x09\
+\x46\x6e\x24\xc1\x74\xc2\x8b\x12\xd0\xb6\xaa\x29\x94\xd4\x08\x82\
+\x57\x53\x00\x25\xf3\xd0\x13\x5e\xf4\xcc\x5a\xe4\x33\x07\x42\x62\
+\xf8\x01\x6f\xbe\x9c\x33\x0f\x2c\x2f\x88\x6d\x01\xe4\x10\x55\xde\
+\xb0\x9b\x0e\x61\xb0\xcf\xf2\x3c\xcb\x76\xa7\xdd\x12\xc3\x72\x9d\
+\x59\x78\x58\x18\x4f\x80\x5e\xf4\xf8\x40\x3d\x3e\xf6\xf4\xcd\x91\
+\x11\x76\xec\xc6\xf3\xbd\xc0\x77\x88\x4a\x3e\x4b\x7e\xf2\x34\xf0\
+\x01\x3d\x1d\x9f\xc4\x23\x4f\xd9\xe0\x08\x80\x04\x49\x60\x73\xdc\
+\x5d\x87\xdb\x11\x85\x5d\x24\x84\x08\xb8\xc3\xcf\x9d\x74\x44\x46\
+\x0d\x60\x60\x90\x47\x0e\x06\xfa\xbd\x91\x83\x2a\x1f\x91\xc8\xa2\
+\x07\x6c\x60\x52\x49\x0c\x40\x2a\x89\x09\x4c\xb9\x03\x91\xcf\x56\
+\x64\xb2\xec\x43\xc7\x5c\x28\xa9\xe6\x82\x32\xe3\xb1\x61\x7a\x1d\
+\x8d\x9e\x5c\x02\xaf\x33\xd1\xbb\xe4\x18\x89\xb2\x86\x9e\x32\x65\
+\x96\x3b\x79\x86\xed\x75\xd0\x29\x71\x78\x97\x00\x64\x98\x8e\xf7\
+\xea\x86\xaa\xe2\xcb\x90\x9d\x25\x9d\x7a\x62\x92\x4d\x06\x7c\x52\
+\xef\x95\x91\x79\xe4\xd1\x1f\x19\x15\x60\xe7\x3d\x3d\x1d\x5b\xe4\
+\xf0\x63\x43\x77\xf1\x5c\x4b\x10\x46\xb1\xc0\x27\x21\x47\xf3\x07\
+\x11\xfc\xeb\x0a\xfb\x3d\x75\x48\x36\x48\x02\x90\x82\x44\x70\x0a\
+\xee\xbe\x04\x45\x69\x03\x24\x32\xcc\xe6\xc4\xe4\xc9\x34\xa0\x89\
+\x48\x79\xb6\x20\x03\x79\x1f\x1f\x5f\xc0\x09\x40\x68\x07\x7d\x22\
+\xa9\x88\xf2\x0b\xfa\xc4\xd0\xe4\x0a\x7e\xae\xf0\x24\x24\x36\x93\
+\x71\x59\xe4\x16\xb0\xd0\xa2\x0a\x74\xd3\xf5\x21\x38\xda\x82\x62\
+\x42\x94\x54\xb0\x2b\x66\x02\xc8\x69\xd1\xd5\x8c\x42\xe5\x59\x64\
+\x0e\xbf\x6f\xfc\xbc\x13\x97\xf7\x78\xe7\xd5\x3c\xfc\x66\x84\x4a\
+\x6f\x29\x60\x97\x1e\x7c\xae\xde\x3b\x53\x6e\x09\x4f\xbe\x85\xc0\
+\x37\xc6\xa4\x37\x9f\xf7\xf6\xd6\x88\x50\xf9\xc7\xb8\x7d\xcf\x3c\
+\xfa\x04\x78\x39\x9c\x57\xa6\xfe\x16\x07\xfc\x15\xb8\xbd\xa7\xdd\
+\x8b\x75\xb4\x53\x36\x68\x02\x20\x41\x12\xd8\x04\x38\x09\xbc\xad\
+\xd0\x0b\x05\x52\x73\x79\x8f\xcd\xd5\xfa\xb2\xd0\x0a\x44\x8a\x71\
+\x9f\xf0\x0b\x08\xb3\x40\x9a\x07\x20\x42\x8c\xcc\xb9\x48\x26\x82\
+\x4a\x06\x15\x9e\x71\xe8\x64\x1e\x8a\xba\x9d\x35\x0f\x4a\x6e\x68\
+\x31\xe8\x2c\x54\x73\x12\x44\x34\x41\xcd\x28\x94\x12\x9a\xc4\xc3\
+\xd5\x79\xa6\xf6\x2b\xbd\xb9\x21\x34\x00\x02\xbb\x1c\x7c\xe3\x38\
+\xef\x9a\xc2\xd6\x97\x39\xf6\x62\xe2\x0d\xbb\x8e\x1a\x41\xbd\xe6\
+\x24\xec\xd8\x72\x24\x9e\xe3\xd8\x53\x73\xf5\xdd\x30\x1e\x9f\x9a\
+\xab\x21\x4c\x86\x90\x70\x5e\xfe\xc0\x27\x21\xef\xfe\x3b\x10\xfc\
+\x8b\x0a\xfd\xdd\x74\x58\x36\x78\x02\x90\x82\x44\x70\x14\xee\xfe\
+\x07\xb7\xb9\x85\x5f\x4c\x0d\x1f\x52\x13\x93\xbd\x5e\x72\x01\xcc\
+\xc0\x5c\xe1\xb3\x11\x01\x1b\x79\x28\x1c\x88\xca\x70\x64\x4e\x04\
+\x62\xc2\x12\x39\x10\x89\x99\x09\x22\xeb\xb0\x2c\x12\x8e\x14\xff\
+\x81\x6d\x86\x99\x07\x72\x0a\x74\x37\xe1\xc8\x01\xbf\x08\x13\x7a\
+\xd7\xf0\xf3\xae\x87\xe7\x35\x07\x5c\xc0\xcb\x63\x9b\xa9\xf9\xa2\
+\xd7\x17\x6a\x3d\xc8\x29\xb6\x1a\x3c\x27\xdf\xa2\x19\x77\x1a\x3c\
+\x74\xc7\x00\xcd\x86\xe1\x0a\xa7\x1e\x8d\xc6\x63\xb9\xfa\xa3\x42\
+\xfd\x17\x3d\xbd\x98\x90\x93\xc7\xf0\x9b\xca\x52\x5e\x4a\x56\x61\
+\x31\x82\x37\x04\xe7\xe3\xf6\xc5\x5e\x4c\xec\x49\x2a\x1b\x0d\x01\
+\x90\x88\x70\xe1\x67\x70\xfb\x8f\xb6\x3c\x7b\x88\x79\xe0\xf8\x0a\
+\x24\x19\x30\x33\x41\x68\x07\x55\x91\x50\xc4\x4c\x82\x3e\xb1\x55\
+\x98\x19\xe1\x0c\x50\xaa\xc8\x01\x49\x15\x1e\x55\x28\x8b\x10\xa3\
+\x63\x2e\x94\x9c\x35\x0f\x8c\x92\x04\xbc\xcc\x42\x54\xd3\x88\xcd\
+\x70\x0d\x40\x4e\xc8\x09\x6e\x76\x9e\x65\x59\xca\xcc\xb9\x4a\x28\
+\xaf\x29\x96\xca\x6a\x08\xe0\x4b\xc0\x37\xc4\x14\x5b\x2c\x74\x27\
+\x40\x5f\xab\xb9\xbd\xfe\x28\x81\x7e\x8c\x0f\xcd\xad\x8d\xb9\x80\
+\xaf\x8b\x94\x5e\x5b\xd4\x65\xd9\xed\xea\xed\xa5\xdc\x81\xdb\xd9\
+\x08\xfc\x87\x0b\xff\x6d\x74\x89\x6c\x54\x04\x20\x05\x89\x60\x3f\
+\xe0\x0b\x94\x6e\xd7\xb6\x8b\x86\x98\x08\x2a\x19\x50\x4f\x6e\x8a\
+\x6c\x43\x6e\x02\x54\x5c\xb0\x57\x2a\x8e\xc3\x90\x47\x11\x04\x11\
+\x54\xab\xca\xb9\x32\xbc\x68\x2a\x11\x05\x19\x29\x28\x29\x8e\x41\
+\x5f\xa4\x20\x84\x00\x9c\x19\x7b\xfc\x03\x70\x44\x62\x8e\x0a\x7c\
+\x68\xba\xf3\xe7\x3b\x13\x6b\x3a\xa0\xa7\x63\x01\x70\x71\x4c\xdf\
+\x81\x18\x8d\xe7\x6a\x09\x0d\xbc\x54\x93\x47\x08\x9a\x4a\x78\xb1\
+\x3d\xa0\x27\xa1\xd5\x78\x3f\x8c\xc0\xbf\xad\x6d\xbf\x87\x2e\x91\
+\x8d\x92\x00\x48\x90\x04\xb0\xab\x85\x4f\xe1\xf6\x61\xc8\x30\xed\
+\x58\x62\x51\x01\xe7\x27\x03\xe9\xdc\x93\x80\x95\xab\x19\xc9\x5e\
+\xbe\x22\x7a\x7d\x41\x0c\x6c\xc6\xa2\xaa\x4b\x12\xdc\x34\xf0\x85\
+\x14\xfd\x39\x06\x22\xf7\xc0\x5d\x13\xd1\x31\x00\xdc\xf0\x9f\x67\
+\xa2\x0e\xe1\xf0\xb3\x7c\x2a\xbe\x1c\x80\x43\xbd\x7f\xa3\xce\x52\
+\x73\x6d\x91\xac\x43\x9b\x63\xeb\xb3\xad\x21\x06\xe2\x34\x1c\x6d\
+\xc1\x10\xe1\xbf\xd8\xd5\x76\x8b\x07\x3e\x8d\xda\xbb\x10\xb7\x6b\
+\x11\xfc\x8d\xb6\xfd\x06\xba\x48\x36\x5a\x02\x90\x82\x44\x30\x1f\
+\x38\x11\xbc\x1d\x72\x9c\x84\x54\x5b\xfc\x66\x82\x33\x79\x68\xc8\
+\x64\x23\xa6\xb2\xbc\x59\xa5\xcc\x22\x0d\x4e\x96\xa1\xd8\xdc\x0c\
+\x43\x35\xd1\xc8\xf4\x1e\xab\x99\x86\x9e\xe1\xc1\xea\x9c\x7d\xca\
+\x6c\xb8\x12\xfc\x96\x98\x61\x47\x2c\x93\xc5\x6d\xfd\x06\xfb\xcc\
+\x56\xcd\x69\x88\xac\xbd\xa6\xbb\x92\x8e\x25\x1c\x77\xac\x77\x57\
+\x32\x01\xa5\x4d\xef\xf1\xe2\xb3\x5b\x68\x8b\xd9\x4d\xb3\xf5\x5c\
+\x83\xdb\xa5\x08\xfc\x55\x6d\x7f\xe7\x5d\x24\x1b\x3d\x01\x48\x41\
+\x22\xd8\x06\x77\x17\xe0\xf6\x56\xe0\x4b\x7e\xb4\x5f\x14\x33\x81\
+\x81\x52\x7c\xf6\xce\x0c\x24\xe3\xfb\x25\xc5\x84\x10\xe9\xc9\x4e\
+\x92\x90\x9b\x1e\x6c\x0b\xc0\xcb\x19\x8e\x65\xae\x80\x3a\xc1\x08\
+\x27\x00\x7f\xde\xbf\x18\xc4\x23\x27\xdd\x44\x10\x5b\x8e\xc3\x8f\
+\x87\xfb\x58\x6e\xbe\xe3\x17\xb0\xdd\xdc\x7d\x9b\x9b\x0b\xea\xd4\
+\xdd\xea\xb4\x60\x4c\xda\xd7\xcb\xfb\xe5\x66\xdc\x3e\xba\x21\xa4\
+\xf1\xe6\x21\xe3\x04\xe0\x93\xe7\xf9\xa4\x23\x17\x03\x1f\x57\xd0\
+\xd9\xf6\xf1\x9b\x0b\xce\xde\x37\xdc\xd7\xf4\x3a\xf5\xdc\x15\x8c\
+\x81\x03\x1e\xdc\x49\x47\x19\xb5\x39\xf6\x7f\xc8\x25\x25\x09\x90\
+\xc8\xb1\xff\x62\xca\x2e\xe9\x17\x30\x44\x6a\xb0\xfc\xce\x5d\xda\
+\xcb\x72\x56\xe2\xb5\xfd\x23\x09\x55\x69\x3f\xe8\x49\xfe\x8a\xdb\
+\x7f\x23\xf0\xff\xd0\x89\x8b\x77\xab\x8c\x13\x40\x84\x3c\xcf\xa7\
+\x20\x23\x22\x38\xba\xd3\xf7\xe2\x91\x48\x52\x60\x07\xbe\x35\x03\
+\xf9\x4e\x5d\x4b\x50\xd5\x2c\x42\xc7\x02\x80\x98\xd8\x03\x9c\x0f\
+\xca\x5e\x8e\x0b\x00\xe1\x30\x04\xf0\x0c\x19\x56\xf7\xfe\xf2\x9d\
+\x93\x07\x71\xbb\x12\xb7\xef\x6f\x0c\x61\xbd\xa4\x32\x4e\x00\x2d\
+\x04\x89\xe0\xd5\xb8\xbb\x04\xb7\x43\x3b\x7d\x2f\x91\x12\x35\xb4\
+\x37\xe9\xf7\x61\x12\x06\x60\xdd\xef\x3a\x27\x94\xb9\x44\x1e\xfd\
+\x2f\x21\xe8\x7f\xd7\xe9\x9b\xe9\x66\x19\x27\x00\x4d\x41\x22\x38\
+\x00\x77\xe7\xe0\x46\x09\x45\xc5\x8c\x36\x6c\x87\x24\x01\xbf\x94\
+\xee\x02\x77\x9c\x50\xae\x3e\xe5\xed\x5f\x8d\xc0\x7f\xb2\xd3\x37\
+\xd3\x0b\x32\x4e\x00\x09\x05\x89\x60\x33\xdc\xbd\x4b\x6c\xa9\x26\
+\x27\x1d\x97\xdc\xe5\x59\xe0\x5e\xfd\x6f\x22\xf0\x57\x77\xfa\x66\
+\x7a\x49\xc6\x09\x20\xa5\x20\x11\x90\x3b\x8d\x46\x1b\xbe\x07\x7a\
+\x5d\x2b\xe8\x5d\x21\x87\x1e\x8d\xf3\xb8\x75\x43\x19\x9f\xdf\x6e\
+\x19\x27\x80\x1c\x64\x5c\x2b\x68\xab\xd0\x08\xbd\x9f\xe2\xf6\x15\
+\x04\xfd\xdf\x3b\x7d\x33\xbd\x2e\xe3\x04\x90\xa3\x8c\x6b\x05\x85\
+\xc9\x32\xe0\xa0\xa7\x61\xb9\x77\x6e\xac\x59\x7b\x45\xc8\x38\x01\
+\x14\x24\x62\x18\xf2\xe1\xc0\x89\xe0\x30\xdc\xa6\x76\xfa\x9e\x7a\
+\x4c\x5e\x00\x9e\xb4\x43\xa0\xff\x23\x82\xde\xea\xf4\x0d\x6d\x88\
+\x32\x4e\x00\x6d\x90\xe7\xf9\x3a\x06\xfb\x02\x27\x83\x23\x71\xdb\
+\xa5\xd3\xf7\xd4\xa5\x42\x9e\x7b\x02\xfc\x4f\x10\xf0\x7f\xe9\xf4\
+\xcd\x6c\x0c\x32\x4e\x00\x1d\x10\x31\x65\xd9\x91\x62\xa3\x15\x8e\
+\x26\x66\xab\xb1\x67\x65\x18\xb7\xbf\xe1\x46\xb1\xfa\x9b\x11\xf4\
+\x0f\x76\xfa\x86\x36\x36\x19\x27\x80\x0e\x0b\x92\x01\x8d\x44\xa4\
+\x1c\x83\xd7\xe3\xb6\xbb\xd8\x66\x75\xfa\xbe\x0a\x10\x52\xe1\x69\
+\xd8\xed\x7d\xca\xf6\xd0\xb8\xf7\xbe\xb3\x32\x4e\x00\x5d\x28\x48\
+\x0a\xb3\x71\xb7\x1b\x70\x32\x90\xfb\xad\xa1\xb7\xde\xd7\x52\xf0\
+\x82\xfd\xaf\x08\xf6\x35\x9d\xbe\xa9\x71\xf1\x4a\x2f\xfd\xa0\x36\
+\x6a\x41\x52\x20\x33\x61\x57\x70\x09\x61\x27\xdc\xe6\x00\xd7\x16\
+\xfa\x3a\x70\x4b\x94\x1e\x48\xde\xf9\x17\x95\x8d\x1c\x77\x34\x7d\
+\xf6\xfd\x08\xf6\xe7\x3b\xdd\x66\xe3\xd2\x5a\xc6\x09\x60\x03\x10\
+\x24\x87\x21\xe0\x44\xe0\xdf\x66\xfb\x3e\x13\x51\x90\xca\x6d\x69\
+\xee\xeb\xb8\x2d\x01\x2f\xc8\x25\xd0\x17\x23\xc8\xeb\x9d\x7e\xf6\
+\x71\xc9\x26\xff\x0f\xe9\x4f\xa5\x34\xb1\x29\xa6\xdf\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\xb2\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x2f\x49\x44\
+\x41\x54\x38\x8d\x55\x93\x4d\x4c\x5c\x55\x00\x46\xcf\xbd\xef\x0e\
+\xcc\x00\xaf\x6f\x18\x0b\x45\x40\x52\x69\x8a\x18\x8d\x69\x48\xb1\
+\x5d\x58\x17\x9a\xb2\x64\xa3\x86\xb8\x68\x8a\x61\xe1\x4e\xdd\x98\
+\xb8\x32\x21\x46\xdd\x8e\x98\x8e\x89\xa9\x36\xb1\x0b\x35\x75\xe1\
+\xc2\x18\x7f\x62\x62\x0d\x69\x43\x61\x24\xf5\x8f\xda\x96\x9f\x50\
+\xa0\x98\x02\xed\xbc\x99\xe9\xbc\x77\xdf\x7d\xf7\xba\x29\x09\x3d\
+\xeb\xef\x7c\xbb\x23\x9c\x73\xec\x65\x72\x72\x52\xf9\xbe\x7f\x54\
+\x6b\x3d\xa4\xa4\x3b\x1e\xc7\xb1\x6b\xc4\xa6\xac\xb5\x9e\x8f\xa2\
+\xe8\xca\xd4\xd4\x54\xbc\x77\x2f\x76\x0f\x84\x10\xa2\x54\x2a\x0d\
+\xb4\x65\xd3\xaf\x9e\xea\xcd\x1f\x69\xcd\xe5\x69\xce\x16\xe8\x0b\
+\xd6\x09\x77\x6e\xb1\xba\xb9\xe1\xbe\xfb\x3d\xfa\x7b\x7d\xcb\x9d\
+\x2a\x95\x4a\x57\xdd\x03\x51\xed\xca\xc5\x62\xf1\xd4\x60\x6f\xe6\
+\xec\x01\x59\xc8\xf8\xda\x92\xf1\x2c\xf5\xda\x06\xf5\xc4\xe2\xcb\
+\x3c\x9d\xa4\xe2\xed\xe7\x97\x9f\xfe\xf6\x9f\xfa\x95\x78\x62\xe2\
+\x2d\x21\xc4\x27\xce\x39\xa7\x00\x4a\xa5\xd2\xc0\xe0\x63\xcd\x9f\
+\x75\x6a\xa5\x92\xfa\x16\x77\x8c\x61\x7f\x47\x07\x37\xff\xbd\x0e\
+\x3d\x1e\x07\x3b\x15\xff\xdd\xa9\xd3\xe1\x59\x5e\x39\x94\x64\x4c\
+\xdc\xf8\xa8\x36\x36\x36\x0b\xcc\x7a\x42\x08\x75\xa0\xd0\xfc\x43\
+\x5f\x4b\xd0\xe3\xe9\x18\xa5\x3c\x94\xe7\x61\xad\x65\x65\x79\x89\
+\xf6\x6c\x48\x57\xa0\xe9\x6a\x0d\x11\xae\xc1\xcf\x65\xc3\x89\x81\
+\xaa\x9c\xbf\x9d\x39\x51\xfa\xf4\xfc\x39\x19\x04\xc1\xf0\x13\xbd\
+\xc1\x50\x12\x86\xdc\x5a\x5f\x27\x31\x06\x0b\x24\xc6\x10\x56\x42\
+\xe2\xda\x3d\x30\x55\xb0\x75\xd2\xa4\x41\x3e\x77\x9f\x76\x11\x33\
+\xfa\xcc\xce\xa0\xe7\x79\x23\xca\x18\x33\x2c\x6d\x86\x54\x6a\xfc\
+\x7c\x3b\xc6\x81\xb0\x29\x58\x4b\xbd\xd1\x40\xeb\x18\x52\x01\x2e\
+\x41\xc9\x88\x67\x0f\x47\x54\xa3\x98\xc3\x85\x94\x34\x6d\x19\x92\
+\x4a\x9a\xe3\x51\xa2\x88\x8c\x45\x64\x9a\xd0\xa9\x45\x1b\x8b\x4e\
+\x2d\x5b\x3b\x77\x29\x5d\x58\x60\xe2\xbd\x39\x3e\xfc\x62\x01\x5c\
+\xc4\xdd\x5a\xc4\x3b\x9f\x37\x68\xf3\xea\x34\x2b\x33\xac\xac\x73\
+\x4d\xda\x4a\x56\xd7\x36\x08\x5a\xb2\xec\xcf\x07\x60\x1d\x58\xc7\
+\x0b\x27\x47\x78\xae\xeb\x22\x49\xa2\x49\x8d\x06\x11\xd1\xde\x16\
+\xf3\xc1\x6b\x06\xbf\xd5\x60\x8c\xf1\x54\xb5\xa6\x2f\xa9\x4c\xe3\
+\xa5\x9e\xc7\xfb\x11\x46\xa3\x8d\x01\x27\xc0\x81\x95\x1e\x2a\x93\
+\xd2\x94\x31\x60\x53\x10\x9a\x9b\xb7\x23\x10\x9a\x7b\x4e\x52\xbd\
+\x6f\xe7\x14\x30\x97\xe8\x0a\xc6\x2b\xb0\xb2\xb8\x44\x61\x5f\x1b\
+\x7e\x2e\x0b\x4e\xe0\x90\x58\x0c\x52\xc4\x20\x13\x40\x33\xbb\x18\
+\x61\x5d\x44\xa1\x6b\x1f\xc6\x98\x79\xb5\xbd\xbd\x3d\xf3\xd7\x0d\
+\x73\xbd\xbf\xbf\x30\x10\x0b\x89\xf1\x9a\xd0\x52\xe1\x9c\xc1\x3a\
+\x8b\x95\x06\x84\x66\xa7\x96\x10\xd6\x23\x5e\x7d\xd1\x12\x59\x8f\
+\xd7\xcf\xe5\x57\xa3\x28\xfa\x51\x4e\x4d\x4d\xc5\x2b\x1b\xb5\xb1\
+\xa8\x72\x2d\xed\xee\x1f\x40\xf9\x01\x97\xe7\xff\x60\x61\x79\x95\
+\x58\x2a\x2e\xdf\x68\x60\x65\x42\x79\x29\xe2\xfd\x0b\x35\xf0\x12\
+\xce\x5e\x0c\xd2\xa5\x4d\x71\x7a\x66\x66\xa6\x2a\x01\x8a\xc5\xe2\
+\xd5\x5f\xcb\x5b\x6f\x98\xf0\x4f\x8b\xe7\x28\xf4\xf4\xe1\x77\x3e\
+\x4a\xdd\x0a\xce\x7c\x5f\x65\xad\xa2\x39\x79\xcc\xf1\xf1\x9b\x59\
+\xce\xfc\x52\x48\xbf\x99\xf1\xdf\x9d\x9e\x9e\xfe\xcd\x39\xe7\x1e\
+\x8a\x69\x7c\x7c\xfc\x68\xf7\x23\xde\x97\x47\x9e\xec\x3e\x94\x4a\
+\x9f\x6a\xe2\xf3\xf2\xc1\xf3\x54\x2a\x21\x0b\x6b\x92\xaf\x2f\x05\
+\xab\x8b\x9b\xee\xf4\x03\xd9\x3e\x54\xe3\x2e\xa3\xa3\xa3\x2d\xb9\
+\x5c\x6e\xc4\x18\x33\x94\xcd\xd8\x63\x5a\xc7\x32\xac\xa7\xb3\xc6\
+\x98\xf9\x4a\xa5\xf2\x53\xb9\x5c\x0e\xdd\x1e\xe9\x7f\x4f\xfa\xc8\
+\x4e\x6d\xe5\x60\x83\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
+\x82\
+\x00\x00\x0b\xd7\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x0e\x74\x45\
+\x58\x74\x54\x69\x74\x6c\x65\x00\x43\x6f\x6d\x70\x75\x74\x65\x72\
+\xf8\x18\x12\x76\x00\x00\x00\x17\x74\x45\x58\x74\x41\x75\x74\x68\
+\x6f\x72\x00\x4c\x61\x70\x6f\x20\x43\x61\x6c\x61\x6d\x61\x6e\x64\
+\x72\x65\x69\xdf\x91\x1a\x2a\x00\x00\x0b\x17\x49\x44\x41\x54\x68\
+\xde\xe5\x9a\x6b\x6c\x1c\xd7\x75\xc7\x7f\xe7\xde\x3b\xb3\x0f\x52\
+\x24\x45\x9a\x54\x15\xc3\x7a\x50\xf1\x0b\x6e\xed\x24\xaa\x25\x51\
+\xa9\x95\x5a\x40\x12\xd4\x71\x81\x02\x89\x03\xa3\xaf\x00\x4e\xd0\
+\xda\x75\x61\x03\x82\xd3\xd8\x7d\xa0\x68\xd3\x7e\x28\xea\x18\x0d\
+\x8a\x3e\xd2\xc4\xf5\x97\xc4\x4e\xea\x0f\x8a\x8d\x54\x34\x1a\x19\
+\x74\x5b\xc4\x70\x22\xb7\x31\x1a\xc7\x52\x52\x47\xa1\x64\x5a\x12\
+\xc5\xa7\x44\x72\x97\xbb\x33\xf7\x9e\x7e\xb8\xb3\x4b\x52\x92\xf5\
+\xa2\x10\xc1\xe8\x00\xc3\xb9\x3b\x3b\x3b\x7b\xfe\xf7\x3c\x7e\xe7\
+\xce\xd2\xa9\x2a\xef\xe6\xcd\xbd\xab\xad\xff\x7f\x2f\x60\xf8\xdb\
+\xcf\xff\x2d\x2a\x0f\xa2\x48\xd0\x40\x08\x81\x10\x14\x5d\x3e\xf6\
+\x81\x40\xc0\x7b\x45\x43\x20\xa8\x8f\xe7\x43\x71\x8d\x2a\xde\xfb\
+\xe2\xb5\x12\x34\x10\xbc\x6f\x8f\xe3\x7b\xc5\xf9\x10\xd0\xa0\x78\
+\x1f\x82\x48\xf8\xec\x9f\xfc\xd1\x9f\x3f\x71\xd9\x02\x86\x87\x87\
+\xbb\x30\xfc\xee\x07\x77\xee\x12\x63\x0c\x22\x06\x11\x41\x10\x44\
+\x80\x62\x0c\x8a\x6a\xdc\xa3\xc1\x85\xb0\xe0\xf1\x2d\x11\xc1\xe3\
+\x7d\x3c\xe6\xde\x13\x7c\x4e\xee\x3d\x3e\x2f\x8e\xc5\x6b\x0d\x01\
+\x55\xa5\x5e\xaf\x9b\x91\x97\x46\x3e\x0f\x5c\xbe\x80\x46\xa3\x61\
+\x4a\x55\x1b\x16\x6a\x0d\x6e\xf9\xd8\xe7\x08\xab\xae\x05\x8a\xc6\
+\xc3\x79\xb7\xce\x8a\x63\xef\x17\x3f\x4d\xf0\x6a\x56\x1d\x42\xed\
+\x30\x41\x19\xd8\xb2\x0d\x05\xb4\xb0\x42\x95\x38\xf3\xad\x73\xcb\
+\xc6\x1b\xd7\x77\x83\x82\xf7\x9e\x3c\x28\xcd\xcc\x73\x72\x66\x01\
+\x55\xe8\xa8\xa4\xdc\xb0\xa1\x97\x50\xcc\x48\x0c\xa5\xb8\x67\x59\
+\xc6\xff\x7c\xef\xdf\x49\x92\x84\xa0\x81\x2b\x23\x40\x15\x30\xa8\
+\xd8\x15\x33\xa9\xc5\x48\xcf\x38\xe7\x9c\xc1\x5a\x47\xee\x03\x2a\
+\xa0\xa2\x2c\x34\x32\x02\x16\x15\x58\xd7\xb7\x86\x10\x84\x50\x88\
+\x0d\x45\x18\xc6\xdd\x00\x90\x24\x29\x21\x5c\x11\x01\x31\x96\x61\
+\xe5\x6c\xb7\xc6\x68\xe1\x89\xd6\xfb\x0a\x69\xd9\x90\xfb\xe8\xb9\
+\x3c\x04\x72\xaf\xcc\xd7\x33\x54\x95\x72\xea\x48\x13\xcb\x62\xe6\
+\x41\x21\x14\x9e\x04\x25\xc4\xe4\x05\x20\xbd\x32\x02\x66\xc9\xf3\
+\x0e\x9a\x59\xd6\xfe\x82\x25\x11\xd1\xea\xe5\x86\x6b\x71\x22\x35\
+\x26\x86\x8e\x8f\x02\x16\xea\x4d\x72\x1f\x00\xa5\xb7\xab\x4c\xa3\
+\xe9\xa3\xe7\x74\x29\x1f\x82\x46\x5f\xfa\xdc\x17\x1e\x48\xae\x8c\
+\x07\xbc\xcf\xc9\x9a\x19\xa8\xe2\x7d\xde\x36\x72\xc9\x13\xac\x30\
+\x46\x51\x8c\x51\x9a\x59\x86\xf7\xe0\x15\xe6\x6b\x0d\x34\x28\x69\
+\x6a\xb1\xd6\xd0\xcc\x63\xd9\x6c\xdf\xa3\xf8\x7c\x50\x45\xfd\x15\
+\x14\x30\x0b\xd8\x46\x83\xb9\xf9\xd3\x68\x9e\xe1\xb3\xc5\x22\x94\
+\x96\xbe\x18\xa4\x7d\x7d\x51\x59\x11\x72\x7c\xa6\x84\x00\x8d\x3c\
+\x90\xe7\x39\x22\x96\xae\x6a\x89\x2c\xf7\xa8\x4a\x7b\xc6\x51\xda\
+\xc9\xac\x45\xd2\x03\x38\xe7\xda\xa1\xeb\x56\x11\x41\x2c\x64\xf3\
+\x32\x31\x71\x12\xcd\x17\x09\xcd\xf9\xc2\x50\x83\x08\x98\x08\x83\
+\x25\xeb\x81\xc4\x5a\xb4\xb1\x80\x2f\x92\x33\x6b\x78\x12\x31\xb8\
+\xd4\x52\x4e\x1d\x8a\xd0\xca\xd9\xa0\x10\x7c\x01\x2e\x0d\xf8\xb0\
+\xe4\x01\x6b\x1d\xbe\xe5\x81\xcb\xa5\xa9\x38\xa5\x6c\xca\x8c\x9f\
+\x78\x8b\x2f\x3c\xf2\x91\x4b\xa0\x69\x1d\x0d\x4a\xee\x03\x07\x46\
+\x95\x1f\x8c\x57\xe8\x2c\x57\xc1\x28\x8a\xa5\xed\x38\x0d\x50\x54\
+\xa9\x56\x1e\x15\x29\x80\x35\x86\xe0\xb5\xf0\x40\xb8\x6a\x34\x25\
+\xf3\x23\xfc\xe8\xa4\xc7\x19\x40\x0d\xc6\x18\x54\x04\x89\xbe\xc0\
+\x20\x04\x42\x91\x13\x8a\x4a\xcb\xa3\x82\xb6\x39\x20\x72\xd5\x68\
+\xaa\x5e\x49\x5c\xfc\x80\x18\x83\x88\x25\x88\x80\x2a\x22\x85\xf1\
+\xad\x10\x34\xbe\x1d\x8b\x71\xa2\x0a\x01\x57\x9b\xa6\x06\x10\x11\
+\x8c\x18\xd4\x44\x18\x8a\x01\x54\xe2\x8c\xb7\x66\x44\x4d\xe1\x13\
+\xf0\x21\x3f\x43\xc0\x55\xa4\xa9\x4d\x12\x9c\xb3\x18\x67\xc1\x38\
+\x02\xd2\x06\x56\xdb\x91\x01\x54\x04\x13\xdb\x1f\xf2\x6c\x85\x80\
+\xab\x47\x53\xd5\x40\xe2\x2c\x2e\x49\x70\x2e\x45\xad\xc3\x2b\x31\
+\x17\x8d\x20\x26\x20\xe2\x11\x2f\x80\x87\x10\x27\x38\xcb\xb2\x25\
+\x01\xde\x07\xf2\xa2\x3c\x85\x15\x21\x73\x1e\x9a\x5a\x4b\xfc\x9c\
+\x92\x07\xbd\x6c\x9a\xe6\x79\xce\x8f\x5e\x7d\x11\x93\x54\x91\xa4\
+\x8c\x18\x57\xc4\x4f\x2b\x10\x74\x45\x48\x8b\xc4\xdc\xc8\xf2\x66\
+\x3b\x3c\x5d\x08\x9e\x50\xcc\x4a\x2b\x17\xce\x65\xf8\x72\x9a\x5a\
+\x6b\xc8\x7c\x2c\xab\x3e\x28\xf3\xf5\x26\xaa\x4a\x9a\x5c\x1a\x4d\
+\x8d\xc0\x17\x1f\xfb\x24\xbd\x7d\xbd\x74\x77\xf7\x50\x2a\xa5\x18\
+\x63\xda\x65\xd8\x87\x80\xf7\xa1\xa8\x76\x71\xa7\xc8\xa3\x15\x39\
+\x10\xdf\xa0\xc8\x85\x33\x66\x3c\x32\x34\x52\x54\x04\x44\x71\x89\
+\x6d\xd7\xe1\xa6\xf7\xf8\x10\xdd\xde\xd5\x51\x22\xcb\xc3\x92\xb1\
+\x17\xa0\x29\xaa\x74\xaf\xe9\xa4\xb7\xa7\x8b\xb5\x6b\xbb\xa9\x54\
+\x2a\x18\x13\x4b\xa4\x0f\xb1\x64\xc7\x10\x0e\xfc\xf0\x8d\x83\xbc\
+\xfa\xea\xab\xec\xde\xbd\x9b\x7a\xad\x46\x28\x6e\xea\x62\x7d\xce\
+\x96\x90\x2f\x60\xc4\x42\xcb\x60\x96\x6a\x2f\x28\x69\xe2\xb0\xc6\
+\xc6\xa4\x16\xc5\x37\x72\xd2\x24\xc5\x5a\xa1\x52\x4a\x8b\x49\xd0\
+\x8b\xa2\x69\x9e\xe7\x3c\xf7\xdc\x5e\xaa\x9d\x1d\x54\xaa\x15\x12\
+\xe7\xea\x91\x35\x11\x7a\xcb\xc1\x58\x5b\x58\x48\x2b\x95\xaa\x7c\
+\xe3\xeb\xcf\x04\x44\x02\xca\xdf\x45\x01\x41\xf1\x79\xde\x76\x6b\
+\xec\x57\x0c\x18\x96\xa0\x46\x9c\x79\x10\xca\xa5\x04\x63\x6d\xe1\
+\x98\x80\x18\x4b\xa9\x64\xe8\x28\x27\x88\x8d\x15\x83\xa0\xf1\xfa\
+\x0b\xd0\x54\x15\x6e\x7d\xdf\xad\x0c\xac\xfb\x39\x8e\x1c\x19\x6d\
+\xbc\xf1\x83\x43\x37\x56\xab\x55\x5f\x2e\x97\x03\xd4\xa8\x01\xf1\
+\x0f\x80\xa1\x36\xbf\x08\xd4\x69\x34\x5c\xfe\xf4\xd3\x4f\x4f\x02\
+\x38\x0d\x31\x89\x15\x48\x9c\x5d\xa2\xb1\x91\xb6\x00\xa4\x20\xb3\
+\x0a\x95\x52\x82\xb1\x82\x18\xc8\x9b\x9e\x24\x49\xb1\x02\x69\xea\
+\x08\x41\x31\x26\xe2\x47\x8a\xe2\x79\x3e\x9a\x82\xf2\x0b\xb7\xdc\
+\xc6\x86\x0d\x1b\x19\xe8\xef\xd3\x24\x71\x3b\x1f\xfb\x83\x3f\xfe\
+\x57\x60\x11\xf0\x7a\x11\x0f\xad\x5c\x8c\xb3\x78\x3f\xe7\x1c\xc6\
+\x14\x25\x4c\x4c\x11\xf7\xa6\x68\xcc\xa2\x47\xd2\x34\x45\xd0\x48\
+\x6d\x0b\xa5\x54\x70\xce\x14\xf7\x50\x8c\x17\xd4\x28\xa8\x5c\x90\
+\xa6\xde\x7b\xde\x7c\xf3\x27\x2c\xd4\x6a\x74\x74\x76\x94\xdf\xb3\
+\xfe\x3d\x5f\xfb\xd2\x3f\xfd\xfd\x68\xb9\x5c\x3a\xd8\xdd\xdb\xe3\
+\x5e\xf8\xb7\x6f\x75\x18\x63\x12\x11\x6c\x88\x8d\x67\x8e\xea\x42\
+\xd0\xf0\x1f\x9a\x2d\xfe\xc3\x5d\x77\xdd\x73\xc2\x85\x10\x68\xe9\
+\x74\x89\xc3\x14\x06\x8b\x2d\x8e\x62\x30\xa6\x08\x24\x23\xb8\xc4\
+\xb5\xea\x2d\x69\x22\xf1\xb6\x05\x65\x51\xc5\xab\x81\xe0\x11\x23\
+\x17\x45\xd3\xb4\x6c\x11\xe3\x49\x4b\x96\x6d\xdb\xb6\xd9\xe0\x19\
+\x7c\xf2\x9f\xbf\x92\x04\xe5\xfe\x0d\xd7\xae\x9f\xea\xef\xef\xcf\
+\x93\x24\xb5\x31\xea\x8c\x33\xa9\x5d\x5b\xad\x54\xee\xb5\x69\xf5\
+\x69\x60\x77\x14\x50\x14\x6b\x6b\x2c\xc6\x18\xac\x35\x85\xe1\x06\
+\xdb\xf2\x86\x89\x42\xac\x35\x60\x81\x10\x28\x9b\x22\xe4\x8b\xee\
+\xd5\xfb\x80\x18\x45\xbc\x69\x57\x9b\xf3\xd1\x34\xcf\x73\xbe\xf9\
+\xcd\xe7\xf2\x24\x49\xd4\xda\xf8\xdd\x20\x66\x6e\x6e\xee\xc0\xf7\
+\x5e\x39\xf0\xd6\xd0\xd0\xd0\xdc\x67\xee\xff\xd4\x9f\x3a\x9b\xfe\
+\xb2\x20\x15\xd0\xb2\x0a\x3e\x84\x90\x18\x23\xdf\x12\x11\x71\xad\
+\x52\x15\x2b\x83\xc1\x2c\x13\x61\x8d\xc1\x58\x83\x29\x04\x38\x2b\
+\x45\xec\x0a\x56\xa5\x30\x6e\x89\x07\xd6\x04\xf2\xe0\x23\x3d\x5b\
+\x79\x74\x7e\x9a\x36\x7f\x7c\xe8\xcd\xa1\xe5\xdd\xa0\xf7\xde\xcf\
+\xcc\xcc\x9c\x4e\x92\x64\xf1\xb7\x3e\xf5\xeb\x0f\x19\xcc\xfa\x89\
+\x13\xe3\x9f\x9c\x9a\x3a\x7d\x6a\xb2\x36\x59\xef\x72\x89\x5d\xbb\
+\xb6\xbb\xf1\xd2\x4b\x07\x26\xda\x49\x8c\x2a\x8d\x46\x83\xff\x3d\
+\xf0\x02\x2d\xb3\xda\xed\x4b\xbb\x3b\x5a\xb9\xc9\x3b\x9c\x3f\xbb\
+\x41\x7d\x67\x9a\xaa\x92\xde\xbe\x63\xdb\x7f\xad\x5b\x37\x40\xc8\
+\xf3\x46\xa5\xb3\xa3\x34\x3d\x3d\xc3\x35\xbd\xbd\x64\xbe\xb9\xf8\
+\xc6\xa1\x43\xa5\xd9\xa9\x53\xda\xd3\xd7\xfd\xe1\x46\x7d\xb1\x99\
+\x24\xf6\xe5\xef\x7c\xf7\xbf\x3f\x36\x3c\x3c\x9c\x47\xb8\xab\xba\
+\x10\x02\xd5\x6a\xc2\x0b\x5f\x7a\x80\x96\x1b\x63\x28\xc5\xf1\xf2\
+\x5d\xa4\x55\x99\x5a\xa4\x2e\xea\x74\xb1\x1e\x88\x63\x4f\x0b\x8e\
+\x17\x43\xd3\xfe\x6b\x7a\x69\x36\x1a\x6c\xdd\xba\xd5\xfd\xe4\xf0\
+\x9b\x3a\xd0\xd7\x07\x46\xb8\x69\xcb\xcd\xf6\xf0\x4f\x0f\xcb\xe6\
+\x2d\x1b\x65\x72\x72\x8a\xcd\x9b\x37\x95\xc7\x4f\x9c\xdc\x78\xcb\
+\xad\x37\xff\xf6\xbe\x7d\xfb\xbe\xdc\xae\x42\x79\x96\x1b\x55\x65\
+\xa0\xaf\x37\x86\x8b\x11\xac\x58\xc4\x5a\x8c\x48\xdb\xf0\x73\x0b\
+\x68\x2d\x72\x96\x56\x70\xf1\xb5\x8f\x0b\x9c\x0b\xd0\x54\x55\x31\
+\x22\x6c\xda\xb4\x91\xda\xc2\xbc\x7d\xff\x6d\xef\xe3\xc8\xd1\xa3\
+\x0c\xac\x1b\xa0\x52\xa9\x24\x1b\x36\x5c\xc7\xdc\xa9\xd3\x0c\x6e\
+\xde\xc4\xd4\xd4\x94\xdc\x71\xc7\x1d\x95\xe7\x9f\x7f\xee\xcf\xf6\
+\xec\xd9\xf3\xd5\x27\x9e\x78\xa2\x5e\xac\x89\xe5\xf9\xfd\x2f\xbe\
+\x78\x37\x68\x60\x05\x05\x97\x1b\x17\xb1\x1a\x08\xe4\x3e\xaf\xc4\
+\xf6\x20\x14\xcb\xc4\xd6\x32\x53\xe3\xac\x87\x62\xf9\x78\x0e\x9a\
+\xd6\x6b\x35\x2a\x95\x2a\x5f\x7f\xe6\x19\xc4\x08\x79\xee\x99\x9e\
+\x9e\x66\x61\xa1\xc6\xce\xa1\x9d\xcc\xcd\x2f\x70\xfc\xd8\x31\x66\
+\x66\xa6\xb9\xf1\x86\x9b\x98\x18\x9f\xe0\xf8\xf1\xe3\x8c\x8e\x8e\
+\x12\x54\xf9\xe8\x47\xef\xba\x76\x68\xe7\x07\x5f\x1f\x19\xd9\xff\
+\x10\xf0\x57\x00\xee\xf7\x1f\x7c\xf8\x13\x8f\x3f\xfe\xf8\x80\x6a\
+\x51\x1e\x5a\x04\x5c\x46\xc1\x66\xb3\x29\x79\x7e\x5a\x4e\x2d\x66\
+\x1d\x9b\xae\xdb\x70\x70\xe7\x8e\x21\x5b\xab\xd7\xda\x59\x20\x22\
+\x24\x69\x99\xef\xbe\xf2\x0a\x03\x7d\xeb\xe8\xea\xea\xa6\xab\x6b\
+\x0d\xe5\x72\x19\x97\x26\x18\x0a\xcf\x89\x81\x62\xc1\x6f\xad\xa3\
+\xa7\xa7\x87\x72\xb9\x4c\xb5\x5a\xa5\x54\x2a\x15\x05\x43\x96\xad\
+\xea\x74\x59\x5b\x1f\x13\x6f\x70\xf3\xe0\xfa\xe1\xe1\x7d\xf7\xb7\
+\x05\x00\x3c\xf2\xc8\x23\x27\xdf\x29\x07\x25\x36\x44\x0e\xa8\x7c\
+\xe1\x6f\xfe\xfa\x57\x87\xb6\xef\x68\xde\xfe\x8b\xdb\x2b\x8d\x46\
+\x63\xc5\x75\xa5\x52\x99\x90\x67\x7c\xe3\x5f\x9e\x5d\x9c\x9c\x9c\
+\xf4\xa5\x52\x29\x24\x2e\x29\x4a\x23\x88\xc8\x39\xf2\x7d\xa9\x49\
+\x14\x23\x17\xb5\x68\x6d\x36\x9a\xa5\x3c\xcb\x47\xda\x39\x20\x2b\
+\x3a\xb6\xb3\x36\x03\xa4\x3b\x76\xed\xd8\x78\xef\xc7\x3f\xfe\xf9\
+\xbe\xfe\x6b\xee\x36\xe2\x4a\xdf\x7f\xed\x35\x9a\xcd\xe6\x19\x02\
+\x4a\x54\xab\x5d\x7c\x68\xd7\xae\xa4\xb6\xb0\x70\x3c\x49\x92\x1f\
+\x77\xad\xed\x71\xe5\x52\x5a\x35\xc6\x24\xc6\x88\xd1\x82\xa6\xea\
+\xb5\xd6\xc8\x1a\x2f\xbf\x7d\xe4\xe8\x93\xfb\xf7\xff\xe7\xc9\x4b\
+\x5d\x79\xef\xdd\xbb\x77\xb6\x2d\x00\x30\xf7\xdc\x73\x8f\x1d\x1b\
+\x1b\xb3\x2b\x94\x36\x9b\x32\x31\x31\x51\x9a\x9d\x9d\xed\x1b\xba\
+\x7d\xfb\xbe\xbb\xef\xfe\xb5\x8d\x2a\x41\x4e\x9d\x9a\x01\x02\x65\
+\x77\xe6\x23\x25\x4f\xc9\x5a\x86\x86\x86\x6c\xb3\x91\x6f\xfa\xca\
+\x93\x5f\xb6\x17\xa2\xe9\x7b\x6f\xbc\xf1\x1f\x1f\x7c\x70\xcf\xee\
+\xd5\x3c\x46\x70\x5b\xb7\x6e\x35\x87\x0f\x1f\x76\xe5\x72\xd9\x9e\
+\x39\xfb\x22\x52\x36\xc6\x74\x1e\x3a\x78\xe8\xf5\xc7\xfe\xf0\xd1\
+\xeb\x8a\x5e\xf3\xc2\xcf\x26\x94\x4b\xa2\xa9\xae\xe2\x97\x46\x37\
+\x38\x38\x18\xc6\xc6\xc6\x74\x76\x76\x76\xc5\x4d\xf2\x3c\x0f\xde\
+\xfb\x86\x73\xee\xe4\xcb\xdf\x79\xe5\xa1\xde\xde\xde\x35\xd6\x5a\
+\x7b\x9e\x70\xbb\x6c\x9a\xae\xca\x03\xcf\x3e\xfb\x6c\x00\x9a\x8f\
+\x3e\xfa\xe8\x66\xeb\xd8\x3f\x37\x3f\xbf\xbe\xde\x68\xa6\x7d\x7d\
+\xbd\x4c\x4d\x4d\x73\x29\xc7\x46\xbd\xde\x28\x55\x2a\xa5\xa9\xa9\
+\xe9\x4b\xa2\xe9\xaa\x04\x14\x37\xf0\x9f\xfd\xdc\x9e\xbf\xbc\xee\
+\xda\xc1\xd1\xf1\x93\xe3\x1b\x36\xf5\xf4\x30\x39\x39\xc5\xcd\x37\
+\xdd\x70\x49\xc7\x6d\xdb\xb6\xbb\x1f\x1e\x7c\xfd\xfc\x34\x1d\x1f\
+\x3f\x8b\xa6\xab\x12\x00\xf0\xf0\xc3\x0f\xfc\x7c\xb9\xd2\xb9\x7b\
+\xcb\xe0\x96\x6a\xa9\x94\xca\xcc\xf4\x4c\x8b\x7e\x74\xad\xe9\x24\
+\x6b\x36\xcf\x79\xf4\x59\xce\xe0\xe6\x4d\xb4\xae\x9f\x9f\x3b\x6d\
+\x3f\x70\xdb\xfb\xf9\xe9\xe8\x28\xfd\x03\xfd\xe7\xa6\xe9\x2f\xed\
+\x3a\x8b\xa6\xab\x16\xd0\x68\xe6\x7f\xf1\xe9\xcf\xfc\xe6\xd1\x63\
+\xc7\x8f\x7d\xe0\xb5\xef\xbf\x46\x20\x30\x7a\xf4\x48\x5c\x45\x22\
+\x68\xf1\xac\x92\xe2\xe9\xb1\x2c\x6b\xe7\xc6\xc6\xc6\x50\x55\xde\
+\x7a\x6b\x8c\xed\xdb\xb6\x73\x7a\x6e\x9e\x63\x6f\xbf\xcd\xf4\xf4\
+\xd4\x3b\xd0\xf4\x57\xce\xa2\xe9\xaa\x04\xdc\x77\xdf\x7d\xfd\xa5\
+\x4a\x7a\xe7\xf5\xd7\xdf\xd0\x79\xfd\x7b\xaf\x67\xd7\x1d\x1f\x6a\
+\x1b\x18\x17\x62\x4b\x46\xb7\x90\xd1\x3e\xdf\x5a\x19\x2e\xcb\x6b\
+\x45\xe1\xde\xdf\x60\xe9\x39\xe9\xd9\x34\xdd\xb2\x79\xcb\x0a\x9a\
+\xae\x36\x07\xe6\x66\x67\x67\x4e\x3c\xf0\x7b\xbf\xb3\xce\x3a\x27\
+\xfc\x0c\xb6\xac\x99\x95\xb2\x66\x36\x72\x45\x72\xe0\xa9\xa7\x9e\
+\x5a\xbc\xf3\xce\x3b\x6f\xe9\xe9\xe9\xe9\xe4\x67\xb8\x2d\xa7\xe9\
+\xaa\x73\x60\x64\x64\x24\x27\xfe\x6a\xf4\xae\xdb\xde\xf5\xff\xec\
+\xf1\x7f\x9d\x3d\x46\xc4\x32\x49\xfc\x0b\x00\x00\x00\x00\x49\x45\
+\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x07\x51\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x07\x18\x49\x44\x41\x54\x78\x5e\xed\x99\xdf\x4f\x1c\x55\
+\x14\xc7\xbf\x67\x96\x65\x7f\xb0\x94\x2d\xfd\x05\xfe\x68\xb7\xf1\
+\x47\xfb\xa0\x16\x7c\xa8\xd1\x36\x0a\x92\x98\x80\x35\x65\xff\x02\
+\x69\x52\x4d\x8c\x26\x85\x26\x6a\xdf\x4a\x9f\xc4\x98\x08\x8d\x0f\
+\x26\xfe\x08\xcb\x93\x8f\x60\xac\xad\x36\xa9\x50\x2d\xd6\x34\x26\
+\x5d\x6d\xa2\x31\x91\x14\x0a\x4a\x91\xc2\xee\xb2\xbf\x58\x60\xe6\
+\x98\xbb\xb0\xed\xee\x32\xb3\x3b\xb3\xec\xc0\x6a\xb9\x4f\x84\x3d\
+\x77\xee\x39\x9f\xf3\xbd\x67\xce\xbd\x43\xb8\xcf\x07\xdd\xe7\xf1\
+\x63\x13\xc0\xa6\x02\xee\x73\x02\x9b\x5b\xa0\xd4\x04\x70\xaa\xbb\
+\xd7\x03\xc8\xa7\x19\x68\x25\xc0\x2d\xfc\x63\xb0\x5f\x62\xea\x79\
+\xef\xe4\xf1\xbe\x62\xfb\x5b\x52\x0a\x78\xa7\xfb\x33\x11\x74\x6f\
+\x2a\xf0\xec\x60\x19\x18\x48\xc0\x72\xac\xa7\xe3\x58\xb0\x58\x20\
+\x4a\x06\xc0\xdb\xdd\x9f\xd4\x49\x90\x06\xb5\x82\x4f\x0b\xd8\xd7\
+\xd5\x71\xfc\xd8\xff\x0e\xc0\xbb\x1f\x7e\x3a\x40\x44\x47\x53\x81\
+\xed\xac\x76\xc3\xf3\x60\x0d\x5c\x0e\x3b\xbe\xbb\xe6\xcf\x88\x57\
+\x86\x52\xff\x41\xc7\xeb\x99\xff\x2c\x90\x48\x49\x28\xa0\xbd\xbb\
+\xd7\x6d\x87\x1c\x48\x8f\xe1\x89\x47\x3d\x10\x10\xc4\xc8\x06\xc0\
+\x8c\xb3\xef\x9f\x3c\xde\x5e\x60\xcc\x19\xd3\x4a\x02\xc0\xa9\xee\
+\x4f\x1b\x00\x1a\xcc\x0e\x28\x05\x21\x1b\x00\x98\x2f\x77\x9d\x7c\
+\xad\x61\x13\x40\x11\x08\x94\x84\x02\xd4\xb6\x80\x88\x4d\x4b\x01\
+\xff\xd9\x2d\xd0\x7f\x69\xb8\x8d\x80\xa3\x20\xb4\x66\x25\xcf\x3f\
+\x39\x3d\xbb\x30\x31\x75\xe7\xa0\x2c\xcb\x77\x7f\xd2\xdc\x02\xb0\
+\xec\xed\xea\x38\x36\x5a\x04\x01\xac\xcf\x61\xa8\x7f\xf0\x6a\x03\
+\xb1\xd2\x0b\xc0\x93\xcb\x69\x59\x96\xf9\xcf\xf1\x49\x0a\x84\xc2\
+\x9a\x66\xc5\xcc\xbe\x58\xc4\xf4\x2d\x90\xcc\x3a\x41\x04\xaf\x7b\
+\x4c\x4e\xcf\x60\xec\xef\x7f\x56\xdb\x33\xfa\xba\x4e\x1e\x6f\xd3\
+\xfd\x20\x1d\x86\xa6\x02\x58\xc9\xfc\xaa\xea\xae\xc3\x2f\x8c\xdf\
+\xfe\x67\xe4\xaf\xa9\x99\x47\xc0\x18\x03\x30\x04\x62\x5f\x57\xc7\
+\x6b\x43\x7a\xe6\x1a\xb1\x31\x0d\x40\xff\xe0\x75\x37\x71\xf4\x26\
+\x40\xcb\x2f\xf3\xb4\xb1\xb0\xb8\x88\x91\x5b\xe3\x18\xb9\x35\x81\
+\x87\x6b\x77\xe1\xc0\xfe\x7d\xaa\x3e\x33\x49\x7b\xbd\x8d\xcf\x16\
+\x65\xaf\x6b\x41\x31\x0f\xc0\xa5\xe1\x4e\x22\x9c\x4e\x5f\x38\x12\
+\x8b\xe1\xe7\x1b\xbf\x61\x7c\xf2\xf6\xdd\x7f\x3f\xb5\xff\x31\x6d\
+\x00\xe0\x3e\xef\x8b\x87\x8b\x2a\xf9\x6c\x10\x66\x02\x18\x25\xc2\
+\x9e\xf4\x05\xa7\xa6\xef\xe0\xe2\xf0\x4f\xc9\xac\xcf\x06\xe7\x10\
+\x8d\xc7\x91\x0b\x80\x98\xcb\xe4\xdc\xea\x6d\xac\x2f\xda\xe1\x67\
+\x5d\x00\xf4\x0f\x5e\xf5\x10\x2b\x37\xd5\xa4\x2f\xfe\x57\x6e\xb5\
+\xe2\xe2\x0f\x57\x31\x35\x33\xa3\x03\x80\xd4\xe8\x6d\x7c\xb6\xe8\
+\x7b\x3f\xe5\x9b\x29\x0a\xd0\x53\xfc\x74\x03\x60\xee\xf0\x36\x1d\
+\xee\x31\x52\xd8\x8c\xd8\xfe\x07\x00\xe0\x8c\xb7\xe9\x50\xa7\x91\
+\xa0\x8c\xd8\x96\x3e\x00\x22\xaf\xb7\xf1\xb9\x01\x23\x41\x19\xb1\
+\x35\x09\x80\x78\x05\xc6\x32\x8e\xb7\xd9\x4e\xe9\xde\x02\x84\x7a\
+\x6f\xe3\xa1\xa2\x9c\xfd\xd5\xc0\x98\x02\x40\x2c\xd4\xff\xdd\xf0\
+\x40\xb2\xef\xd7\x18\x7a\x00\x30\x63\xcc\xdb\x74\x28\x67\xfb\x6c\
+\x24\xdb\xeb\x0b\x60\xb9\xff\xd7\xec\x02\xf5\x01\x30\xb7\x00\x0a\
+\x20\xa6\x29\x20\x9f\x0a\x46\xc6\xc6\x11\x89\xc7\x50\xb3\x6d\x1b\
+\x76\xed\xd8\xbe\x2a\x39\x0c\xfe\xc5\xfb\xe2\xe1\xba\xb5\x66\x38\
+\xdf\x7c\x73\x01\x0c\x5e\x77\x83\xa3\x43\x04\x3a\x90\xcf\x91\xf4\
+\xdf\x99\x11\x82\x84\x06\x33\xf7\xbe\xa9\x7d\x40\x7a\x30\xe2\x4c\
+\x60\x04\x82\xc8\x3c\x88\xda\xd6\x23\x78\xd3\xb7\x40\x06\x88\x4b\
+\x57\xda\x01\x12\xe7\x83\x2a\x35\x35\x24\xb3\x0e\xf4\x40\x72\xf6\
+\x98\xd9\xfa\xae\x4b\x2b\x9c\x4b\xee\xfd\x83\x3f\xb6\x42\x61\xb1\
+\xb7\xdd\x20\x76\x83\x69\x14\x12\xf9\xcd\x7c\xd7\xe7\xf2\xc7\xd4\
+\x1a\x60\x64\xdf\x6f\x94\xad\x61\x00\x7c\xb1\xa5\x0e\x8a\xf4\x02\
+\x58\x64\x91\xc4\x3b\x3a\xf3\x7a\x9a\x59\x34\x2d\x43\x20\xf9\x2c\
+\x35\x7f\x63\xea\x59\xbe\x18\xd0\x74\x01\xe0\x6f\x5a\x8e\x82\x49\
+\x5c\x64\x8a\xfb\x7b\x23\x8d\xc9\x10\xc0\x3e\x6a\xfe\xba\xe8\x1f\
+\x35\x8b\x11\x7c\xce\x22\x98\xcc\xb4\x4c\x27\x92\x81\xd3\xf2\x57\
+\xda\x82\x07\x23\x28\x40\x18\x55\x45\xa0\xbb\xce\x3d\x5f\x8e\x13\
+\x00\xc4\xa5\x88\x87\x19\x41\x89\xd0\x59\xf3\xa6\xff\xac\xf0\x65\
+\xf2\xa3\xba\x06\x48\x78\x01\x8c\xd1\xda\xb7\xfc\x05\x41\x5e\xa5\
+\x00\xbe\x70\xa4\x01\xe0\x5e\x83\x99\x36\xc2\x26\xaf\x2a\x26\x3f\
+\xae\xf3\xb0\xbc\x1c\x38\xa9\xc0\x67\xf0\xe7\x04\x6a\x4a\xbf\x65\
+\x66\xc0\x4f\x12\xbc\xb5\x6f\xf8\x0d\x6d\xbb\xd5\x00\x92\x99\x97\
+\xae\x1b\x89\xa8\x20\x5b\x15\x55\x88\xc0\xa1\x24\xaf\xd1\x56\x5f\
+\x83\x71\xfe\xbe\x55\xb2\x58\xbe\xd8\xb9\xa7\xe6\x5b\x30\xda\x41\
+\x54\x07\xc6\x00\xca\x94\x33\xf4\xd2\x79\xcd\xc3\x94\x6a\x0d\xe0\
+\x0b\x47\xc4\x04\x43\xdd\x5b\x41\x10\x56\x26\xb1\xac\xf8\xc3\xc1\
+\xb0\x32\x1f\x89\x3e\xad\xc8\x4a\xe6\xa3\x52\x81\xe7\x00\x60\x29\
+\xb3\xc0\xb9\xc5\x05\xe7\x96\x0a\x26\x49\xca\x8c\x49\x80\xa6\xa5\
+\x7a\xad\x82\xac\x01\xa0\xa5\x0d\x90\x0c\xdd\xe5\xaf\x05\x40\x6a\
+\xae\x08\x3e\x11\x8b\x23\x12\x98\x83\xbc\x74\xef\x0b\x91\xd6\xb3\
+\xcb\x1d\x36\x38\x5c\x4e\x38\x2a\x2b\x72\x2f\xcf\x38\x4b\x2d\xe7\
+\x54\xbf\x26\xab\x03\x18\x6c\x75\x63\x7e\x71\x14\x20\xd5\xae\x2d\
+\x5f\xb0\xac\xf0\xef\x24\xe1\x81\x42\xe7\x8b\xe7\x47\x83\x61\x84\
+\x67\x45\x73\xb8\x7a\x38\x2a\x9d\xc9\x8c\x5b\x6d\xe5\xf9\x5c\x59\
+\xfe\x9d\x71\x99\x5a\xce\xa9\x7e\x4d\xd6\x7c\x0d\xf2\x85\x97\x7d\
+\x00\xbd\xaa\x6f\x85\x7b\x56\xf2\xd2\xd2\xb5\xe9\x5b\xb7\x0f\x4a\
+\x16\x49\x76\x6e\xa9\xb8\x5a\x51\x55\x59\x4b\x92\xf4\x88\xd1\xe7\
+\x08\xfb\xd0\xf4\x2c\xe2\xe1\x58\x72\xaa\x64\x91\x56\x64\xee\x4a\
+\xfe\x6d\x70\x9c\xa1\xe6\x73\xaa\xd7\x6a\xda\x00\x0a\x2c\x86\x89\
+\x58\xe2\xfb\xc0\xed\xe9\xe7\xd3\x1d\x2c\xb3\x96\x4d\x54\x6d\xdf\
+\x3a\x62\x75\x94\xd7\x03\xb4\x45\xaf\xf3\x0b\xf1\x04\x66\x27\xa7\
+\x93\xe6\x95\xd5\x6e\x54\xb8\x5d\x7a\xa7\xa6\xd9\x71\x08\x76\xab\
+\x87\x1a\x07\x54\xaf\xd6\x73\x36\x42\x05\x15\x43\x46\x30\x1e\x89\
+\xdd\x8c\xcc\x86\x1e\x96\x65\x79\xd5\x41\xdf\xb5\xb5\xf2\x8a\xc3\
+\xe5\xb2\x5a\xac\x96\x67\xf2\x45\x33\x1f\x8d\x23\x38\x35\x93\x34\
+\xab\xae\xd9\x81\x72\xa7\x2d\xdf\x94\xac\xdf\xb9\x0f\x76\x6b\xbb\
+\x56\xf0\xc2\x38\x0f\x80\xb5\x15\x43\x51\xdd\x23\xc1\x70\x34\x1a\
+\x0a\x1f\xca\xf6\x5c\xa8\xc2\x55\x5d\xf5\xa7\xcd\x61\xdb\x47\x92\
+\x54\xab\x16\x99\xc8\xbe\x50\x81\x18\x3b\x76\xd7\xc0\x52\x56\x96\
+\x1f\x00\xf3\x18\x88\x7c\xb0\x97\xf5\xe4\x0a\x3c\xf5\xa0\xdc\x00\
+\xb4\x8a\x21\xe3\x32\xc0\x1e\x10\x65\x7c\xf9\xd1\xf6\x8e\x43\x89\
+\x58\xc2\x1f\x09\xcc\xd5\x2e\x26\x16\x1e\xcf\xb6\xb3\x57\x38\xef\
+\xd8\x2b\xec\x56\xbb\xcb\xb9\x52\x74\x79\x2e\x12\x98\xfb\x35\x12\
+\x08\x1f\x16\xb6\xe5\x76\xdb\x62\xf5\x03\x3b\xac\x39\xa3\x17\x3e\
+\x91\xe2\xa3\xe6\xf3\xbe\xfc\x94\xee\x59\xe4\x3d\x0b\x64\x16\x43\
+\xee\x83\x85\x7b\x52\x8d\xc5\x4a\xd7\xd8\x66\xa4\x58\xb2\xa2\xfc\
+\x11\x09\x84\xef\xc4\xc2\xd1\x27\x59\x51\x74\xd5\x83\x9a\xbd\x0f\
+\x4e\x80\xe8\x21\xf5\xc0\xb8\x0f\x20\x1f\x35\x9f\x2b\xe8\xeb\x51\
+\x7e\x00\xcb\xc5\xb0\x35\x97\xa4\x58\x28\x25\xbe\x28\xba\x37\xd1\
+\x81\xe9\x56\xc5\xd2\xc2\xd2\x8d\xc8\x6c\xa8\x72\x3e\x36\xaf\xda\
+\x74\x91\x24\xcd\xed\xdc\x5d\x33\x49\x92\x94\xf5\xf9\x98\x43\x00\
+\xf5\x00\x4b\xbe\xb5\x9e\x38\xf3\x02\x30\x22\x27\x61\x5b\x88\x2a\
+\xc0\x3c\x91\x88\x27\x46\x62\xa1\xc8\xdd\x43\x97\xb3\xca\x15\xb4\
+\x39\x6d\xe2\xc8\x7d\xaf\x17\x49\xee\x6f\xee\x84\xbd\x7c\x40\xcf\
+\xfe\xd6\xe3\x7b\xd1\x01\xa4\x16\x2d\x4c\x15\x5a\x2e\xf3\x97\x22\
+\xe3\x85\xca\x3c\x17\x08\xd3\x00\xa4\x2f\x5a\x90\x2a\x20\x64\x8e\
+\x01\x40\xee\x5c\xab\xcc\x37\x1c\x80\x21\x55\x08\x99\x8b\xcb\x51\
+\x87\xd5\x57\x2c\x99\x97\x0c\x80\x0c\x55\x9c\x7f\xa5\x15\xa4\x88\
+\xc2\x99\xea\xd1\x87\xc0\x92\x8f\x5a\xbe\x32\xed\x43\xa8\x1a\x88\
+\x75\xd9\x02\x7a\x8a\xd1\x46\xd9\x6c\x02\xd8\x28\xf2\xa5\xb2\xee\
+\xa6\x02\x4a\x25\x13\x1b\xe5\xc7\xa6\x02\x36\x8a\x7c\xa9\xac\xbb\
+\xa9\x80\x52\xc9\xc4\x46\xf9\x71\xdf\x2b\xe0\x5f\xee\x84\xf8\x5f\
+\x1c\xc0\xfb\x9a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\
+\x00\x00\x08\x76\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x2c\x00\x00\x00\x2c\x08\x06\x00\x00\x00\x1e\x84\x5a\x01\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x03\x11\
+\x01\x11\x2a\x44\xd1\x6f\x8a\x00\x00\x07\xf6\x49\x44\x41\x54\x58\
+\xc3\xed\x99\x6d\x8c\x5d\x45\x19\xc7\x7f\xcf\x9c\x39\xe7\x9e\xfb\
+\xb2\xbb\xb7\x7b\xbb\x94\x2e\x6f\x2a\x21\x15\x4a\x31\x9a\xb4\xe5\
+\x25\x48\x88\xe2\x4b\xc4\x48\xf9\x48\xd4\xe8\x17\xfc\x64\x42\x4c\
+\xea\x56\x24\xe4\x06\x92\x9a\x34\x90\x18\x89\x26\x26\x1a\xad\x92\
+\x68\xfa\x05\x25\x2a\x9f\x6a\x95\x2f\x6a\x68\x03\xd4\xf2\x56\x09\
+\xb4\xd0\x2e\x2d\x77\xb7\xed\xee\xde\x97\x73\xcf\x39\x33\x8f\x1f\
+\xce\xdd\xa5\x8b\x85\xee\x85\xad\x62\xc2\x24\x27\x93\x3b\x77\xe6\
+\xcc\x7f\x9e\xf9\xcf\xf3\xfc\x9f\x39\xc2\xfb\x28\xcd\x66\xd3\xc4\
+\xb1\xbd\x51\x02\xb9\x1d\xd5\x4f\x20\xe6\xaa\x52\x54\x6a\x98\x20\
+\x08\x01\xa7\xde\x9f\x4e\xfa\xc9\x6b\xea\x39\xa0\xc6\x3f\xd9\xef\
+\xe4\x7f\x6c\x36\x9b\xe9\xfb\x99\x53\xde\xcb\xa0\x3d\x7b\xf6\x04\
+\xaf\xbe\x7a\xf8\x5b\x2a\x32\x65\x8c\xb9\x7c\x72\xf2\x12\x26\xd7\
+\x4f\x52\xaf\xaf\xc1\x5a\xbb\xac\xaf\xaa\x32\x37\x77\x86\x93\x6f\
+\x9e\xe4\xd8\xb1\x63\xad\x24\x4d\x7e\x66\xc9\x7e\xb8\x7d\x7b\xf3\
+\xcd\xff\x0a\xe0\x9d\x3b\x77\x4e\x18\xab\x7f\x00\xb6\xac\x6d\xac\
+\x65\xf3\x96\xad\x34\xc6\x1b\x94\x4a\x31\x51\x14\x12\x04\x16\x50\
+\xd4\x2b\xb9\xcb\xc9\xf2\x9c\x7e\x3f\x21\x49\x12\x3a\xed\x0e\x87\
+\x0f\xbf\xc8\xab\x47\x8e\xbc\xa9\xb8\xbb\x77\x6c\xbf\xef\xf7\x17\
+\x14\x70\xb3\xd9\xac\xc5\x95\xe8\xef\xa0\x1b\x37\x5d\xbb\x89\xcd\
+\x5b\xb6\x52\x29\x57\x10\x91\xe2\x41\x50\x14\xd5\xc5\xc7\xe3\xb5\
+\x00\xaf\x28\x79\x9e\x73\xfa\xf4\x69\xa6\xa7\xa7\x79\xfa\xe9\x03\
+\x2e\x4d\xd2\x3b\xa7\xa6\xee\x7d\x7c\x18\x0c\x66\x98\xce\x71\xc5\
+\x7e\x0f\x74\xe3\xa6\x4d\xd7\x71\xd3\x4d\x37\x53\x29\x57\x60\x00\
+\xd4\x3b\x4f\xbb\xd3\xa6\xd5\x6a\x71\xf4\xe8\x51\x5e\x7b\xfd\x35\
+\x66\x67\x67\xc9\xd2\x74\x69\x11\xc6\x08\xe3\xe3\x6b\xb8\xe2\xf2\
+\xcb\xd9\xba\xe5\x86\x20\x2c\x85\x7b\x76\xed\x7a\xf0\xca\x61\x30\
+\xd8\x95\x76\x7c\xe8\xa1\xe6\xda\xcc\xf1\x9d\x6a\xb5\xc2\xf5\x5b\
+\x6f\x28\xb6\x5e\x84\xa4\xd7\x63\x66\x66\x86\x7f\x1e\x3a\xc8\xf1\
+\xe3\xc7\x49\x92\xde\xf2\x09\xac\x65\xc3\x86\x0d\x5c\x73\xf5\x46\
+\xc6\xea\x63\xa8\x2a\xd5\x5a\x95\x89\x89\x09\xae\xb9\x7a\x63\xe9\
+\xd9\x67\x9f\x79\x10\xb8\x6b\xa5\x38\x82\x95\x76\xbc\xf5\xb3\xb7\
+\xdd\x25\x22\x77\x6e\xd9\xba\x95\x4b\x26\x2f\x43\x44\x58\x58\x98\
+\xe7\xd8\xb1\x63\xfc\xf5\xc9\x7d\x9c\x3c\x79\xb2\x95\xe5\xd9\x3e\
+\x83\xfc\x05\x74\x2f\xc8\x7e\x84\x69\xef\xbd\x6d\xb5\x5a\x8d\x17\
+\x5e\x78\x9e\x7a\xbd\x4e\xbd\x5e\x47\x55\x89\x4a\x11\xd6\x86\x1c\
+\x9f\x3e\xbe\xf1\xd3\x37\xdf\xf2\xe8\xde\xbd\x7b\x4f\xaf\xaa\x85\
+\x45\xf8\x22\x0a\x57\x5c\xf6\x11\x44\x8a\xd3\x3f\x3b\x3b\xcb\xa1\
+\x43\x07\x7d\xa7\xd3\xb9\x27\xe9\x66\x3f\x6e\x36\x9b\xfe\xed\xe3\
+\x54\x55\x76\xed\xfa\xc1\xd7\x15\xfd\xe9\xbe\x7d\x7f\x2e\xdd\xb1\
+\x6d\x1b\xf5\xb1\x3a\x22\x42\x1c\xc7\x4c\xae\x9f\x34\x2f\x77\x5f\
+\xba\x1d\xf8\xd1\xea\x72\x58\xd9\x28\x22\xd4\x6a\x23\x88\x08\x49\
+\x92\xe0\xbd\xe7\xd4\xa9\x99\xbd\x3b\xbe\xfb\xfd\x47\xce\x05\xb6\
+\x58\xa8\xe8\xd4\xd4\xbd\xbb\x81\xbb\x55\x95\xfd\xfb\x9f\x5a\xfa\
+\x2f\x2a\x45\xac\x5f\xbf\x1e\xf5\xf2\xb9\x0b\x71\xe8\x1a\x71\x1c\
+\x63\x6d\x80\x88\xa0\xea\x71\xce\xd1\x4f\xb3\xd6\x4a\x06\x4f\x6d\
+\xbf\xf7\xd7\x28\x87\xdf\x98\x7e\x03\xe7\xdc\x80\xdf\x01\xb5\xea\
+\x08\x62\xe4\xaa\x0b\x02\x58\xc4\x60\x03\x5b\x78\x43\x65\x69\xe2\
+\x95\x51\x4a\x54\x8d\xfe\x23\xcf\x73\xb2\x34\x2b\xda\x8c\x01\x81\
+\x52\xa9\xb4\xf6\x42\x00\x96\x20\x30\x20\x32\x60\x88\x0e\x05\x18\
+\x40\x94\xe3\x00\x79\x5e\x8c\x33\x8b\xef\xf2\x3a\xbe\xea\x6e\x4d\
+\xe1\xa8\x73\xee\x8a\x53\xb3\x33\x04\x81\x65\x7e\x7e\x0e\xe7\x72\
+\x54\x79\x66\xe8\x68\x65\x96\xc7\x2b\xe7\xdd\xea\x07\x0e\xa3\xba\
+\xab\xdb\xed\xf2\xd2\xe1\xc3\xb4\x66\x5a\x24\xfd\x3e\x47\x8f\x1c\
+\x79\xdd\x9a\x6c\xf7\xca\x2d\x2c\xf3\xd6\x5a\x54\x75\x59\x7b\x9e\
+\xe7\xab\x0f\x78\x6a\xea\xbe\x9f\xe0\xdd\xb5\xfb\x0f\x3c\xf5\xe0\
+\xa1\x43\x07\x1f\x7d\xfe\xb9\x43\x77\xcf\xcf\x77\xae\x1c\x46\xc4\
+\xa8\x88\x06\x26\x28\xf6\x6b\x50\xbc\xf7\x43\xed\x8e\x1d\xa6\xf3\
+\x8e\x1d\xf7\x3f\x07\xdc\xff\x5e\xa5\x61\xbf\x9f\x94\xca\xe5\x78\
+\x59\xdb\x59\x80\x47\x80\x85\x55\x05\xbc\x58\x76\xef\xdc\xd6\x20\
+\xa5\xa1\x46\x47\x00\x8c\x71\x65\x55\x13\x17\x6a\x4a\x47\xf1\x45\
+\x04\x55\xb4\x8d\x31\x99\x11\xba\x27\x66\xfa\xda\xcd\xba\xeb\x6d\
+\xb0\xf6\x9d\xe8\x50\x01\x52\xa0\x3f\x34\xe0\x5f\x34\xef\xa8\x1b\
+\xf1\x5f\x01\x3e\x29\x22\x63\x88\x5c\x24\xc2\xc5\x82\x59\xa7\xea\
+\x26\x34\xf7\x11\x46\xdf\x92\x7a\xbe\xd0\x69\x6f\xd7\x80\x82\x80\
+\x2a\xaa\xb0\x6e\x3c\xe2\x14\xf3\x85\x2b\x3b\x87\x85\x1f\x99\xfa\
+\xc2\xcf\xc7\x6a\xd1\xb4\x28\x0b\x5e\x39\x18\x68\xf6\xd8\x57\x9b\
+\x4f\xcc\x9f\x17\xf0\xa3\x0f\x7c\x79\xb3\x17\x7d\x02\xa5\x01\x10\
+\x58\x4b\xb9\x56\x27\xae\x8e\x52\x8a\x6b\x44\x51\x09\x6b\x03\x82\
+\x30\x42\x02\x8b\x88\x21\x30\x06\x8c\x41\xc4\x2c\x73\x2b\xde\xe7\
+\x78\xef\x50\xef\x71\x2e\xe3\xe8\x8c\xe3\x4c\xbe\x5c\xbe\x2c\xba\
+\xc6\x89\xf1\xfa\x97\xf2\x34\xc1\xe3\x11\x01\x6f\xa2\x87\x7f\xf9\
+\xc0\x1d\x9f\xff\xc6\xfd\xbf\x3b\xf0\xae\x80\x3d\xf2\x30\xaa\x8d\
+\x8d\x9f\xba\x1e\x5b\x19\x07\xb1\x88\x04\xf4\x52\x4f\x2f\xe9\xd3\
+\x4b\x7a\xf8\x7e\x4e\xe8\x3c\x95\xb2\x50\x8e\xa3\x77\x54\xda\x26\
+\x08\x31\x41\x08\x40\x28\x10\x2e\xf4\xb0\xd8\xb3\xcf\x1c\xce\x15\
+\x94\xf8\xf8\xe6\xcf\x0c\x36\xc6\xd3\xef\x9c\xe2\xc5\xa7\xff\xd6\
+\x30\xf8\x87\x80\x5b\xcf\x47\x89\x1b\x46\x46\xc7\x88\xaa\x13\x38\
+\x20\xcb\x1c\xed\x6e\xc2\x5c\x3b\x61\x61\xa1\x43\xa7\xd3\x06\x75\
+\x54\xca\x11\x23\xb5\x2a\x6b\x46\xab\xd4\xc7\x6a\xff\xe1\x5b\xcf\
+\x95\x1b\x8c\x55\x42\x5a\x9d\x9c\x6e\xb7\x37\x08\x1e\x8a\x31\x86\
+\x72\x29\x58\xa2\x8a\x60\xa8\x8c\xad\xa3\x36\xb6\x86\xf6\xdc\xe9\
+\x1b\x57\xc2\x61\x1b\x58\x03\x41\x48\x6d\x74\x82\x24\x49\x49\xe9\
+\x11\xfa\x0e\x26\x15\x34\x55\x0c\x4a\x58\xae\x50\xaa\x8d\x50\x1a\
+\x1d\xa5\xbc\x66\x0c\xbb\xc4\xcd\x41\xf4\x52\xbf\x9c\x22\x40\xb5\
+\x0e\x73\xfd\x69\xda\xed\x05\xaa\xd5\x1a\xaa\xca\x2b\xaf\xbc\xcc\
+\x75\x1b\x2e\xa3\xb6\xe6\xad\x60\xe7\xf3\x8c\x20\x08\x00\xa2\x15\
+\x7b\x09\x55\x8f\x0d\xcb\x5c\x3c\xf1\x51\x1a\x99\x67\x6e\xa1\x43\
+\xb7\xd7\x23\xed\x27\x80\x12\x45\x21\xd5\x6a\x85\xfa\xd8\xe8\xe2\
+\xcb\x8b\x43\x76\x9e\x72\xcb\xba\x8f\xd1\x4f\x33\x16\x3a\x7d\xb2\
+\x3c\xe5\xb6\x9b\x37\x13\x45\xe1\xd2\x5a\xb3\x7e\x97\xb9\x99\x23\
+\xc3\xbb\x35\x31\x86\xf6\xe9\xd7\xe9\xce\x9f\xc0\x86\x65\xc2\x30\
+\xa6\x1e\x0b\x3e\xcc\x51\xf5\x20\x39\x26\x4f\xe9\x9c\x9a\x5b\xd2\
+\x17\x62\xec\x39\x21\xab\x3a\xde\x0a\x6e\x8a\xaa\xc3\x78\x4f\xe4\
+\x1d\xf3\xad\x37\xf0\xea\x50\xe7\x06\x1e\x43\xdf\x9b\x1f\x8e\xe3\
+\x98\x34\xcd\x11\x0a\x45\xa5\xea\x8a\x4c\x38\xef\xe3\xb2\x7e\x01\
+\xfa\x5c\x5c\x95\x73\xb5\xca\x79\x52\x60\x39\x4b\x63\x08\x51\x18\
+\x0c\x0f\xf8\xd2\x4b\xd6\x71\xe6\xcc\x42\xb1\x6a\x09\x41\x2c\x5e\
+\x95\x40\x3c\x99\x7a\xbc\xcf\x0b\xab\xa9\xe2\xd1\xc1\xa4\x83\x7a\
+\xd1\xe2\x22\xa8\x73\x20\x7e\x99\x85\x7d\x9e\xa3\xea\x06\x7d\x8a\
+\xe4\x34\x30\x16\x1b\x06\x44\xa5\x98\xd1\xd1\x91\xe1\x01\x87\xd6\
+\x32\x5e\x1f\xc1\x67\x09\x5e\x42\x14\x8b\x7a\xc8\x53\x43\x1e\x5a\
+\xf2\xac\x4f\x9e\x26\xe4\x59\x3a\xa8\xfb\xe4\x59\x42\x96\x16\x75\
+\x9e\xa7\x38\xe7\x8a\xc7\xe7\x83\xda\xe1\x5c\x8e\xf3\x0e\x1b\x46\
+\x94\x2b\x55\xe2\x4a\x8d\x72\xa5\x8a\xad\xd4\x88\x4a\x55\x2a\x71\
+\x0d\x6b\x83\xd5\x49\xf3\x3f\x08\xe5\x43\xc0\x1f\x02\x1e\x0e\xb0\
+\xfe\x3f\x01\xd6\x0f\x29\x71\xe1\x01\xeb\x87\x1c\x7e\xdf\xe5\xbc\
+\x39\x9d\xa2\xa0\x1e\xf0\xa8\xca\xff\x7c\x21\xf6\xdd\xad\x3b\x00\
+\xab\xae\xb8\x51\xf7\x82\x7a\xf7\xc1\xb6\x30\xea\xcf\x02\x3b\x30\
+\xb6\xea\x07\x90\xc3\xaa\x20\x06\x31\xe1\xdb\xba\xad\x32\x58\x11\
+\x44\x8a\x04\x56\x4c\xb0\xa4\xf4\x86\xb1\x70\xbb\xdf\x4b\x6a\xde\
+\xf5\x41\x4a\x88\x2d\x63\x0c\xe0\x41\x14\x08\x40\xbc\xa2\x41\x0a\
+\x62\xc1\x14\xd2\x93\x20\x04\x53\xfc\x96\xc0\x22\x79\x8a\x71\x0e\
+\xe3\x1c\x81\x1f\x28\x35\x5f\x88\x74\xe7\x1d\x61\x58\xa2\x54\xa9\
+\x12\xc5\x55\x6c\xa9\x42\x10\x55\x30\x36\x06\x31\xb8\x3c\x25\xe9\
+\x75\x01\xe6\x56\x00\x58\x1f\xef\xf5\x7a\x77\x3d\xfe\x9b\xdd\x5c\
+\x34\x79\x29\x23\x63\x6b\x09\xa3\x12\x61\x58\xc2\x46\x65\xac\x2d\
+\x61\x6d\x5c\x24\x8d\x03\x11\x2f\x8b\x56\x32\x85\x95\xc4\x58\xc4\
+\x78\x8c\x0a\x5e\x41\x50\x44\x0b\xed\x5b\x1c\x5e\x25\xcf\x53\xfa\
+\x3d\xc8\xb3\x94\xde\xc2\x1c\x88\x01\x51\x5c\x9a\x72\xaa\x75\x82\
+\x7e\xaf\x0b\xc8\x63\xe7\x05\x9c\xfa\xf8\xdb\x91\x49\xd3\x6e\xa7\
+\xfd\xb5\x23\xff\x7a\x31\x58\xe9\xb6\x06\xa6\xf8\x3e\x67\x02\xb3\
+\xf8\xa9\xa0\x10\xf7\x83\x8b\x92\x21\xef\xd0\x3c\xe8\x6f\xe3\xa8\
+\x74\x0f\xe7\xcd\xc5\x07\xe5\x57\xcd\x6d\x17\x11\xe4\xd7\x8a\xb2\
+\x16\x91\x86\x16\x17\x2b\x0d\x44\x1a\xa8\x6f\x80\x34\x44\x28\xa9\
+\x32\x3a\x18\x52\x03\xc2\x82\x34\x4b\x6d\x6d\x20\x03\xf2\xa5\x7b\
+\x33\xa1\x8d\xd7\x44\x44\x66\x81\x59\xaf\xcc\x62\x98\x45\x75\x16\
+\x35\xb3\x62\x7c\xcb\x39\xf7\xfc\x37\x9b\x7f\x3a\x71\x2e\x5c\xff\
+\x06\x79\xb3\xd7\x3f\xff\x53\x63\x6d\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x09\x53\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
+\x01\x00\x9a\x9c\x18\x00\x00\x01\x36\x69\x43\x43\x50\x50\x68\x6f\
+\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\
+\x6c\x65\x00\x00\x78\xda\xad\x8e\xb1\x4a\xc3\x50\x14\x40\xcf\x8b\
+\xa2\xe2\x50\x2b\x04\x71\x70\x78\x93\x28\x28\xb6\xea\x60\xc6\xa4\
+\x2d\x45\x10\xac\xd5\x21\xc9\xd6\xa4\xa1\x4a\x69\x12\x5e\x5e\xd5\
+\x7e\x84\xa3\x5b\x07\x17\x77\xbf\xc0\xc9\x51\x70\x50\xfc\x02\xff\
+\x40\x71\xea\xe0\x10\x21\x83\x83\x08\x9e\xe9\xdc\xc3\xe5\x72\xc1\
+\xa8\xd8\x75\xa7\x61\x94\x61\x10\x6b\xd5\x6e\x3a\xd2\xf5\x7c\x39\
+\xfb\xc4\x0c\x53\x00\xd0\x09\xb3\xd4\x6e\xb5\x0e\x00\xe2\x24\x8e\
+\xf8\xc1\xe7\x2b\x02\xe0\x79\xd3\xae\x3b\x0d\xfe\xc6\x7c\x98\x2a\
+\x0d\x4c\x80\xed\x6e\x94\x85\x20\x2a\x40\xff\x42\xa7\x1a\xc4\x18\
+\x30\x83\x7e\xaa\x41\xdc\x01\xa6\x3a\x69\xd7\x40\x3c\x00\xa5\x5e\
+\xee\x2f\x40\x29\xc8\xfd\x0d\x28\x29\xd7\xf3\x41\x7c\x00\x66\xcf\
+\xf5\x7c\x30\xe6\x00\x33\xc8\x7d\x05\x30\x75\x74\xa9\x01\x6a\x49\
+\x3a\x52\x67\xbd\x53\x2d\xab\x96\x65\x49\xbb\x9b\x04\x91\x3c\x1e\
+\x65\x3a\x1a\x64\x72\x3f\x0e\x13\x95\x26\xaa\xa3\xa3\x2e\x90\xff\
+\x07\xc0\x62\xbe\xd8\x6e\x3a\x72\xad\x6a\x59\x7b\xeb\xfc\x33\xae\
+\xe7\xcb\xdc\xde\x8f\x10\x80\x58\x7a\x2c\x5a\x41\x38\x54\xe7\xdf\
+\x2a\x8c\x9d\xdf\xe7\xe2\xc6\x78\x19\x0e\x6f\x61\x7a\x52\xb4\xdd\
+\x2b\xb8\xd9\x80\x85\xeb\xa2\xad\x56\xa1\xbc\x05\xf7\xe3\x2f\xc0\
+\xc6\x4f\xfd\xe8\x5a\x4f\x62\x00\x00\x00\x20\x63\x48\x52\x4d\x00\
+\x00\x7a\x25\x00\x00\x80\x83\x00\x00\xf9\xff\x00\x00\x80\xe8\x00\
+\x00\x52\x08\x00\x01\x15\x58\x00\x00\x3a\x97\x00\x00\x17\x6f\xd7\
+\x5a\x1f\x90\x00\x00\x07\x97\x49\x44\x41\x54\x78\xda\x6c\x96\xf9\
+\x6f\x54\xf7\x15\xc5\xcf\xf7\x2d\xf3\xde\xec\x33\x9e\xf1\xcc\x78\
+\xf7\x38\xc6\x36\x8b\x6d\x0c\x76\x4d\x28\xd4\xac\x49\x5b\x08\xa6\
+\x95\xa8\x4b\x91\x42\x1a\x25\x55\x53\x89\x56\xb4\x8d\x1a\xd4\x56\
+\x42\x2a\x55\x22\xa1\x24\x85\x54\xa9\x9a\xb6\x49\x5b\x14\x65\x03\
+\x24\x50\x5a\x50\x0a\x0d\xa1\xa6\x01\x02\xb6\xa1\x66\xb5\x31\x8c\
+\xb7\x99\xb1\x67\x7f\xf3\xde\x9b\x79\xdb\xb7\x3f\x40\x23\x05\xf5\
+\xf3\x07\x1c\xe9\x1e\xdd\x7b\xce\x25\x40\x10\xf7\xa1\xf0\x93\x10\
+\x22\x5c\x08\xc4\x62\xe0\x27\x3e\x2c\x10\x23\x0b\x97\xf8\xaa\xb7\
+\x35\xba\x02\xeb\x58\x06\x75\x55\x82\x5f\x4c\x69\x79\xdc\x96\xa7\
+\x4b\x11\x5b\x68\xaa\xac\x5b\x67\x66\xf2\xf9\xc3\x33\xc5\xec\x68\
+\xc1\x2a\xa3\x44\x74\xc4\x8c\x04\xe6\x69\x01\x0c\x08\x00\x80\xc3\
+\x43\xa8\xa6\x86\x46\x31\xdc\x3c\x50\xfd\xe8\x8b\x6d\x8e\xd0\x3a\
+\x4e\xa5\x57\xac\xbc\x7e\x44\x52\x94\x0b\x79\x33\x95\x32\x61\xa0\
+\x96\x78\x03\x4e\x07\xba\x5d\x1e\x4f\xff\xa2\xfa\xf0\x73\xd7\x0b\
+\x89\x73\xe7\xe2\x13\x7b\x24\xad\x74\xeb\x61\x3d\x16\xb0\x03\x20\
+\x00\x28\x28\x6c\xd8\x1c\x5c\xfe\xd4\xee\xe8\xa6\xc3\x0d\x9a\x7b\
+\x42\x8a\x65\x07\x72\x69\xe9\x35\x00\x9a\xc3\xe5\xe8\x35\x6c\x74\
+\xfd\x9c\x95\xef\xb1\x8b\xa2\x8f\x68\xb8\x9c\x4d\xe5\x5f\x65\xf3\
+\xe6\x21\x51\x60\x57\x56\x05\xbd\xfb\x35\xd3\xc8\x8e\xa9\x89\xa1\
+\x32\x74\x90\x2f\x4e\x60\x01\xa0\xd8\x1e\x59\xfd\xc2\x8f\xaa\xbf\
+\xbe\x37\x15\x9b\x7b\x72\x36\x2b\xbf\xcf\x57\xd8\x9f\x4d\x85\x94\
+\x1f\x37\xfb\x2a\xdb\x3c\xac\x0b\x92\x47\x03\x35\x64\x70\x39\x01\
+\xa9\x5c\x1a\x09\x21\x7d\x4f\x33\x2b\x5f\x77\xce\xf2\xcf\x3a\xec\
+\xec\x7b\xeb\x6a\x9a\x3f\x30\x79\x23\xf2\x4e\xf2\xc2\xaf\x44\x70\
+\x00\x08\xd8\x36\x74\xc1\x07\x17\x5a\x5c\xd1\x9d\x7b\xea\xbe\x79\
+\x30\x31\x1e\xef\xcb\xcb\xf2\xa5\x70\x73\xd5\x09\x77\xc8\xb3\x2b\
+\x23\xe7\x82\xc9\x62\x0a\x95\x9a\x07\x4d\xcf\xf7\xd0\x8e\xe7\x57\
+\x93\x99\x23\xb7\x68\xc3\x8e\xc5\x24\x4f\x8a\x3e\x22\xd1\x8d\xbc\
+\x47\xe8\x67\xb2\xe6\xef\x98\x82\xf1\x56\x77\xdd\x82\x43\x93\x46\
+\x26\x79\x45\x9d\x1c\x32\x60\x82\xed\xc3\x2a\xb4\xdb\xa2\x4d\xd5\
+\x1e\xee\xa3\xf2\xa4\xb2\xdd\x6e\x38\x2f\x0a\xb5\xae\xf3\x2e\xd1\
+\xbe\x9c\xb7\xf1\x68\xfb\x7e\x0f\x54\xbf\x49\x79\x93\x25\xad\x3f\
+\xe8\x41\xfc\xe6\x34\x99\x3c\x3d\x8e\xc7\x0e\x6d\x27\xfa\x8c\x4a\
+\xf3\xff\x4a\x10\xa7\xc7\x19\x31\xdc\xe4\xdb\x46\x46\x7d\x03\x92\
+\x71\xa2\xb1\x2a\xfc\x76\x3a\x5f\x7c\xdf\x6b\xd9\xd3\xdc\x14\x72\
+\x88\x54\x78\x5e\x79\x9c\x5f\x79\x42\xd1\x8b\x87\xd3\x11\xe9\x4c\
+\xf7\xca\xb6\x16\x9b\xca\x00\x0c\xd0\xf4\xbd\xa5\x78\x44\x5c\x4e\
+\x0c\x45\x07\xe7\xe0\x89\x23\xe5\x44\xd7\xae\x55\x30\x4d\x13\x33\
+\xa7\xef\x80\xb7\x38\xe4\xb5\x22\x28\x8b\xb0\xb7\xc1\xfd\xa1\x36\
+\xae\x2e\xb2\x65\xc9\xdb\x8f\x86\x9a\x5e\x3d\x35\x73\x6d\x13\x1b\
+\x15\x5b\x5b\x9e\xab\xdb\xb8\x5f\x89\xe5\x37\xc1\xc5\x3d\x11\x0c\
+\x06\x76\x37\xef\xec\x44\x64\xa0\x05\xd2\xa5\x39\xc4\x7e\x33\x02\
+\xb1\xc1\x43\x5d\xad\x7e\x02\x00\xee\x1a\x1f\x2a\x3a\x23\x84\x61\
+\x18\xf8\x1a\x02\x30\x0b\x3a\x12\x63\xd3\x84\xe7\x78\x10\x91\xab\
+\xa4\x26\x35\x90\x31\xf6\x39\x83\xf6\x57\x86\xe5\xf1\x0f\x99\xf5\
+\x15\xed\xdb\xdd\x9a\x30\xa2\xeb\x74\xb2\xec\xa5\x7b\x72\x0e\x15\
+\x06\x4f\xa1\xdc\xcc\xa1\x74\x4f\x42\x3e\x96\x45\x39\xa3\xdc\x5f\
+\xe1\xb4\x8c\x8f\x76\xbc\x4b\xcf\xee\xfd\x1b\x9d\x9b\x48\xd0\xea\
+\xd5\x51\xb2\xea\xad\x7e\xb2\x64\x7b\x0f\x2d\x29\x25\xc0\xa4\xa0\
+\x15\xdc\x0f\x41\x69\xd1\xab\x8a\x9f\x44\x7d\xe1\x1d\x4c\xbb\xbb\
+\x6e\xc3\x6c\xb6\x70\x38\xcf\x1a\xad\x45\xa2\x2c\x32\x7d\x0c\x02\
+\x4b\x23\x28\xc7\x65\x18\xa9\x12\x3c\x55\x3e\xf8\xbf\x54\x05\x00\
+\xc8\x5c\x9f\xc3\xfc\xc7\x31\x72\xe1\xe0\x69\x72\xea\xc9\xf7\x48\
+\x39\xab\x52\x10\xa0\xf5\x99\x6e\x38\x1c\x0e\x10\x13\x20\x36\x26\
+\x44\x1d\x4c\x1f\xaf\x30\xef\xf6\x78\xda\xd6\x30\x2e\x5e\xac\x57\
+\x65\xf9\x53\xd6\x49\x56\x98\xc4\x42\x45\x7d\x10\x5a\x4c\x86\x7c\
+\x35\x8d\x8c\x5a\x40\xb1\xda\xa0\x62\x9d\x9b\x00\x80\x3a\x96\x47\
+\xd8\x11\x00\xc3\x31\x88\x88\x41\x0a\x42\x08\x00\xf0\x94\x85\x83\
+\x11\xe0\x60\x45\x94\xa9\x06\x59\xd0\x57\x59\x9a\x39\x1c\x15\x2a\
+\x83\x1c\x47\x88\xc0\x5b\xd6\x9c\x5d\xe0\x36\xe4\x74\x09\x81\xd6\
+\x10\x0c\x4d\x47\x41\x2d\x82\xea\x14\xde\x8e\x10\x08\x7b\xff\x68\
+\x9a\x9f\x5e\x8a\x9a\xf5\x4d\x58\x70\x2f\x49\x23\xad\xd5\x10\x7c\
+\x22\xa8\x66\x21\xf9\xc1\x38\x44\x8d\x43\xd1\xa6\xc2\xa0\x26\xec\
+\x82\x50\xab\x17\xcd\x9c\xae\x2b\x3c\xa7\x51\x1d\x20\x80\x69\x9a\
+\x40\x15\x07\x26\x6f\x61\xf8\xf2\x08\x8a\xa3\x33\x30\x59\x03\xed\
+\xdd\xb5\x00\x80\x72\x56\xc5\xed\x3f\x0f\x53\x77\x57\x00\x8d\x6b\
+\x5a\xc9\xff\xa2\x20\x79\x74\x9c\xde\xfe\xd3\x10\xd1\xed\x16\xb2\
+\x9a\x84\x22\x95\x61\x37\x6c\xf0\x53\x3b\xc6\xd4\x69\x30\x19\x43\
+\x2e\x3b\x38\x31\x58\x28\x14\x26\xfd\x15\x3e\xb4\xac\x5a\x88\x4a\
+\x5f\x10\x99\xf9\x34\x44\x41\x80\x73\x49\x00\x00\x90\xbb\x39\x8f\
+\xa1\x97\x3e\x26\xc3\x4f\x9d\x24\x97\x37\x1f\xa3\xc5\xab\x69\x0a\
+\x00\xc1\xfe\x28\xb1\xfa\x9c\x34\x59\x48\xc1\xa2\x16\x78\x86\x47\
+\x15\xf1\x4f\x97\xa0\x7b\xe3\xe5\xac\xce\x8d\x95\xe2\xd3\xcb\xb9\
+\x9a\x15\x76\x8b\x3f\x29\x4e\x53\x24\xdf\xbc\x09\x43\x96\xd0\xfa\
+\x4c\x37\x8d\xf8\xc3\xa8\x68\x0c\x12\x00\x90\xcf\xcd\xc3\x69\xd9\
+\x50\x62\x75\xcc\x9e\xbf\x47\xfc\xef\x84\xa9\xab\x23\x00\xce\xce\
+\x23\xb2\x36\x8a\x4f\x8f\x0d\x42\xb1\x34\xd8\x58\x01\x2e\xc9\x36\
+\xe8\x10\x84\xae\x66\xb1\x61\x9e\xb9\x5b\x4a\x9c\x22\x6e\x6e\x9b\
+\xd3\x14\x6f\xe9\x9a\x71\x7d\xea\x6a\x0c\x4c\xb5\x48\xfb\x7e\xbd\
+\x85\xb4\xfe\xb4\x97\xe0\x81\xff\x5c\xd4\x41\x5b\x9e\xe8\xa4\x42\
+\x93\x9b\x62\x85\x8b\x06\xfb\xa3\x9f\x27\xa6\x30\x47\x50\x6b\x8b\
+\x20\x6c\xab\x84\x55\xc6\x1c\xa7\x32\x67\x82\x15\xde\x81\xb2\x8e\
+\xb3\x64\x93\x6d\x6b\xdb\xc6\xda\xc5\x43\x0d\xd3\xbe\x96\x2c\x2f\
+\xaf\x51\x22\xe6\x21\xa7\xdf\x8d\x65\xbd\x5d\x94\xb4\x3b\xb0\x60\
+\x6b\x27\xc4\xb0\xf3\x73\xcf\x41\x1f\x84\xef\x03\xca\x52\x89\xfe\
+\x7d\xcb\x5f\xc9\xc4\xd8\x04\x74\xc1\x44\x28\xe3\xf9\xa5\x5f\xb2\
+\x1f\x88\xd5\x66\x27\x2f\xc4\x93\x7d\x2c\x4c\x57\xca\xc1\xf1\xbd\
+\x2b\x02\x0b\x1f\xb3\xe6\x95\x5f\x94\xed\xe8\xcb\xe9\x52\x74\x7e\
+\x34\x4e\xe2\x67\xee\x92\xa6\x6d\xed\x48\x8c\xcf\x92\x91\xdf\x0f\
+\xd2\xe8\xba\x36\xa2\xc6\x8b\x28\x15\x14\xca\x94\x28\xe4\x1b\x59\
+\x0c\xed\xfd\x27\x46\x2f\xfe\x87\xc8\x36\x0d\x86\xa2\xdd\x68\x49\
+\x87\x07\x26\x3d\xa9\x03\x37\x8d\xb8\xac\x15\xe9\xcb\x9c\x0e\x16\
+\x97\xf2\xb3\xbb\xbf\x53\xc3\xdd\xf0\x79\xfd\x5b\x8d\x78\xee\x1b\
+\x6c\x3d\x73\x31\xc7\xcb\x2d\xde\x3a\x3f\x82\x8b\x22\x64\x72\xdf\
+\x75\x9a\x9d\x4a\x03\x00\x06\x77\x1d\xa3\xd7\xae\x5c\x23\xab\xaa\
+\x96\xc1\x28\xe8\xc8\x28\x69\x58\x76\x02\x4d\xd5\x92\x1d\xe9\xba\
+\xcd\x39\xbe\xb8\x7a\xc2\x9e\x7a\x7a\x66\xae\xd0\xa9\xc3\x00\x2b\
+\xc1\x8d\xb8\x99\xce\xe6\xf4\xf2\xec\xd7\xa2\xdd\x87\x74\xa9\x74\
+\xd4\x4c\x1b\x2f\x07\xbc\x81\xde\x02\x29\x36\x2a\x37\xf2\xc8\x0f\
+\xc6\x49\x68\x6d\x03\xfc\xd5\x01\xfc\xfb\xe0\x3f\x88\x22\xab\x48\
+\x2b\x39\x78\x05\x17\x32\xa4\x08\xbe\x44\x46\x2a\x13\x8e\xaf\x7a\
+\x6d\x4e\xd6\xac\xe7\xce\x8e\xca\xb1\x9f\xe4\xac\xe2\x49\xf0\x00\
+\xcb\x23\x02\x16\x3c\xae\xab\x53\xc3\x3c\x15\xac\xaf\x34\x2e\x79\
+\x93\x29\x59\x9f\x21\x6d\xec\x31\x55\x23\x6e\x4e\x28\x8f\x34\xf0\
+\xe1\xca\xc4\xe8\x34\x19\x39\x72\x9e\xd4\x93\x08\x54\xa6\x0c\x4a\
+\x80\x8c\x9a\x9f\xf0\xe7\xc5\x17\xb9\xb8\xf9\xdd\x90\xc7\xdf\x59\
+\xd1\x54\x79\x8a\xe1\xf9\x03\x9d\xde\xa6\xfd\xab\x43\x8b\xf1\xe5\
+\xe0\x22\x70\x14\x14\x00\xc0\x80\xc5\xf1\xc4\xf0\x3e\xa7\x25\x26\
+\xb7\xd4\x77\xfc\xc5\x29\x19\xc7\xd9\x04\xb3\x47\x9b\x37\xdf\x48\
+\xca\x89\x2e\xb7\xdd\xb6\x32\xa6\x66\x1a\x22\x2e\x0f\x71\x96\xf8\
+\x19\x8f\x6e\xbf\xa0\x14\xd5\x8b\x36\x0e\x81\x48\x43\xcd\x6f\x35\
+\x1f\x33\xf0\xc7\xd9\x33\x3f\xdf\x10\x68\x7f\xbd\xc5\x5d\x0d\xc3\
+\x32\xbe\x58\xfa\x04\x04\x22\xe1\x71\x29\x75\xef\x0f\x79\x45\x1d\
+\x5c\x5b\xd5\xf6\x52\xe3\x82\xca\x91\x6c\x41\xba\x24\x15\xe4\xa3\
+\x5a\x51\x3b\x57\x6d\x05\x8e\xe9\xb9\x12\x2a\x18\xa7\x3f\xcb\xc8\
+\xcb\x6a\x1b\x43\x2f\xb0\x4e\xa1\xf7\x9a\x1a\xff\xec\xf8\x9d\xcb\
+\xab\xaf\xc8\x77\xaf\x3d\x1e\xec\x84\x45\x2d\x98\xd4\xfa\xff\x5f\
+\x85\xc0\x72\xc8\x94\xe5\x1b\x9f\xc4\x6e\xf5\x8f\x39\xfd\x1d\x41\
+\xaf\xf3\x5b\xfe\xa0\x6d\xa7\x64\x96\x7f\xe6\x64\x6d\xb6\x29\xbd\
+\x00\x1f\xe7\xd1\x66\x4b\x72\x72\x42\xb9\x7b\x76\x32\x95\xdb\x7b\
+\x47\x49\x0e\x4b\x90\x60\x67\x6d\x0f\xcb\xe1\xbf\x03\x00\xde\x6c\
+\xa3\x50\x23\xee\x80\xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
+\x60\x82\
+\x00\x00\x0a\xa6\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x33\x00\x37\x00\x45\x38\x50\
+\x3f\x23\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe0\x08\x1f\x09\x0a\x28\x22\x11\xc6\xd5\x00\x00\x0a\x33\x49\x44\
+\x41\x54\x78\xda\xed\x5d\x6b\x50\x54\xe7\x19\x7e\xbe\xb3\x67\x77\
+\x01\xb1\xae\x0c\x1a\x8b\x7f\x70\xa0\x36\x8c\x4c\x6c\x66\xe2\x2d\
+\x02\x33\xb5\x3a\x23\x36\x4d\xa3\xf5\x47\x8d\x4a\x62\x52\x54\x34\
+\xda\x41\xe3\xea\x20\x4e\x34\x0c\x51\xa8\x5a\x2c\x49\x87\xc6\xb4\
+\x1a\xc5\xd8\x9a\x74\x32\x99\x34\xe2\xad\xa9\x09\x88\x46\xbc\x61\
+\x4a\x54\x84\x70\xc9\x1f\x07\xf0\x92\x21\x20\x7b\x39\xe7\xeb\x8f\
+\x65\xd7\xb3\xe7\x7c\xe7\xec\x9e\xb3\x0b\x05\x39\xef\x8c\x03\xec\
+\xe5\x7c\x67\x9f\xf3\x7e\xcf\xfb\xbc\x97\xb3\x12\x4a\x29\x85\x69\
+\x86\x8d\x33\x21\x30\x01\x34\x01\x34\x01\x1c\xc1\xc6\xab\x3d\x71\
+\xf7\xee\x5d\x9c\xaf\xad\xc5\x37\x0d\x0d\xb8\x7a\xe5\x0a\xda\xdb\
+\xbf\x83\xab\xaf\x0f\x20\x40\x62\xe2\x38\x4c\x9d\x3a\x15\x19\x59\
+\x99\x98\x3b\x6f\x1e\x62\x63\x63\x47\x2c\x80\x44\x2d\x0a\xcf\xfb\
+\xf9\x1c\xb4\xb4\xb6\x00\xf2\x67\x09\x82\x1e\x7b\x32\xed\x49\x6c\
+\x29\xd8\x8a\x8c\xcc\x0c\x73\x0b\x4b\xad\xa3\xa3\xe3\x11\x60\x52\
+\xf0\x64\x76\xf3\xe6\x4d\xe4\xbe\xf2\x0a\x3e\xff\xf7\xe7\x26\x80\
+\x52\xb3\xda\xac\x4a\xe0\xa8\xc4\xfb\x24\x8f\x79\xbc\x1e\x14\x6c\
+\xd9\x8c\x7b\xf7\xee\x99\x00\x06\x19\x95\x81\x26\xdf\xc6\xe4\xd1\
+\xef\x5d\x9d\x5d\x38\x73\xea\xb4\x09\x60\x10\x78\x5a\xcf\x11\xe5\
+\xeb\xce\xd5\xd4\x98\x00\xaa\x87\x1b\xc9\x3f\xb0\x79\xb1\xb1\xb1\
+\x11\x82\x20\x98\x00\x32\x01\xa2\x2a\xde\x27\xf9\xbb\xe7\x87\x1f\
+\xe0\x76\xb9\x4c\x00\x01\xa0\xa7\xa7\x47\x7d\xeb\x52\x0d\x0f\x35\
+\x85\xb4\xcf\xde\x2a\xd9\x85\xf6\xb6\x36\x9c\x3a\x79\x12\xb7\x6e\
+\xde\x52\x72\x23\x85\xf6\x63\x23\x5d\x48\xfb\xcd\xe5\x72\x61\xff\
+\x5f\xde\x45\xd9\xde\xbd\xea\x5b\xbc\xff\x08\x49\x49\x49\x38\x79\
+\xe6\x34\x62\xe3\xe2\xcc\x2d\xec\x37\xbb\xdd\x8e\xd7\xd6\xaf\xc3\
+\x53\x53\x9f\x52\x17\xd5\x23\x70\xeb\xea\x8e\xc2\x49\x13\x27\x8e\
+\x78\xb0\x22\x02\x50\x14\x44\xb6\x4e\x1c\xe1\xf5\x6c\x5e\xd7\xab\
+\xa9\x86\xb4\x31\x3d\x50\x87\xa0\x36\x3d\x2f\x02\x00\xd5\x72\xe3\
+\x11\xca\x8f\xbc\x21\x0f\xa4\x32\xb0\xb4\x04\xb6\xe9\x81\x08\x2f\
+\x78\x50\x73\x0b\xeb\x23\x41\x53\xce\xe8\x04\x30\xc0\x73\x54\x9b\
+\x0f\x4d\x0e\x0c\x81\x20\xab\x2a\x33\x82\xb7\x70\xf8\x00\xca\x53\
+\x66\xa2\xa2\x0d\x4d\x00\x0d\x04\x94\x01\xf4\xc0\x1b\x37\x6e\xe0\
+\x42\x6d\x2d\x40\x88\xea\x85\xfd\xd1\x98\x31\xf8\xcd\xe2\xc5\x43\
+\x1c\x40\x22\x93\x30\x94\x21\x6d\xa2\x6c\xb7\x1b\x1b\xb1\x62\x79\
+\x0e\xba\xba\xba\xd8\x9e\xdf\xbf\x6e\x62\x62\xe2\xff\x0d\x40\xce\
+\x90\xc7\x51\x68\x96\xf6\xa3\x61\x77\xee\xdc\x41\xde\xaa\x55\xe8\
+\xba\xdb\xa5\xbc\x50\xb2\x8b\x38\x2a\x7e\xd4\x30\x4c\xe5\x58\xe0\
+\x46\x09\xc4\xde\x9e\x5e\xac\x5d\x9d\x87\xd6\x96\x56\x36\x4d\x0c\
+\x21\xd1\xce\x1b\x06\x6f\x80\xf8\xcf\xeb\xf5\x62\xed\x9a\x3c\xd4\
+\x5f\xbb\xa6\x7d\xe1\x86\x08\x88\xc6\xaa\x31\xe1\x80\x6a\xd0\x8a\
+\xb6\xef\x40\xf5\x17\x5f\xaa\x73\x1e\x86\x56\xf4\x8f\x7c\x0b\x53\
+\x28\xba\x73\x46\xed\x9d\xf2\xb7\x71\xa4\xb2\x52\x5f\xa4\xa7\xc3\
+\x05\x40\x79\xf4\x8d\xf2\x95\xff\xc7\xd1\xbf\xe3\x8f\x7b\xf7\x04\
+\x47\x7c\xf9\xda\x84\x7d\xb1\xfa\xfa\x5c\xc3\x6c\x0b\xb3\x80\x8c\
+\xc0\x13\x4e\x9f\x3a\x85\x37\xb6\x6d\x63\x17\x2a\x18\x82\x7d\x52\
+\xf2\x24\x24\x8e\x4b\x84\xbf\x1f\xf6\xc4\x13\x13\x86\x99\x90\xa6\
+\x2a\xd1\xd7\x00\x88\xd7\xeb\xaf\x63\x63\x7e\x3e\xbc\x82\x97\x1d\
+\x1c\x68\xb0\x47\x4e\x49\x4f\xc7\xc1\xf7\x0f\x61\x6c\xc2\xd8\x61\
+\x58\x8d\x89\xf2\xb6\x6d\x6b\x6d\xc3\xef\xd7\xad\x43\x6f\x4f\x6f\
+\xe8\x59\x1c\xea\xf3\xbc\x3f\x57\x54\x0c\x19\xf0\x8c\x09\x69\x12\
+\x1d\x22\xbf\x7f\xff\x3e\xd6\xac\x5e\x85\xef\xda\xdb\xc3\x5a\x63\
+\x6c\xc2\x58\xbc\x53\x51\x81\x89\xfe\xee\xe0\xb0\x04\x90\x95\xda\
+\xb1\x48\x3f\x84\xf5\xf5\xf5\x61\xfd\xda\xd7\x82\x27\x1e\x34\x40\
+\xb4\xd9\x6c\x28\xfb\x53\x39\x26\xff\x74\xf2\x63\x50\x4c\xd0\x92\
+\x15\x61\x80\x28\x8a\x22\xb6\x6c\x72\xe2\x7c\x6d\x6d\x78\x55\x1d\
+\x02\x14\xef\xda\x89\xd9\x19\xb3\x87\x79\x35\x86\x84\x88\xc8\x81\
+\x9f\xda\x28\x16\x17\x15\xe1\x5f\x9f\x7e\x1a\xb6\x58\x5f\xbd\x3a\
+\x0f\x0b\x17\x2d\x8a\xe8\x43\xfa\x64\x0e\x05\x6f\xb5\xc2\xc2\x71\
+\xb2\x62\x0e\x85\x28\x52\x08\x82\x00\xc2\x11\xd8\xac\xd6\x01\x02\
+\x50\xcd\xdb\x24\xd2\x83\x10\x02\xbb\xdd\xae\x7a\x88\x83\x7f\x3b\
+\x80\xf7\x0f\x1c\x54\x4f\xc7\x64\xf2\x68\xe1\xa2\x45\x78\x7d\xb3\
+\xd3\x10\x68\x82\x20\xc0\xe3\xf1\xc0\x6e\xb7\x23\x26\xc6\xae\x00\
+\x2d\xb0\x24\x21\xb0\x58\x08\x2c\x16\x2e\xf0\x1c\xa5\x14\x84\x10\
+\x10\x42\x06\xc1\x03\x25\xd6\xd5\xd5\x85\x65\x4b\x5e\x84\xc5\x62\
+\x61\x7e\xa0\x2b\x97\x2f\xab\x73\x9d\xcc\xa3\x33\x32\xb3\x50\xbc\
+\x73\xa7\x21\xf0\x1e\x3e\xec\x43\x6c\x6c\x0c\x38\x8e\x53\x00\x26\
+\x05\xc9\xff\xb8\xf4\xf7\x20\x70\x78\x3e\x70\x8c\xe8\x15\x13\x34\
+\x22\xae\xcb\xed\xc2\xc5\xaf\xbe\x52\xe7\x36\xb5\x6a\x8a\xec\x98\
+\x69\x69\x69\x28\xdb\x57\x06\x9b\xcd\xa6\xeb\xf4\x5c\x2e\x37\xac\
+\x56\x1e\x31\x31\xf6\xfe\xad\x29\x42\x10\x04\x58\x2c\x16\x43\x93\
+\xb3\x5e\xaf\x17\x84\x10\xf0\x3c\xaf\xea\x8d\x91\xdf\xa9\x44\x34\
+\xc0\x92\xa7\x60\xa1\xc4\x37\x05\xc6\x8f\x1f\x8f\x8a\xf7\xf6\xc3\
+\x31\x56\x9f\xd6\x73\xb9\xdd\xb0\xdb\x6d\x20\x84\x80\x52\x1f\xa7\
+\x51\x4a\xc1\x71\x1c\x22\xb9\x21\x95\x52\x0a\x8f\xc7\xa3\x7a\x0c\
+\x7d\x1c\x48\xa1\x3e\xf2\x4b\x10\xde\xac\x8c\x86\x17\xc7\xc6\xc5\
+\x61\x5f\x79\xb9\x6e\xad\x47\x29\x85\xdd\x66\x0b\x6c\x45\xaf\xd7\
+\x2b\x7f\x01\x48\x4d\x0d\xb8\xaa\x2a\x70\xe7\xcf\x03\x4d\x4d\x20\
+\x0f\x1e\xf8\x9e\x72\x38\x80\xd4\x54\x88\xb3\x66\x41\xcc\xce\x06\
+\xcd\xc8\x60\x06\x42\x8f\xc7\x03\xab\xd5\xaa\xf0\xc4\xc8\x53\xb9\
+\x50\x9c\xa9\x16\x24\x64\xb2\x85\x23\x1c\x76\x95\x96\x60\xda\xf4\
+\xe9\xba\xc1\xf3\xff\x14\x04\x01\xa2\x28\x4a\x35\x13\xb8\xca\x4a\
+\x58\x4a\x4b\x41\x6e\xdf\x66\x9f\x66\x47\x07\xd0\xd1\x01\x4b\x6d\
+\x2d\x2c\x7b\xf6\x80\xa6\xa6\x42\x70\x3a\x21\x2e\x5f\x0e\xc8\xf8\
+\x8f\x05\xa2\xfe\xbe\xb0\x7c\xeb\x92\x30\x0a\x00\x21\x00\x26\x20\
+\x28\x28\x2c\xc4\x2f\x9f\x7b\xce\x10\x78\xfe\x20\x25\x05\x8f\x34\
+\x35\xc1\x9a\x95\x05\x3e\x37\x57\x15\x3c\xe6\x69\x35\x35\x81\x5f\
+\xb9\x12\xd6\xac\x2c\x90\xb6\x36\x66\x30\x94\xae\xcb\x19\xf6\x3a\
+\x1a\xc2\xeb\xa8\x0a\x4f\x2a\xde\x43\x02\x3c\x13\x09\x4f\x49\xc1\
+\xe3\xce\x9c\x81\x75\xe6\x4c\x90\xba\x3a\xe3\xd4\x5e\x57\x07\xeb\
+\xec\xd9\x20\xb2\x7b\x5f\xe4\x11\xdb\x98\x07\x6a\xdd\xfa\x10\x6e\
+\xd1\x81\x3e\xe2\x27\x00\xd8\x5d\x5a\x8a\x73\x35\xe7\x0c\x79\x5f\
+\x90\xe7\x55\x57\x83\x5f\xb8\x10\xe8\xee\x8e\x3c\xcd\xe8\xec\x84\
+\x35\x3b\x1b\xdc\xd9\xb3\x4c\xca\xd0\xef\x81\xa4\x1f\x1d\x56\xda\
+\x15\x6a\xcc\x83\x6a\x7b\xa3\x20\x0a\xd8\xb4\x71\x03\x5a\x5a\x5a\
+\x0c\x7b\x1f\x69\x6b\x83\x75\xc9\x12\xc0\xed\x8e\x5e\xae\xe6\x76\
+\x83\x5f\xbc\x58\x41\x03\xfe\x35\xf5\x7b\x20\xa5\xda\x1e\xa6\x55\
+\xad\xa1\xda\x19\x4d\x47\x47\x07\x5e\xcf\xdf\xa0\x8c\xa2\xe1\x78\
+\x9f\x28\x82\x5f\xba\x14\xe8\xec\x8c\x7e\xc2\xdb\xdd\x0d\x7e\xc5\
+\x0a\xa0\x7f\x2d\xa9\x3c\x32\x56\xce\xa2\x1a\x5c\x47\x75\x00\xca\
+\xc8\x6e\xea\xaf\x5d\xc3\xee\xd2\xd2\xb0\x01\x0c\x70\xd1\xe1\xc3\
+\x11\x71\x5e\x38\x9c\xc8\x1d\x3a\x14\x14\xb0\x04\x41\xd0\xa9\x03\
+\x43\x70\x9b\x63\x8c\x03\xab\xf2\xf2\xc0\x5b\x95\x87\xa5\xa2\x88\
+\xca\xca\x4a\xb4\xb7\xb5\x29\xf5\xa4\x8c\x0a\xde\x7b\x77\x3f\x7e\
+\xf6\xf4\xd3\x98\x9f\x9d\xcd\xac\xe6\x48\xc5\x31\xa5\x14\xa0\x14\
+\x96\x92\x92\x01\xaf\xbc\x58\x4a\x4b\x21\xbe\xf4\x52\x40\x27\x8a\
+\xa2\x18\x41\x39\x8b\x31\xe6\x11\x3f\x3a\x1e\xb9\xab\x56\xaa\xbe\
+\x75\x4a\x7a\x3a\x72\x96\x2e\xf3\xa5\x55\x54\x9b\x1f\x0b\x0b\x0a\
+\x90\x92\x92\x82\x9f\x4c\x0e\xae\x01\xba\x3d\x1e\xc4\xd8\xed\x20\
+\x84\x04\xc0\x24\x9d\x9d\x20\xcd\xcd\x03\x0e\x20\x69\x6e\x06\xa9\
+\xae\x06\xcd\xca\x0a\x6c\x63\x63\xa3\x1d\x60\x67\x1d\xa2\x20\xe2\
+\x61\x6f\xaf\xea\xdb\x67\xcc\x9c\x09\xe7\x96\xcd\xea\x5a\x52\x72\
+\xec\x07\xf7\x1f\x60\x63\xfe\x06\xc5\xf1\xb8\xfe\xab\x2f\x2d\x08\
+\xd0\x4f\x3e\x19\xb4\xfa\x1f\x77\xe2\x44\xc0\xfb\x44\x51\x34\xd0\
+\x13\x21\x88\xe8\x2e\xa5\x57\x73\x73\xf1\xab\xe7\x9f\x67\x73\xa8\
+\xec\x8e\xf8\x6f\x1a\x1a\xf0\xe6\xf6\x1d\x32\xd9\x48\x14\x11\x98\
+\xfb\xe0\x83\xc1\x03\xf0\xe2\x45\x83\xc5\x04\x42\xd8\x39\x31\xf4\
+\x83\x59\xf4\x56\x31\xa6\xa4\xa7\xb3\xf9\x55\x96\x57\x7f\xf8\xe1\
+\x31\xfc\xf3\xa3\x8f\x1e\xf1\x90\xa4\x54\x16\x08\x26\x8d\x8d\x83\
+\x57\x82\xbe\x75\xcb\x20\x80\xac\x6a\x44\x18\x35\x42\x96\xc5\xc7\
+\xc7\x63\x5f\x79\x39\x1c\xd2\xee\x9a\xda\xa4\x3f\x05\xb6\x15\x6c\
+\x0d\xcc\xca\xf8\xab\x2d\x41\xa7\xf1\xfd\xf7\x83\x86\x9f\xbf\x08\
+\x61\x2c\x13\x09\x15\x89\x75\x6c\xe7\xe4\x49\xc9\x28\x29\x29\xf5\
+\x6d\x49\xa2\x1d\xf1\xdd\x6e\x37\x36\xe6\xe7\xe3\x81\xec\xe4\x87\
+\x82\xe9\xe7\x40\xb9\x06\x8c\xa0\xb1\xfe\x8b\x79\x73\xb1\x7a\xed\
+\x9a\xb0\xf2\xeb\xd6\x96\x56\x14\x16\x14\x30\x35\x20\x1d\x33\x66\
+\xd0\x00\xa3\x0e\x47\x04\x00\xd2\xc8\xbc\x8e\x65\xeb\xd6\xaf\xc7\
+\xf4\xe9\x33\x42\x8b\x76\x00\x27\x8e\x57\xa1\x9b\x95\xe3\x4e\x1e\
+\xc4\x76\xa7\x6c\xad\xe8\x0d\x58\x1a\x04\xd3\x66\xb3\x61\x6f\x59\
+\x19\x92\x92\x92\xb4\x23\x7f\xbf\x5d\xaf\xaf\x57\x44\x64\x71\xc6\
+\x8c\x41\xc3\x4f\xbe\x96\xb1\xe9\x2c\xb5\x3a\xa0\xc1\xca\xf9\x84\
+\x1f\x4f\x40\xc9\xee\x3f\x80\xe7\xf9\x90\x35\xc5\xab\x57\xaf\x29\
+\x3f\xd4\xfc\xf9\x83\x07\xa0\x6c\xad\xe8\x78\x60\x14\x66\xf4\x66\
+\x3d\xfb\x2c\xb6\x6d\x7f\x23\x64\x4e\xfd\xc5\x7f\xce\x2a\x97\xcf\
+\xcc\x04\x4d\x49\x19\x78\xfe\x4b\x49\x01\xcd\xcc\x8c\x30\x0a\xb3\
+\x52\xaf\x28\x0d\x1d\x2d\x5d\xb6\x0c\xbf\x7d\x71\x89\x72\x1d\x49\
+\xb0\x6a\x68\xf8\x9a\xa9\x51\x05\xa7\x73\xc0\x01\x14\x9c\x4e\x45\
+\xbf\x24\x7a\x37\x1b\x22\x3a\x40\x16\x6c\x2d\x0c\xce\x7f\x65\x74\
+\xe1\xf5\x78\x51\xfd\x65\xb5\x72\x6b\xe5\xe4\x80\x4e\x9b\x36\x70\
+\xde\xf7\xcc\x33\xbe\x3e\x89\x14\x3c\x5d\xb9\x30\x55\x01\x2a\xca\
+\xc3\xe6\x71\xa3\xe2\xb0\x77\x5f\x19\xe2\x46\xc5\x05\xb7\x47\x25\
+\xeb\xfe\x75\xff\x7e\x45\x5a\x07\x8e\x83\xb7\xb2\x12\x18\x37\x2e\
+\xfa\xe8\x8d\x1e\x0d\xef\x81\x03\x80\x6c\x60\xc0\x62\xb1\x44\xd8\
+\x17\x66\xb5\x36\xa3\x60\x69\x69\x69\x28\x2a\x2e\x56\x7a\x61\xff\
+\x7a\xe7\x6a\x6a\xd0\xf2\xed\xb7\x8a\x16\x23\x4d\x4e\x86\xe7\xe8\
+\x51\x40\x67\x43\x3e\x84\x4c\x80\xf7\xd8\x31\x50\x99\x7c\xf1\x8f\
+\x7e\x84\x0d\xa0\x20\x0a\x21\x05\x73\xa8\x4a\xb2\x1e\xfb\xf5\x0b\
+\x2f\xe0\xd5\xdf\xe5\xb2\x9b\xf6\x04\x78\x73\xc7\x8e\xc0\x36\x92\
+\x07\x14\xef\xc7\x1f\x03\xf1\xf1\x91\x9f\x44\x62\x22\x3c\x55\x55\
+\x10\xe7\xcc\x51\x3c\xe5\x57\x0c\x7c\xf8\x5e\x3c\x1a\x0e\x87\x83\
+\x5d\x81\xee\xff\x90\x09\x09\x09\x21\xa7\xb3\xf4\x98\x73\xcb\x66\
+\xb4\xb6\xb6\xe0\x72\xdd\x25\x85\xb4\xf9\xef\xf5\xaf\x71\xfc\xb3\
+\xcf\x90\xbd\x60\x81\x92\x0f\xe7\xce\x85\xe7\xc2\x05\xf0\x2f\xbf\
+\x0c\x72\xe9\x92\x61\xce\xf3\x1e\x39\x02\x9a\x9c\xac\x4c\xdf\x38\
+\x2e\xe0\xfd\x64\x38\x7f\x11\xb7\xbf\x99\xee\x2f\xae\x2a\x3e\x8a\
+\x28\x82\x3b\x7c\xd8\xd7\x58\x6f\x6a\x0a\xef\x98\xa9\xa9\x10\x36\
+\x6d\x82\x98\x93\xa3\x68\xac\xfb\x4d\xda\x5c\x27\xc3\xfd\x9b\xcc\
+\xfd\x3d\x65\x8e\xe3\x82\xa7\x12\x64\x95\x24\x52\x53\x03\xee\xf8\
+\x71\xdf\x68\x47\x73\x73\xf0\x68\x47\x4a\x8a\x6f\xb4\x63\xc1\x02\
+\xd5\xd1\x0e\x16\x78\x8f\x05\x80\x52\x4f\xf4\x39\x9d\x38\x60\xeb\
+\xb0\x66\x63\x1e\x8b\xef\x91\xf6\x0d\x49\x5a\xc2\x1a\x88\x8c\x26\
+\x78\xba\x82\xc8\x70\x00\x51\x1a\x91\xa3\xf5\x4d\x9a\xa1\x86\x2c\
+\xc9\xe3\xf8\xbf\x39\xf8\x03\x4a\x24\x20\x72\x1c\x17\x5c\xdc\x18\
+\x49\x00\xfa\x79\x91\x39\x2b\x18\x06\x15\x84\x1a\xeb\x95\xda\xff\
+\x00\x3d\x59\x77\x9e\x09\x70\xf8\xb9\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x4d\xd8\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x88\x00\x00\x00\x88\x08\x06\x00\x00\x00\x3c\xba\xa3\x52\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
+\x01\x00\x9a\x9c\x18\x00\x00\x0a\x4f\x69\x43\x43\x50\x50\x68\x6f\
+\x74\x6f\x73\x68\x6f\x70\x20\x49\x43\x43\x20\x70\x72\x6f\x66\x69\
+\x6c\x65\x00\x00\x78\xda\x9d\x53\x67\x54\x53\xe9\x16\x3d\xf7\xde\
+\xf4\x42\x4b\x88\x80\x94\x4b\x6f\x52\x15\x08\x20\x52\x42\x8b\x80\
+\x14\x91\x26\x2a\x21\x09\x10\x4a\x88\x21\xa1\xd9\x15\x51\xc1\x11\
+\x45\x45\x04\x1b\xc8\xa0\x88\x03\x8e\x8e\x80\x8c\x15\x51\x2c\x0c\
+\x8a\x0a\xd8\x07\xe4\x21\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1\x7b\
+\xa3\x6b\xd6\xbc\xf7\xe6\xcd\xfe\xb5\xd7\x3e\xe7\xac\xf3\x9d\xb3\
+\xcf\x07\xc0\x08\x0c\x96\x48\x33\x51\x35\x80\x0c\xa9\x42\x1e\x11\
+\xe0\x83\xc7\xc4\xc6\xe1\xe4\x2e\x40\x81\x0a\x24\x70\x00\x10\x08\
+\xb3\x64\x21\x73\xfd\x23\x01\x00\xf8\x7e\x3c\x3c\x2b\x22\xc0\x07\
+\xbe\x00\x01\x78\xd3\x0b\x08\x00\xc0\x4d\x9b\xc0\x30\x1c\x87\xff\
+\x0f\xea\x42\x99\x5c\x01\x80\x84\x01\xc0\x74\x91\x38\x4b\x08\x80\
+\x14\x00\x40\x7a\x8e\x42\xa6\x00\x40\x46\x01\x80\x9d\x98\x26\x53\
+\x00\xa0\x04\x00\x60\xcb\x63\x62\xe3\x00\x50\x2d\x00\x60\x27\x7f\
+\xe6\xd3\x00\x80\x9d\xf8\x99\x7b\x01\x00\x5b\x94\x21\x15\x01\xa0\
+\x91\x00\x20\x13\x65\x88\x44\x00\x68\x3b\x00\xac\xcf\x56\x8a\x45\
+\x00\x58\x30\x00\x14\x66\x4b\xc4\x39\x00\xd8\x2d\x00\x30\x49\x57\
+\x66\x48\x00\xb0\xb7\x00\xc0\xce\x10\x0b\xb2\x00\x08\x0c\x00\x30\
+\x51\x88\x85\x29\x00\x04\x7b\x00\x60\xc8\x23\x23\x78\x00\x84\x99\
+\x00\x14\x46\xf2\x57\x3c\xf1\x2b\xae\x10\xe7\x2a\x00\x00\x78\x99\
+\xb2\x3c\xb9\x24\x39\x45\x81\x5b\x08\x2d\x71\x07\x57\x57\x2e\x1e\
+\x28\xce\x49\x17\x2b\x14\x36\x61\x02\x61\x9a\x40\x2e\xc2\x79\x99\
+\x19\x32\x81\x34\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\
+\xf3\xfd\x78\xce\x0e\xae\xce\xce\x36\x8e\xb6\x0e\x5f\x2d\xea\xbf\
+\x06\xff\x22\x62\x62\xe3\xfe\xe5\xcf\xab\x70\x40\x00\x00\xe1\x74\
+\x7e\xd1\xfe\x2c\x2f\xb3\x1a\x80\x3b\x06\x80\x6d\xfe\xa2\x25\xee\
+\x04\x68\x5e\x0b\xa0\x75\xf7\x8b\x66\xb2\x0f\x40\xb5\x00\xa0\xe9\
+\xda\x57\xf3\x70\xf8\x7e\x3c\x3c\x45\xa1\x90\xb9\xd9\xd9\xe5\xe4\
+\xe4\xd8\x4a\xc4\x42\x5b\x61\xca\x57\x7d\xfe\x67\xc2\x5f\xc0\x57\
+\xfd\x6c\xf9\x7e\x3c\xfc\xf7\xf5\xe0\xbe\xe2\x24\x81\x32\x5d\x81\
+\x47\x04\xf8\xe0\xc2\xcc\xf4\x4c\xa5\x1c\xcf\x92\x09\x84\x62\xdc\
+\xe6\x8f\x47\xfc\xb7\x0b\xff\xfc\x1d\xd3\x22\xc4\x49\x62\xb9\x58\
+\x2a\x14\xe3\x51\x12\x71\x8e\x44\x9a\x8c\xf3\x32\xa5\x22\x89\x42\
+\x92\x29\xc5\x25\xd2\xff\x64\xe2\xdf\x2c\xfb\x03\x3e\xdf\x35\x00\
+\xb0\x6a\x3e\x01\x7b\x91\x2d\xa8\x5d\x63\x03\xf6\x4b\x27\x10\x58\
+\x74\xc0\xe2\xf7\x00\x00\xf2\xbb\x6f\xc1\xd4\x28\x08\x03\x80\x68\
+\x83\xe1\xcf\x77\xff\xef\x3f\xfd\x47\xa0\x25\x00\x80\x66\x49\x92\
+\x71\x00\x00\x5e\x44\x24\x2e\x54\xca\xb3\x3f\xc7\x08\x00\x00\x44\
+\xa0\x81\x2a\xb0\x41\x1b\xf4\xc1\x18\x2c\xc0\x06\x1c\xc1\x05\xdc\
+\xc1\x0b\xfc\x60\x36\x84\x42\x24\xc4\xc2\x42\x10\x42\x0a\x64\x80\
+\x1c\x72\x60\x29\xac\x82\x42\x28\x86\xcd\xb0\x1d\x2a\x60\x2f\xd4\
+\x40\x1d\x34\xc0\x51\x68\x86\x93\x70\x0e\x2e\xc2\x55\xb8\x0e\x3d\
+\x70\x0f\xfa\x61\x08\x9e\xc1\x28\xbc\x81\x09\x04\x41\xc8\x08\x13\
+\x61\x21\xda\x88\x01\x62\x8a\x58\x23\x8e\x08\x17\x99\x85\xf8\x21\
+\xc1\x48\x04\x12\x8b\x24\x20\xc9\x88\x14\x51\x22\x4b\x91\x35\x48\
+\x31\x52\x8a\x54\x20\x55\x48\x1d\xf2\x3d\x72\x02\x39\x87\x5c\x46\
+\xba\x91\x3b\xc8\x00\x32\x82\xfc\x86\xbc\x47\x31\x94\x81\xb2\x51\
+\x3d\xd4\x0c\xb5\x43\xb9\xa8\x37\x1a\x84\x46\xa2\x0b\xd0\x64\x74\
+\x31\x9a\x8f\x16\xa0\x9b\xd0\x72\xb4\x1a\x3d\x8c\x36\xa1\xe7\xd0\
+\xab\x68\x0f\xda\x8f\x3e\x43\xc7\x30\xc0\xe8\x18\x07\x33\xc4\x6c\
+\x30\x2e\xc6\xc3\x42\xb1\x38\x2c\x09\x93\x63\xcb\xb1\x22\xac\x0c\
+\xab\xc6\x1a\xb0\x56\xac\x03\xbb\x89\xf5\x63\xcf\xb1\x77\x04\x12\
+\x81\x45\xc0\x09\x36\x04\x77\x42\x20\x61\x1e\x41\x48\x58\x4c\x58\
+\x4e\xd8\x48\xa8\x20\x1c\x24\x34\x11\xda\x09\x37\x09\x03\x84\x51\
+\xc2\x27\x22\x93\xa8\x4b\xb4\x26\xba\x11\xf9\xc4\x18\x62\x32\x31\
+\x87\x58\x48\x2c\x23\xd6\x12\x8f\x13\x2f\x10\x7b\x88\x43\xc4\x37\
+\x24\x12\x89\x43\x32\x27\xb9\x90\x02\x49\xb1\xa4\x54\xd2\x12\xd2\
+\x46\xd2\x6e\x52\x23\xe9\x2c\xa9\x9b\x34\x48\x1a\x23\x93\xc9\xda\
+\x64\x6b\xb2\x07\x39\x94\x2c\x20\x2b\xc8\x85\xe4\x9d\xe4\xc3\xe4\
+\x33\xe4\x1b\xe4\x21\xf2\x5b\x0a\x9d\x62\x40\x71\xa4\xf8\x53\xe2\
+\x28\x52\xca\x6a\x4a\x19\xe5\x10\xe5\x34\xe5\x06\x65\x98\x32\x41\
+\x55\xa3\x9a\x52\xdd\xa8\xa1\x54\x11\x35\x8f\x5a\x42\xad\xa1\xb6\
+\x52\xaf\x51\x87\xa8\x13\x34\x75\x9a\x39\xcd\x83\x16\x49\x4b\xa5\
+\xad\xa2\x95\xd3\x1a\x68\x17\x68\xf7\x69\xaf\xe8\x74\xba\x11\xdd\
+\x95\x1e\x4e\x97\xd0\x57\xd2\xcb\xe9\x47\xe8\x97\xe8\x03\xf4\x77\
+\x0c\x0d\x86\x15\x83\xc7\x88\x67\x28\x19\x9b\x18\x07\x18\x67\x19\
+\x77\x18\xaf\x98\x4c\xa6\x19\xd3\x8b\x19\xc7\x54\x30\x37\x31\xeb\
+\x98\xe7\x99\x0f\x99\x6f\x55\x58\x2a\xb6\x2a\x7c\x15\x91\xca\x0a\
+\x95\x4a\x95\x26\x95\x1b\x2a\x2f\x54\xa9\xaa\xa6\xaa\xde\xaa\x0b\
+\x55\xf3\x55\xcb\x54\x8f\xa9\x5e\x53\x7d\xae\x46\x55\x33\x53\xe3\
+\xa9\x09\xd4\x96\xab\x55\xaa\x9d\x50\xeb\x53\x1b\x53\x67\xa9\x3b\
+\xa8\x87\xaa\x67\xa8\x6f\x54\x3f\xa4\x7e\x59\xfd\x89\x06\x59\xc3\
+\x4c\xc3\x4f\x43\xa4\x51\xa0\xb1\x5f\xe3\xbc\xc6\x20\x0b\x63\x19\
+\xb3\x78\x2c\x21\x6b\x0d\xab\x86\x75\x81\x35\xc4\x26\xb1\xcd\xd9\
+\x7c\x76\x2a\xbb\x98\xfd\x1d\xbb\x8b\x3d\xaa\xa9\xa1\x39\x43\x33\
+\x4a\x33\x57\xb3\x52\xf3\x94\x66\x3f\x07\xe3\x98\x71\xf8\x9c\x74\
+\x4e\x09\xe7\x28\xa7\x97\xf3\x7e\x8a\xde\x14\xef\x29\xe2\x29\x1b\
+\xa6\x34\x4c\xb9\x31\x65\x5c\x6b\xaa\x96\x97\x96\x58\xab\x48\xab\
+\x51\xab\x47\xeb\xbd\x36\xae\xed\xa7\x9d\xa6\xbd\x45\xbb\x59\xfb\
+\x81\x0e\x41\xc7\x4a\x27\x5c\x27\x47\x67\x8f\xce\x05\x9d\xe7\x53\
+\xd9\x53\xdd\xa7\x0a\xa7\x16\x4d\x3d\x3a\xf5\xae\x2e\xaa\x6b\xa5\
+\x1b\xa1\xbb\x44\x77\xbf\x6e\xa7\xee\x98\x9e\xbe\x5e\x80\x9e\x4c\
+\x6f\xa7\xde\x79\xbd\xe7\xfa\x1c\x7d\x2f\xfd\x54\xfd\x6d\xfa\xa7\
+\xf5\x47\x0c\x58\x06\xb3\x0c\x24\x06\xdb\x0c\xce\x18\x3c\xc5\x35\
+\x71\x6f\x3c\x1d\x2f\xc7\xdb\xf1\x51\x43\x5d\xc3\x40\x43\xa5\x61\
+\x95\x61\x97\xe1\x84\x91\xb9\xd1\x3c\xa3\xd5\x46\x8d\x46\x0f\x8c\
+\x69\xc6\x5c\xe3\x24\xe3\x6d\xc6\x6d\xc6\xa3\x26\x06\x26\x21\x26\
+\x4b\x4d\xea\x4d\xee\x9a\x52\x4d\xb9\xa6\x29\xa6\x3b\x4c\x3b\x4c\
+\xc7\xcd\xcc\xcd\xa2\xcd\xd6\x99\x35\x9b\x3d\x31\xd7\x32\xe7\x9b\
+\xe7\x9b\xd7\x9b\xdf\xb7\x60\x5a\x78\x5a\x2c\xb6\xa8\xb6\xb8\x65\
+\x49\xb2\xe4\x5a\xa6\x59\xee\xb6\xbc\x6e\x85\x5a\x39\x59\xa5\x58\
+\x55\x5a\x5d\xb3\x46\xad\x9d\xad\x25\xd6\xbb\xad\xbb\xa7\x11\xa7\
+\xb9\x4e\x93\x4e\xab\x9e\xd6\x67\xc3\xb0\xf1\xb6\xc9\xb6\xa9\xb7\
+\x19\xb0\xe5\xd8\x06\xdb\xae\xb6\x6d\xb6\x7d\x61\x67\x62\x17\x67\
+\xb7\xc5\xae\xc3\xee\x93\xbd\x93\x7d\xba\x7d\x8d\xfd\x3d\x07\x0d\
+\x87\xd9\x0e\xab\x1d\x5a\x1d\x7e\x73\xb4\x72\x14\x3a\x56\x3a\xde\
+\x9a\xce\x9c\xee\x3f\x7d\xc5\xf4\x96\xe9\x2f\x67\x58\xcf\x10\xcf\
+\xd8\x33\xe3\xb6\x13\xcb\x29\xc4\x69\x9d\x53\x9b\xd3\x47\x67\x17\
+\x67\xb9\x73\x83\xf3\x88\x8b\x89\x4b\x82\xcb\x2e\x97\x3e\x2e\x9b\
+\x1b\xc6\xdd\xc8\xbd\xe4\x4a\x74\xf5\x71\x5d\xe1\x7a\xd2\xf5\x9d\
+\x9b\xb3\x9b\xc2\xed\xa8\xdb\xaf\xee\x36\xee\x69\xee\x87\xdc\x9f\
+\xcc\x34\x9f\x29\x9e\x59\x33\x73\xd0\xc3\xc8\x43\xe0\x51\xe5\xd1\
+\x3f\x0b\x9f\x95\x30\x6b\xdf\xac\x7e\x4f\x43\x4f\x81\x67\xb5\xe7\
+\x23\x2f\x63\x2f\x91\x57\xad\xd7\xb0\xb7\xa5\x77\xaa\xf7\x61\xef\
+\x17\x3e\xf6\x3e\x72\x9f\xe3\x3e\xe3\x3c\x37\xde\x32\xde\x59\x5f\
+\xcc\x37\xc0\xb7\xc8\xb7\xcb\x4f\xc3\x6f\x9e\x5f\x85\xdf\x43\x7f\
+\x23\xff\x64\xff\x7a\xff\xd1\x00\xa7\x80\x25\x01\x67\x03\x89\x81\
+\x41\x81\x5b\x02\xfb\xf8\x7a\x7c\x21\xbf\x8e\x3f\x3a\xdb\x65\xf6\
+\xb2\xd9\xed\x41\x8c\xa0\xb9\x41\x15\x41\x8f\x82\xad\x82\xe5\xc1\
+\xad\x21\x68\xc8\xec\x90\xad\x21\xf7\xe7\x98\xce\x91\xce\x69\x0e\
+\x85\x50\x7e\xe8\xd6\xd0\x07\x61\xe6\x61\x8b\xc3\x7e\x0c\x27\x85\
+\x87\x85\x57\x86\x3f\x8e\x70\x88\x58\x1a\xd1\x31\x97\x35\x77\xd1\
+\xdc\x43\x73\xdf\x44\xfa\x44\x96\x44\xde\x9b\x67\x31\x4f\x39\xaf\
+\x2d\x4a\x35\x2a\x3e\xaa\x2e\x6a\x3c\xda\x37\xba\x34\xba\x3f\xc6\
+\x2e\x66\x59\xcc\xd5\x58\x9d\x58\x49\x6c\x4b\x1c\x39\x2e\x2a\xae\
+\x36\x6e\x6c\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\x0b\xe3\x7b\x17\
+\x98\x2f\xc8\x5d\x70\x79\xa1\xce\xc2\xf4\x85\xa7\x16\xa9\x2e\x12\
+\x2c\x3a\x96\x40\x4c\x88\x4e\x38\x94\xf0\x41\x10\x2a\xa8\x16\x8c\
+\x25\xf2\x13\x77\x25\x8e\x0a\x79\xc2\x1d\xc2\x67\x22\x2f\xd1\x36\
+\xd1\x88\xd8\x43\x5c\x2a\x1e\x4e\xf2\x48\x2a\x4d\x7a\x92\xec\x91\
+\xbc\x35\x79\x24\xc5\x33\xa5\x2c\xe5\xb9\x84\x27\xa9\x90\xbc\x4c\
+\x0d\x4c\xdd\x9b\x3a\x9e\x16\x9a\x76\x20\x6d\x32\x3d\x3a\xbd\x31\
+\x83\x92\x91\x90\x71\x42\xaa\x21\x4d\x93\xb6\x67\xea\x67\xe6\x66\
+\x76\xcb\xac\x65\x85\xb2\xfe\xc5\x6e\x8b\xb7\x2f\x1e\x95\x07\xc9\
+\x6b\xb3\x90\xac\x05\x59\x2d\x0a\xb6\x42\xa6\xe8\x54\x5a\x28\xd7\
+\x2a\x07\xb2\x67\x65\x57\x66\xbf\xcd\x89\xca\x39\x96\xab\x9e\x2b\
+\xcd\xed\xcc\xb3\xca\xdb\x90\x37\x9c\xef\x9f\xff\xed\x12\xc2\x12\
+\xe1\x92\xb6\xa5\x86\x4b\x57\x2d\x1d\x58\xe6\xbd\xac\x6a\x39\xb2\
+\x3c\x71\x79\xdb\x0a\xe3\x15\x05\x2b\x86\x56\x06\xac\x3c\xb8\x8a\
+\xb6\x2a\x6d\xd5\x4f\xab\xed\x57\x97\xae\x7e\xbd\x26\x7a\x4d\x6b\
+\x81\x5e\xc1\xca\x82\xc1\xb5\x01\x6b\xeb\x0b\x55\x0a\xe5\x85\x7d\
+\xeb\xdc\xd7\xed\x5d\x4f\x58\x2f\x59\xdf\xb5\x61\xfa\x86\x9d\x1b\
+\x3e\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\x7f\xd8\x28\xdc\x78\xe5\
+\x1b\x87\x6f\xca\xbf\x99\xdc\x94\xb4\xa9\xab\xc4\xb9\x64\xcf\x66\
+\xd2\x66\xe9\xe6\xde\x2d\x9e\x5b\x0e\x96\xaa\x97\xe6\x97\x0e\x6e\
+\x0d\xd9\xda\xb4\x0d\xdf\x56\xb4\xed\xf5\xf6\x45\xdb\x2f\x97\xcd\
+\x28\xdb\xbb\x83\xb6\x43\xb9\xa3\xbf\x3c\xb8\xbc\x65\xa7\xc9\xce\
+\xcd\x3b\x3f\x54\xa4\x54\xf4\x54\xfa\x54\x36\xee\xd2\xdd\xb5\x61\
+\xd7\xf8\x6e\xd1\xee\x1b\x7b\xbc\xf6\x34\xec\xd5\xdb\x5b\xbc\xf7\
+\xfd\x3e\xc9\xbe\xdb\x55\x01\x55\x4d\xd5\x66\xd5\x65\xfb\x49\xfb\
+\xb3\xf7\x3f\xae\x89\xaa\xe9\xf8\x96\xfb\x6d\x5d\xad\x4e\x6d\x71\
+\xed\xc7\x03\xd2\x03\xfd\x07\x23\x0e\xb6\xd7\xb9\xd4\xd5\x1d\xd2\
+\x3d\x54\x52\x8f\xd6\x2b\xeb\x47\x0e\xc7\x1f\xbe\xfe\x9d\xef\x77\
+\x2d\x0d\x36\x0d\x55\x8d\x9c\xc6\xe2\x23\x70\x44\x79\xe4\xe9\xf7\
+\x09\xdf\xf7\x1e\x0d\x3a\xda\x76\x8c\x7b\xac\xe1\x07\xd3\x1f\x76\
+\x1d\x67\x1d\x2f\x6a\x42\x9a\xf2\x9a\x46\x9b\x53\x9a\xfb\x5b\x62\
+\x5b\xba\x4f\xcc\x3e\xd1\xd6\xea\xde\x7a\xfc\x47\xdb\x1f\x0f\x9c\
+\x34\x3c\x59\x79\x4a\xf3\x54\xc9\x69\xda\xe9\x82\xd3\x93\x67\xf2\
+\xcf\x8c\x9d\x95\x9d\x7d\x7e\x2e\xf9\xdc\x60\xdb\xa2\xb6\x7b\xe7\
+\x63\xce\xdf\x6a\x0f\x6f\xef\xba\x10\x74\xe1\xd2\x45\xff\x8b\xe7\
+\x3b\xbc\x3b\xce\x5c\xf2\xb8\x74\xf2\xb2\xdb\xe5\x13\x57\xb8\x57\
+\x9a\xaf\x3a\x5f\x6d\xea\x74\xea\x3c\xfe\x93\xd3\x4f\xc7\xbb\x9c\
+\xbb\x9a\xae\xb9\x5c\x6b\xb9\xee\x7a\xbd\xb5\x7b\x66\xf7\xe9\x1b\
+\x9e\x37\xce\xdd\xf4\xbd\x79\xf1\x16\xff\xd6\xd5\x9e\x39\x3d\xdd\
+\xbd\xf3\x7a\x6f\xf7\xc5\xf7\xf5\xdf\x16\xdd\x7e\x72\x27\xfd\xce\
+\xcb\xbb\xd9\x77\x27\xee\xad\xbc\x4f\xbc\x5f\xf4\x40\xed\x41\xd9\
+\x43\xdd\x87\xd5\x3f\x5b\xfe\xdc\xd8\xef\xdc\x7f\x6a\xc0\x77\xa0\
+\xf3\xd1\xdc\x47\xf7\x06\x85\x83\xcf\xfe\x91\xf5\x8f\x0f\x43\x05\
+\x8f\x99\x8f\xcb\x86\x0d\x86\xeb\x9e\x38\x3e\x39\x39\xe2\x3f\x72\
+\xfd\xe9\xfc\xa7\x43\xcf\x64\xcf\x26\x9e\x17\xfe\xa2\xfe\xcb\xae\
+\x17\x16\x2f\x7e\xf8\xd5\xeb\xd7\xce\xd1\x98\xd1\xa1\x97\xf2\x97\
+\x93\xbf\x6d\x7c\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\
+\xbe\xc9\x78\x33\x31\x5e\xf4\x56\xfb\xed\xc1\x77\xdc\x77\x1d\xef\
+\xa3\xdf\x0f\x4f\xe4\x7c\x20\x7f\x28\xff\x68\xf9\xb1\xf5\x53\xd0\
+\xa7\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfc\x63\x33\x2d\xdb\
+\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\
+\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\
+\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x43\x03\
+\x49\x44\x41\x54\x78\xda\xec\x9d\x77\x94\x14\x65\xd6\xc6\x7f\x55\
+\x5d\xd5\x39\x4c\x64\x22\xd3\xc3\x10\x9b\x9c\x44\x44\x41\x70\x15\
+\x15\x03\x6b\x64\xcd\xba\xab\xad\x98\xb3\x6b\x5e\x5d\x73\x4e\x2b\
+\xee\xa8\xeb\xea\xba\x06\xd4\x55\x51\xcc\x28\x49\x14\xc9\x20\x34\
+\x99\xe9\xc9\x79\xa6\xa7\x73\x77\x75\xd5\xf7\x47\x37\xa1\x19\x30\
+\x00\x83\xe1\xe3\x9e\x33\x67\xce\xa9\xee\xae\xaa\xf7\xd6\x53\xf7\
+\xbd\xf7\xbe\xf7\x3e\xaf\xa0\x69\x1a\x07\xe4\x80\xec\x4e\xc4\x03\
+\x2a\x38\x20\x07\x00\x72\x40\x0e\x00\xe4\x80\x1c\x00\xc8\x01\x39\
+\x00\x90\x03\xf2\x6b\x13\xe9\x80\x0a\x00\xc8\x03\x46\x01\x83\x81\
+\x22\x40\x0f\xf8\x80\xcd\xc0\x62\x60\x09\xa0\x1c\x00\xc8\x1e\x88\
+\xc7\x59\xfe\x9b\x1d\xbc\xcb\xeb\xee\x09\xfc\x45\x69\x0e\xdf\x1c\
+\x59\xde\x48\xc4\xd3\x8a\xd2\x10\x44\x8b\xa9\x88\x76\x3d\xfa\x12\
+\x1b\xc6\x41\xb9\x18\x07\xe5\x20\x48\xe2\xb9\xc0\x74\x8f\xb3\x3c\
+\xfa\x2b\x1b\x43\x97\x9e\x5f\xd8\xdb\x3c\xc8\x6f\x11\x20\x2e\xaf\
+\xbb\x18\xb8\x3c\xe6\xed\xb8\xa9\xfd\xcd\xb5\xf8\x3f\xda\x42\x6c\
+\x8b\x6f\xd7\x0a\x32\x4a\x98\x87\x77\xc3\x71\x5a\x5f\xec\xc7\x97\
+\x21\xe8\x75\x97\x78\x9c\xe5\xff\x3c\x00\x90\xdf\x29\x40\x5c\x5e\
+\xb7\x5b\x0d\xc6\xff\xd9\xf2\xec\x72\x5a\xff\xbd\x1a\x35\x10\xfb\
+\xc9\xbf\x35\xba\xb2\xc9\xbd\x7e\x24\xd6\x23\x9d\xd3\x80\x7f\x78\
+\x9c\xe5\xab\x7f\xef\x00\xf9\x7f\xe5\xa4\xba\xbc\xee\x07\x22\x2b\
+\x9b\xfe\xe9\x3d\x75\x06\xcd\xcf\x2c\xfb\x59\xe0\x00\x88\x78\x5a\
+\xa8\xfa\xcb\xa7\xd4\xdf\xfe\xf5\x54\x2d\x9a\xf8\xde\xe5\x75\x4f\
+\x39\x10\xc5\xfc\x7e\xc0\xf1\xb4\xff\x93\x2d\x37\x79\xcf\x98\x49\
+\x64\x4d\xcb\x5e\x9d\xab\xed\x95\xd5\x54\x9e\xfb\x11\x4a\x73\xf8\
+\x0d\x97\xd7\x7d\xd1\xef\x59\x6f\xff\x2f\xa6\x18\x97\xd7\xfd\x94\
+\xef\x7f\x1b\xae\xa8\xbd\x7e\x36\x24\x76\x3d\x5e\xd3\x90\x5c\x4c\
+\xa3\x0a\xd0\xf7\x70\x20\x1a\x74\x28\xcd\x61\x22\xab\x9a\x08\x2d\
+\xac\x47\x69\x0a\xed\x7a\xca\x19\x90\x43\xc9\xab\x93\xd0\x65\x19\
+\xcf\xf0\x38\xcb\xdf\x38\xe0\x83\xfc\x06\x01\xe2\xf2\xba\xff\x1e\
+\x9c\x5b\x7d\x5b\xd5\x05\x9f\xa0\x29\x6a\xa7\xcf\x2d\xe3\x8a\xc9\
+\xb9\x6c\x18\xe6\xd1\x05\x8b\x80\xb9\xc0\x06\x20\x92\x0a\x7d\x47\
+\x24\xda\x22\xa7\xfb\xde\xdb\x48\xeb\x3f\x57\x10\xaf\x0b\x76\xfa\
+\xbd\xf9\xe0\x02\xba\xff\xfb\x58\x44\xb3\x34\xde\xe3\x2c\x9f\x73\
+\x00\x20\xbf\x21\x80\xb8\xbc\xee\x8b\xa3\x1b\xdb\x9f\xf3\x9e\x3a\
+\x83\x44\x5b\x24\x7d\x6e\xb5\xc8\x74\xbb\x75\x34\x99\x67\xb9\x3e\
+\x03\xa6\x79\x9c\xe5\xef\xed\xe6\x1c\xd9\xc0\xc5\x4a\x63\xe8\xde\
+\x86\xbb\xbf\xa1\xe3\x83\x4d\x9d\xbe\x63\x3f\xa1\x27\x45\xcf\xfc\
+\xe1\x4d\xe0\x12\x8f\xb3\xbc\xfd\xf7\x04\x90\xdf\x6d\xa2\xcc\xe5\
+\x75\x0f\x51\x83\xf1\xe7\x6a\x2f\x9f\xd5\x09\x1c\x52\x9e\x99\xe2\
+\xe7\x8e\xc2\x34\x3c\xef\x61\x8f\xb3\xfc\xc6\x1f\x79\x01\x5a\x80\
+\xfb\x5c\x5e\xf7\xd7\x45\xcf\xfc\x61\xaa\xbe\xcc\x31\xa5\xf9\xc9\
+\xa5\x69\xdf\xe9\xf8\x60\x13\x46\x57\xf6\x94\xec\xcb\x86\x36\x02\
+\x57\x1e\x70\x52\x7f\x1b\x32\xb5\xf1\xde\x6f\x89\x78\x5a\x3a\x81\
+\xa3\xe4\x95\x49\x98\x86\xe7\xdd\xfe\x63\xe0\xd8\x09\x28\x73\x80\
+\xab\x73\xaf\x1d\x39\xad\xdb\x4d\xa3\x3a\x7d\xde\xf4\xd8\x62\x42\
+\x8b\xea\xaf\x70\x79\xdd\x97\x1f\x00\xc8\xaf\xdf\x7a\x5c\x17\x98\
+\x55\x79\x71\xdb\x7f\x3d\x9d\xa6\x95\xe2\x7f\x4e\xc4\xd0\x2f\xeb\
+\x76\x8f\xb3\xfc\x9e\x3d\x98\x4e\xeb\x81\x9b\xb3\x2f\x1d\x3a\x2d\
+\xdb\x3d\x38\xed\x33\x4d\x51\xa9\xbf\x79\x1e\x6a\x48\x79\xda\xe5\
+\x75\x17\x1d\x00\xc8\xaf\x17\x1c\x85\x6a\x30\xfe\x48\xc3\x5d\x0b\
+\x3a\x7d\x56\x70\xff\x58\x4c\xc3\xba\x3d\xb4\x27\xe0\xd8\x01\x24\
+\x3e\xe0\x91\x6e\xb7\x8c\xc6\x3a\xa1\x24\xed\xb3\xe8\x86\x36\x5a\
+\x9e\x5d\x06\x70\xed\x01\x80\xfc\x7a\xe5\xea\x96\x69\x2b\x88\x79\
+\x3b\xd2\x0e\x66\x9e\xd3\x1f\xfb\xe4\x5e\xd3\x3c\xce\xf2\x9b\xf6\
+\xf6\x02\x1e\x67\xf9\x66\x04\x4e\x28\x78\x60\x2c\xba\x4c\x63\xda\
+\x67\x2d\xcf\xaf\x22\xba\xb1\xfd\x5a\x97\xd7\xfd\x87\x03\x00\xf9\
+\xf5\x59\x8f\x11\xf1\xca\x8e\x1b\x5a\xff\xb5\x2a\xed\xb8\x5c\x68\
+\x25\xe5\x37\x4c\xdb\x57\xd7\xf2\x38\xcb\x3f\x94\xf2\x2d\xf7\x74\
+\xbb\xe5\xe0\xf4\xa9\x26\xa2\xd0\xf4\xc8\x22\x80\xa9\x07\x00\xf2\
+\x2b\x74\x4c\x9b\x9f\x59\x8e\x1a\x8c\xa7\x1d\xec\x76\xf3\xc1\x88\
+\x36\xfd\xcd\x1e\x67\xf9\xaa\x7d\x79\x31\x8f\xb3\xfc\xf6\x8c\xd3\
+\xfb\x7e\x6e\x19\x5b\x9c\x76\xdc\xff\xc9\x16\xc2\x4b\x1a\x4e\x71\
+\x79\xdd\x27\x1c\x00\xc8\xaf\xc7\x7a\x0c\x8d\x6d\x6a\xff\x8b\xef\
+\xdd\x0d\x69\xc7\x2d\x87\x16\x61\x3f\xb1\xe7\x2c\x8f\xb3\xfc\x81\
+\x2e\xba\xf4\xb4\xbc\x5b\x47\x23\x18\x74\x3b\x98\x11\x68\x7e\x6a\
+\xe9\xef\xc2\x8a\xfc\x9e\x2c\xc8\xd4\x96\x17\x56\xa1\xc5\x12\xdb\
+\x8f\xe8\x04\x72\x6f\x38\x68\x9f\x4e\x2d\xbb\xb0\x22\xef\x1a\x5c\
+\x59\x2f\xdb\x4f\xe8\x99\x76\x3c\x30\xa7\x8a\xf0\x8a\xa6\x63\x5d\
+\x5e\xf7\x41\x07\x00\xf2\xcb\x5b\x8f\x22\xa5\x3e\xe8\xee\x78\x7f\
+\x63\xda\x71\xdb\xc4\x52\x4c\xc3\xba\xbd\xe3\x71\x96\xbf\xd3\xc5\
+\xb7\x30\x2d\xeb\xc2\x41\x08\x92\x98\x66\x45\xda\x5f\xf7\x00\x5c\
+\x74\x00\x20\xbf\xbc\x9c\xda\xf1\xd1\x96\x74\xdf\x43\x27\x90\x73\
+\xe9\xd0\x2e\xb5\x1e\x3b\x58\x91\x85\x46\x57\xf6\x74\xdb\x31\xa5\
+\xe9\xbe\xc8\xcc\xcd\x28\x4d\xa1\x8b\x5c\x5e\x77\xfe\x01\x80\xfc\
+\x72\xd6\x43\xd2\x14\xf5\x89\xf6\xe9\x6b\xd3\x8e\x5b\xc7\x16\x63\
+\x1c\x9c\xfb\xb1\xc7\x59\x3e\x6b\x3f\xdd\xca\xb4\xac\x8b\x06\x83\
+\x28\x6c\x3b\x90\xe8\x88\xd1\xf1\xde\x46\x80\x33\x0f\x00\xe4\x97\
+\x93\x71\xa1\xef\xea\x89\x7a\x5a\xd3\x0e\x66\xfd\x79\xd0\x7e\xb1\
+\x1e\x3b\x58\x91\xd9\xa6\xa1\xdd\x66\x59\x0e\x29\x4c\x3b\xde\xfe\
+\xd6\x7a\x34\x45\x7d\xd4\xe5\x75\xcb\x07\x00\xf2\xcb\xc8\x5f\xda\
+\xdf\x48\xb7\x1e\xc6\xfe\xd9\x58\xc6\x16\x01\xcc\xdc\xcf\xf7\x32\
+\x2d\xf3\xbc\x01\x69\x07\xa2\xeb\x5a\x09\x2d\xac\x03\x18\xf7\x5b\
+\x54\xee\x2f\xb6\x9a\xeb\xf2\xba\x25\xc0\x02\xd8\x00\x13\xc9\x56\
+\x03\x01\x50\x81\x28\x10\x06\x02\x40\xd8\xe3\x2c\x8f\xef\xe6\x1c\
+\x39\x89\xd6\xc8\x99\xc1\xd9\x55\x69\xc7\x1d\xa7\xf5\x05\x51\xb8\
+\xda\xe3\x2c\x57\xf7\xf3\xb0\x3e\xb2\x1e\x5e\x8c\x5c\x6c\x23\x5e\
+\xed\xdf\x1e\xd1\x7c\xe6\xc5\x72\x68\xd1\x64\x60\xd6\x0f\xe8\xc3\
+\x98\xd2\x87\x05\x30\x02\x72\x4a\x1f\x0a\x10\x4b\xe9\xc2\xef\x71\
+\x96\x87\x7f\x77\x00\x49\x81\xa1\x3f\x30\x12\x18\x06\xf4\xd3\x14\
+\xf5\x48\x2d\xac\x90\x08\xc4\xd1\x22\x0a\x5a\x5c\x05\x4d\x03\x51\
+\x40\xd0\xeb\x10\x8d\x12\xa2\x45\x42\x30\x4a\xb8\xbc\xee\xf9\x40\
+\x2d\xc9\x3e\x95\x0d\xa9\xbf\x2d\xc0\xe1\x81\xb9\xd5\x24\x7c\xdb\
+\x3b\x11\x44\x8b\x8c\xed\xd8\x1e\x00\xef\xec\x6f\xd0\x7b\x9c\xe5\
+\x61\x97\xd7\xfd\x90\xfd\xf8\xb2\x1b\x5b\x9e\x5b\xb1\x1d\x20\xf3\
+\xaa\xe9\xa6\xa8\x57\xb8\xbc\xee\x9b\x81\x52\xa0\x17\xd0\x27\xf5\
+\xbf\x3b\x90\xa7\x45\x13\xc3\xd5\x50\x1c\x35\xac\xa0\x45\x12\xc9\
+\xe2\x26\x4d\x43\x90\x44\x04\x59\x44\xb0\xc8\xe8\x2c\x32\x2e\xaf\
+\x7b\x51\x6a\xec\x2b\x49\xf6\xeb\x2c\x07\xea\x7f\x93\x00\x49\xe5\
+\x00\xa6\x68\x8a\x7a\x5d\x6c\x43\x3b\xe1\x55\x4d\x44\xbe\x6f\x26\
+\xb6\xa9\x1d\xa5\x3e\x48\xa2\x2d\x8a\x1a\x51\xd0\x62\x09\x34\x55\
+\x03\x2d\xf9\xce\x24\x95\xa2\x43\x34\xea\x10\xed\x06\xa4\x6c\xe3\
+\x61\x52\x9e\x19\xb9\xc4\x8e\xbe\x87\x03\x7d\xa9\x03\xb9\xbb\x0d\
+\xb9\x9b\x19\xff\x47\x9b\xd3\xae\x69\x39\xb4\x08\xb9\xc0\xf2\xbe\
+\xc7\x59\x5e\xfd\x0b\x19\xc7\x37\xed\x27\xf4\xbc\xb1\xe5\xf9\x95\
+\xdb\xca\x1b\x63\x9b\xda\x89\xac\x68\xc2\xe0\xca\x0e\xc4\xab\xfd\
+\xc4\x2a\x7c\xc4\xb6\xf8\x88\x55\x74\xa0\xd4\x06\x50\x9a\xc3\x24\
+\xda\x22\xa8\x21\x05\x2d\x9a\x7c\x59\xb4\xd4\x6f\x05\x51\x00\x49\
+\x44\x34\xe8\x10\x2d\x32\x52\xae\xe9\x20\xb9\xbb\xed\x20\x43\xbf\
+\xec\xd3\x8d\x83\x72\x30\x0e\xc8\x46\xca\x35\x7f\x04\xbc\x0d\xbc\
+\x0f\xb4\xee\xcb\xc1\x74\x49\x45\x59\x2a\xc5\x3c\x35\xbc\xa2\xe9\
+\xd8\x8e\x0f\x36\x11\x9c\x53\x45\x74\x63\x3b\xa8\xfb\x8e\x8b\x44\
+\xb4\xc8\xc8\x45\x56\x62\x95\x7e\xb4\xc8\xf6\xa6\xb7\xe2\xf2\x89\
+\xd8\x8e\x2e\xbd\x15\x98\x03\xac\xf0\x38\xcb\x03\xfb\x71\xda\x2c\
+\x05\x46\x03\xd7\x54\x4c\x7e\x6f\x54\x78\x79\xe3\xb6\xcf\xe4\x62\
+\x1b\xa8\x1a\xf1\x86\xe0\x6e\xeb\x62\xf7\x44\x74\x19\x06\x4c\xc3\
+\xf2\xb0\x4d\xea\x81\x6d\x62\x29\xba\x0c\xc3\xed\x29\xe7\xbc\xe5\
+\x57\x07\x10\x97\xd7\x3d\x00\x98\x1a\x5a\x58\x77\x59\xcb\xb3\xcb\
+\x09\xcc\xa9\x4a\x5a\x85\xfd\x28\xe6\x43\x0a\xb1\x1c\x56\x84\x65\
+\x74\x01\x06\x57\x36\xa2\x45\x7e\x15\xf8\x08\xf8\x2a\x55\xcf\xb1\
+\x2f\x01\xa1\x03\x46\x00\x47\x03\x13\xe3\xd5\xfe\xc3\xc2\x4b\x1b\
+\x09\x7e\x53\x4b\xe0\x0b\x2f\x4a\x63\x68\xff\x3a\x94\xf9\x16\x32\
+\xcf\x76\x91\x75\xc1\x40\x44\xab\xfe\x6a\xe0\xc9\x5f\x0d\x40\x5c\
+\x5e\xf7\x85\x6a\x20\xf6\x7c\xe3\x83\x8b\x68\x7b\x75\xcd\x3e\xb5\
+\x16\x7b\x2a\x72\x91\x15\xf3\xa8\x02\xac\x13\xba\x63\x1e\x53\x88\
+\x94\x6b\x7e\x17\x78\x17\xf8\xc2\xe3\x2c\xaf\xdb\x43\x50\x88\x29\
+\x5f\x6a\x32\x09\xed\x96\xf0\xaa\x26\x82\x73\xab\x09\xce\xad\x26\
+\xb2\xba\x19\x35\xb4\x8f\x5a\x78\x05\xf6\xf8\xe5\xd2\x97\x65\x90\
+\x77\xfb\x68\xac\x47\x94\x4c\x03\x6e\xdf\x1b\x6b\xb2\x4f\x00\xe2\
+\xf2\xba\x6f\x88\x55\x74\x3c\x54\x73\xd9\x17\x44\xbe\x6f\xfe\x61\
+\x94\x77\x33\x63\xe8\x97\x85\xa1\x4f\x26\x7a\xa7\x1d\x29\xcf\x82\
+\x2e\xc3\x80\x60\x94\x10\xe4\x64\xd4\xad\xc5\x55\xb4\x68\x02\x35\
+\x10\x23\xd1\x12\x41\x69\x0a\x11\xaf\x0b\x10\xaf\xf4\x13\xaf\x09\
+\x10\xaf\x0b\xfe\xec\xa6\x27\x5d\xa6\x11\xf3\xa8\x7c\x6c\xc7\xf4\
+\xc0\x72\x58\x11\x52\x37\xf3\x9b\xc0\x74\xe0\x73\x8f\xb3\xdc\xff\
+\x13\x80\xd1\x1f\x38\x09\x55\xbb\x27\xbc\xb2\x89\xc0\xe7\x5e\x02\
+\xb3\x2a\x3b\x95\x34\xfe\xa8\xc2\x25\x11\x5d\xb6\x11\xb9\xc8\x96\
+\xf4\xa3\x8a\xad\xc8\x79\x16\x74\xd9\x26\x74\x19\x06\x44\xb3\x84\
+\xa0\xd7\x81\x4e\x80\x84\x86\x16\x4b\xa0\x06\xe2\x28\xcd\x61\xe2\
+\x35\x01\x62\x9b\xda\x89\xae\x6d\x25\xba\xa9\x3d\x7d\xdd\x69\x17\
+\x92\x7b\xfd\x48\x72\xae\x18\x3e\x0d\xb8\x1f\xa8\xfa\x45\x00\x02\
+\x5c\x18\xf3\x76\x3c\x5f\x79\xd6\x4c\xe2\x55\xbb\xd6\xb3\x94\x67\
+\xc6\x7e\x6c\x19\xd6\x89\x4e\x8c\x83\x72\xd1\xd9\xf5\x9f\x01\xab\
+\x81\x4d\x40\x4d\xca\xb1\x0a\x03\x5b\xc3\x59\x39\x15\xea\xd9\x81\
+\x5c\x20\x3f\xe5\xed\xf7\xd0\x14\xf5\xd8\x44\x6b\x84\xe0\xbc\x6a\
+\x6a\xaf\x9d\xbd\xc7\xf3\xb6\x65\x6c\x31\xf6\xe3\xcb\xb0\x8c\x2d\
+\x46\xb4\xc8\x0f\x03\xff\xf5\x38\xcb\x57\xec\x04\x0a\x1b\x70\x1c\
+\x70\x41\x74\x43\xdb\x44\xff\x27\x15\xf8\x3f\xde\x42\x64\x75\xf3\
+\x1e\x5d\xd7\x34\x32\x9f\xc2\x87\xc6\x21\xe5\x5b\x10\x2d\xf2\x37\
+\xa9\x68\xc4\x9b\x8a\xd0\x1a\x53\x7a\x08\xa6\xc2\xfc\x04\xa0\x03\
+\x0c\xa9\x54\x40\x1e\x50\x02\xf4\x43\xe3\xec\x58\x85\x8f\xd0\x37\
+\xb5\x74\x7c\xb8\x99\xe0\xd7\x35\xbb\xbd\x66\xf6\x65\x43\xe9\x76\
+\xe3\xa8\x69\xc0\xa5\xbf\x04\x40\xca\xd4\x60\x7c\x93\xf7\xf4\x0f\
+\x76\x69\x39\x74\x99\x46\xb2\x2f\x19\x42\xc6\x94\xbe\xe8\x32\x8d\
+\x4f\xa7\xbc\xec\x25\x7b\xd3\x1a\x90\x0a\x99\xcf\xf2\xbd\xbd\xfe\
+\xdf\xb5\xd7\x6d\x07\x88\xbe\x67\x06\xdd\x6e\x1a\x45\x68\x51\x3d\
+\xa1\x85\x75\x44\xd7\xb5\xa2\x45\x13\x3f\x3e\x0d\x15\xdb\xb0\x1f\
+\x57\x86\xfd\xa4\x5e\x18\x5d\xd9\x9f\x01\x2f\x00\xeb\x81\x3f\xa9\
+\xc1\xf8\x5f\x03\xb3\x2a\x69\x7f\x6b\x1d\xa1\x6f\x6a\x93\xa1\xf8\
+\x4f\xf0\x03\x4c\x43\x72\xb1\x8c\x2d\x46\x69\x08\xd2\xfc\xf4\xb2\
+\xed\xf7\xe8\xb4\xd3\x73\xf6\x14\x10\x05\xfb\x4f\xb1\x5a\x3f\xa0\
+\x03\x21\x15\x22\x4f\x00\x4e\x0f\x2f\x69\xf8\x43\xf3\x33\xcb\x08\
+\x7c\x59\xb9\xcb\xef\x17\x3c\x34\x8e\x8c\x29\xfd\x1e\x04\xfe\xba\
+\xbf\x01\xf2\x58\xe3\xbd\xdf\x5e\xd3\x52\xbe\xb2\xb3\xb3\x38\xba\
+\x90\x82\x07\xc7\xa1\x2f\xb5\xdf\x03\x3c\x91\x6a\x1f\xd8\x57\xce\
+\xe1\x7f\x6b\xae\xfc\xf2\xcc\x1d\x57\x6f\x1d\x27\xf5\xa6\xf0\x89\
+\x09\xaf\x02\x0d\xc0\xe1\xb1\x2d\xbe\x91\xa1\xef\xea\x08\x7c\x55\
+\x45\xf8\xbb\x7a\x94\x96\xf0\x8f\x9a\x7e\xf3\x98\x42\x32\xfe\xd4\
+\x0f\x7d\x99\x83\x8e\x19\x9b\xe8\x98\xb1\x29\x2d\xe1\xb5\x3b\x5f\
+\xc1\xe8\xca\xc6\x32\xae\x18\xcb\xb8\x62\x4c\x83\x73\x11\x6d\xfa\
+\xf7\x80\xcf\x62\x15\x1d\xcf\x6e\xfe\xc3\xf4\xed\x0d\x5b\x02\xf4\
+\xf8\xf0\x64\x8c\x03\x73\x46\x78\x9c\xe5\x4b\xf7\x91\x2e\x48\x59\
+\xb9\xa9\x6d\xaf\xae\x39\xae\xe1\xee\x6f\x3a\xbd\x18\xa2\x55\x4f\
+\xd9\x27\xa7\x20\x77\xb7\x1d\x0a\x2c\xd8\x5f\x00\xe9\x15\xab\xe8\
+\xd8\xb0\x79\xe2\x5b\x9d\x6e\xc8\x32\xb6\x98\xee\x2f\x4c\x44\x30\
+\x4a\x53\x3c\xce\xf2\xe9\xfb\x38\x72\x90\x35\x45\x8d\x6d\x3e\xea\
+\x2d\x62\x9b\xb7\x53\x36\xe4\xdf\x37\x96\xcc\xb3\x5c\x97\x00\x5b\
+\xa9\x19\x7a\xa7\xd2\xdb\xc7\x26\x5a\x23\xa7\x04\x17\xd4\xe2\xff\
+\x64\x0b\xc1\xaf\x6b\x48\xb4\x46\x7e\x24\x86\x16\x7e\xd4\xc9\x36\
+\x0e\xc8\xc1\x7a\x94\x13\xdb\x44\x27\x46\x57\x36\x88\xc2\x43\xc0\
+\x67\x24\x09\x67\x7c\x40\xea\x3e\xdf\x26\xb6\x79\xbb\xc1\xcc\xbf\
+\xf7\x30\x32\xcf\xee\x3f\x15\x78\x6e\x77\x69\x82\x3d\x00\xc8\xb6\
+\x17\xd6\xff\x69\xc5\x35\x35\x97\x7e\xd1\xa9\x8b\x70\x87\x17\xe8\
+\x9c\xfd\x95\x28\xbb\xb0\xfd\x8d\xb5\x9d\xc0\x21\xe5\x5b\x28\x7c\
+\x7c\x3c\x82\x51\x3a\xd9\xe3\x2c\x7f\xb7\x0b\x82\x93\xd2\x78\x95\
+\x9f\x78\xa5\x3f\xed\x81\x9a\x86\x75\x03\x58\xb8\xc3\xf7\xb6\x66\
+\x5c\x5f\xd4\x65\x19\x73\xec\xc7\x97\x1d\x61\x3f\xbe\xec\x64\xa5\
+\x39\x3c\x25\x38\xb7\x1a\xff\x47\x9b\x09\x2f\x6d\xdc\xb5\x65\xd9\
+\x05\x38\x04\x59\x44\xdf\x33\x03\xeb\xe1\xdd\xb1\x1d\xdb\x03\xd3\
+\x90\x5c\x10\x85\xbb\x52\xd3\xe6\x8a\xd4\x12\xc1\x8e\x12\x17\x24\
+\xf1\xbf\xa6\xc1\x39\x67\xed\x08\x90\xf0\xb2\x46\x32\xcf\xee\x3f\
+\x7a\x2b\x40\xf6\xb1\x5c\x6b\x3b\xba\x54\xca\xbd\x76\xe4\x15\x8d\
+\x0f\x7d\x97\xf6\x81\xef\xdd\x0d\x64\x5e\x30\xf0\x6c\xd3\x90\xdc\
+\xa7\x80\x45\x5d\x0d\x90\xec\x44\x5b\xe4\x26\xdf\x5b\xeb\x3a\x7b\
+\xce\xd7\x8d\x44\xca\x35\xdf\xd6\x45\xe0\x00\x18\x12\xf1\xb4\xa4\
+\xbd\x21\x72\xbe\x05\x7d\x0f\x07\x29\xdf\x61\x57\xd2\x9c\x8a\x58\
+\xa6\x4b\x39\xa6\xab\x1c\x27\xf7\x3e\xc3\x7e\x62\xcf\xc7\x9b\x9f\
+\x5a\x46\x68\x51\x1d\x86\x1e\x0e\xf4\x3d\x1c\xc8\xc5\x36\x74\x99\
+\x06\xd0\x89\x68\x11\x05\xa5\x31\x44\xac\xd2\x4f\x6c\x8b\x0f\x2d\
+\x14\x27\xcb\x3d\x04\xf3\xa8\xfc\xb7\x52\x89\xa8\x39\xbb\x00\xc5\
+\xce\xb2\xc0\x34\x22\xff\x2c\xdf\x7b\xdb\xa7\xc2\xe8\xea\x16\x50\
+\xb5\xf3\x10\x85\x3f\xff\x84\xdf\xef\x89\xdc\x91\x75\xf1\xe0\x2b\
+\x7c\x33\x36\x12\x5d\x9b\x9e\x54\x0d\x7c\x59\x89\x69\x48\xee\x71\
+\xfb\x03\x20\x87\x86\x16\x37\xa0\x34\xa7\xbf\x7d\x72\xa1\x95\x54\
+\xe9\xdd\xe3\x5d\x98\xde\x18\x11\x59\x95\xee\x10\x1b\xfa\x64\x22\
+\x9a\xa4\x8f\x4f\x37\x4f\x09\xf9\x54\x1f\x65\xfa\xee\x44\x54\x85\
+\x50\x22\x4e\x8e\x20\x21\x89\x56\x9e\x0c\x3c\x63\x06\x2e\x51\xc3\
+\xca\xa3\x91\x35\x2d\x08\xa2\x80\xf5\xdc\x7e\x68\xa3\x73\x09\x6d\
+\x6a\xa7\xb9\xa2\x15\xfb\xca\x56\x8c\x7e\x0d\x49\x13\x68\xd3\x45\
+\x69\xb4\x45\x90\x8b\xed\x64\x1c\x9e\x43\x56\x9f\x3c\x0c\x3d\xb3\
+\x08\x2f\x69\x38\x4d\x97\x63\x3a\x4d\xef\xb4\xbf\x0f\x3c\xf9\x9c\
+\xe5\xee\xaf\x42\x5a\x14\x0d\x91\x16\x2d\x84\x4e\x10\x68\x4a\xf8\
+\x31\x88\x7a\x9e\x0c\x3f\xbd\xd4\x38\x28\x27\xed\x5e\x63\x95\x1d\
+\x28\x8d\x21\xa4\x7c\x4b\xc1\x1f\xc5\xd3\x6b\xee\xef\x7e\xe4\xbe\
+\xd6\x4f\xbb\x20\x89\x97\x66\x9e\xdd\xff\xd9\xfa\xdb\xe6\xa7\x7d\
+\x10\x59\xd9\x44\x2a\x87\xd3\xe5\x53\xcc\xc8\xd4\xc5\xd2\x1d\xd3\
+\x83\x0b\x10\x4d\xd2\xab\x1e\x67\x79\x57\xa6\x10\x87\x44\x77\xe2\
+\xf7\x48\x3d\x84\xa5\xbb\x73\x22\x9f\x0c\x3c\x73\xb9\x16\x4b\x3c\
+\xad\x34\x85\x09\x84\x23\x34\x7c\x5b\x81\x6e\x61\x1b\xe6\x0d\x61\
+\xcc\x11\x81\x58\xc2\x87\x86\x82\x43\xce\x25\xa6\x2a\x68\x82\x48\
+\x38\xde\x4a\x6b\xb8\x91\x08\x31\xea\x90\x28\x91\xf3\x08\x15\xe6\
+\xd2\xee\xd2\x93\x35\xbe\x07\xd9\x36\x79\xb2\xa4\x31\xf9\x92\xe0\
+\x1d\x6f\x03\xf7\x3c\x6a\xbe\x7d\xc5\x2e\xae\xbe\x5e\xdf\xc3\x81\
+\x2e\xd3\xb8\xad\x3f\x58\x0d\xc6\x89\x6e\xf6\x21\xe5\x5b\xfa\xa6\
+\x42\xfc\xae\x90\x59\x96\x43\x8b\x10\x64\x31\x2d\xf2\x4a\xd1\x58\
+\xe4\xef\x0f\x80\x38\xe3\x35\x9d\x97\x38\xcc\x63\x0a\x01\xbe\xec\
+\x2a\x64\xb8\xbc\x6e\x83\x16\x4b\x1c\xbb\x33\x9f\x98\x71\x40\x0e\
+\xc0\xb2\x9d\xbf\xff\x66\xf8\xd5\x29\xc0\xdf\x34\x55\xeb\x57\x31\
+\x67\x23\x91\x37\x36\x11\xfc\xae\x86\x3e\xfa\x3c\x14\x54\x54\x2d\
+\x69\xe3\x83\x16\x8d\xac\xbc\x6e\x28\xd9\x76\x74\x16\x09\x51\x10\
+\x30\x44\x0d\x64\xb5\x19\x09\xb7\x04\x10\x9a\xa2\x64\x68\x16\x42\
+\x4d\xed\x88\x8d\x2a\x96\x05\x51\x5a\x9e\xf0\x10\x1f\x9f\x8b\xe3\
+\xcc\x3e\xa7\xda\x9d\x19\xa7\x5e\x17\xfa\xfb\x7f\x80\x5b\x6e\x37\
+\x5f\xbf\x6d\x91\xf0\x5c\xe9\x82\xd6\x57\x94\x97\xbe\xd5\x97\xda\
+\x47\x87\x77\x68\x20\x8f\xae\x6b\xc5\x32\xa6\x70\x60\x17\xea\xaa\
+\x5a\x2a\xb0\x20\x75\x33\xb3\xe3\x73\x4a\x65\x79\x2d\xfb\x03\x20\
+\xb6\x9d\x33\x99\x82\x2c\x62\x3e\xb8\x00\x92\x1c\x1b\x5d\x25\x85\
+\x4a\x43\x88\x78\xfd\x0e\x3c\x1d\x3a\x01\x7d\xcf\x0c\x80\x6d\x55\
+\x43\xd3\x3a\x9e\x3f\x1c\xb8\x4d\x69\x0c\x1d\x59\xb7\xb1\x8e\xe0\
+\x53\x6b\xc9\x5b\x17\x47\x12\x75\xd4\xa2\xa7\x5e\xf4\x93\x33\xb4\
+\x18\xc3\x61\xf9\xe8\x87\xe5\x30\xd8\x69\x45\xe7\x30\xf8\x80\x8d\
+\x24\x4b\x0a\x94\x0c\x70\xf4\x86\x3e\xa8\x5a\xaf\x78\x4b\x18\x65\
+\x8b\x1f\xdf\x77\x35\xa8\x0b\x9a\x89\x6d\x08\xa2\x0f\x81\xf1\xa3\
+\x26\xe2\x1f\x37\xf2\xfd\x58\x33\x3d\xae\x1b\x7d\x8e\x35\xd7\x72\
+\xce\xdf\x43\x8f\xdc\x01\xdc\x7f\x95\xe9\x8a\xad\x39\xf7\xd5\x86\
+\x3e\x59\xa3\xc3\xcb\x1a\xd3\x00\x02\x0c\xec\x42\x3d\x85\x45\xa3\
+\x84\x68\xd7\xa7\xd9\x28\x21\x59\x0d\xa9\xed\x0f\x80\x28\x69\x15\
+\xdc\xa9\x3c\x82\xa0\x13\x48\x65\x00\xbb\x4a\xca\x62\x95\x1d\x69\
+\x91\x93\x94\x63\x42\x2e\xb2\x02\x54\x4e\x0f\xbd\x59\x04\x5c\xa5\
+\xb4\x84\x6f\x88\x90\x60\xdd\x83\xf3\x29\xfd\x2a\x84\x0d\x1d\x71\
+\x54\x94\x3c\x3d\xf9\xa7\x1c\x84\xf9\x78\x27\x89\xa6\x30\xba\x0c\
+\x03\x72\xb1\xed\x8b\x54\x24\x12\x04\x9c\x40\x66\x2a\x93\xdb\x00\
+\x7c\x83\x28\x34\xca\xb9\xe6\xbe\x3a\x9b\xfe\x5a\x87\x24\xc0\xd8\
+\xee\x48\x0e\x23\xc1\x77\x36\x11\x9b\x59\x8d\xd0\x16\x27\xf2\xe9\
+\x66\x16\xcd\xd9\x48\xae\x7b\x04\x03\x2f\x3e\xf8\x6e\x34\xee\x7e\
+\x32\xfc\xf4\x79\xe7\x4a\x17\xbc\x02\x7c\x6f\xe8\x9b\x99\xee\x87\
+\x24\xc3\xf3\xbe\x5d\xa8\x27\x11\x55\xeb\xb4\x6a\x2c\x18\x25\x52\
+\x19\xeb\x2e\x07\x48\xb3\x2e\x2b\xbd\x27\x55\x8b\xab\xa8\x61\x05\
+\xc0\xdc\x85\x03\xef\xb3\x63\xee\x63\x6b\x26\x54\xb4\xc8\x0b\x80\
+\xcb\x13\x2d\x91\xfb\x62\x5e\x1f\x4d\xb1\x10\xca\xed\xcb\xe9\xd3\
+\xa2\x23\x8a\x40\x2c\x47\xc2\x7c\xd1\x00\x2c\x93\xcb\xf0\xbd\xb7\
+\x81\x44\x53\x18\xe3\xc0\x9c\x85\x40\x42\xf5\xc5\x8e\x8c\x6d\x68\
+\x3f\x52\xd9\xe4\x23\x51\x1b\x22\xe1\x8b\xd1\x1a\xf5\x13\x97\x54\
+\xe4\x4c\x13\x8e\xee\x59\xd8\xfb\xe4\x22\xf7\x74\x60\x1a\xde\x6d\
+\x85\x16\x4d\x0c\xf1\xcd\xd8\x84\x96\x2b\x93\xf3\xfe\xd1\xf8\xff\
+\xbb\x9e\xf8\x0b\x0d\x08\x7e\x85\xe8\x93\x2b\xf8\x6e\x41\x2d\xfd\
+\x1f\x3d\x12\x43\x40\x7b\xf9\x15\xe5\xa5\x63\x81\x7f\x1b\x7a\xa5\
+\x03\x24\x5e\xe5\x47\x0d\x2b\xe3\xde\x53\xa7\x9b\xbb\xc8\x5f\xb3\
+\xaa\xa1\x38\x89\xb6\x68\xa7\xcc\x36\x3f\x73\xe1\x6e\x4f\x01\xb2\
+\x41\x5f\xea\x48\x07\x88\xa2\x12\xaf\x09\x60\xe8\x9d\xe9\x04\xd6\
+\x74\x11\x40\xfa\xc5\x36\xa5\x67\xe9\x8d\xfd\xb3\x01\xaa\x3a\x3e\
+\xd8\x74\x9f\x16\x57\xd9\xe8\x6f\xa0\xe4\xe9\x66\x24\x55\x24\xac\
+\xc6\x91\x4e\xec\x4e\xd6\xf5\x43\xf1\x7f\x56\x41\x74\x43\x1b\x19\
+\xa7\xf7\x5d\xa7\xb4\x47\xfa\x36\xbe\xe9\x39\x38\xfe\x79\x0d\xfa\
+\x75\x61\x54\x7f\x0c\x41\x10\x10\x05\x01\xbf\x1a\x66\x43\xb0\x8a\
+\x90\x1a\x43\x16\x75\x18\xd0\xe3\x32\x15\x23\x64\xe8\x69\x72\x89\
+\x43\x1c\xc7\x94\x52\x7a\x6c\x7f\x44\x83\x44\xfb\x9b\x6b\xd1\x64\
+\x95\x83\x66\x9c\xcd\xe6\x3b\xe6\xe0\xfb\xba\x06\xfd\xe2\x66\x56\
+\x9e\xfe\x0e\x3d\x9e\x38\x0a\xdb\xe7\x6d\x7f\x32\xf6\xcf\xf9\x93\
+\x71\x60\x0e\x82\x5e\xb7\x6d\x71\x4d\x69\x0e\xa3\x34\x86\xd0\x3b\
+\xed\x05\xa9\xf5\xa8\x7d\x9e\x2b\x8a\x55\xfb\x3b\xe5\x78\x0c\x7d\
+\x32\x21\xb9\x06\xd6\xe5\x00\x59\x66\x1c\x98\xdd\xe9\x60\x64\x65\
+\x13\xd6\xf1\xdd\x47\x02\x1f\x77\xe5\x14\xb3\xb3\xe5\xaa\xbd\xfa\
+\xab\x29\xa6\x11\x79\x54\x77\xb4\x53\xf6\x42\x33\x9a\x00\x31\xab\
+\x80\xf5\xce\x51\x24\x22\x71\xc2\xab\x9a\x70\x9c\xda\x67\x9d\xd2\
+\x1c\xee\xdb\xf8\xc0\xa2\xbe\x0b\xa7\xcf\x26\xd4\x11\x60\x9c\x75\
+\x20\x5a\xaa\x6e\x5b\x53\x35\x04\x51\xc0\x17\xf0\x13\x69\x0f\x13\
+\x91\x62\xe8\x8c\x66\xcc\x7a\x03\x66\xd1\x40\x53\xab\x8f\xc5\x9f\
+\x7e\x4f\xd3\xcc\x99\x5c\xda\xf7\x14\x7c\x27\x64\x50\xfc\xe7\x61\
+\x88\xa2\x88\xef\xbd\x0d\x14\x4d\x19\x48\xee\xe1\x3d\xd8\xf4\xd0\
+\x3c\x74\x75\x61\x7c\x17\xcd\x21\xf6\xc4\x21\xd8\x3f\xa9\x40\x8b\
+\x29\xe8\xb2\x8c\x28\x29\xdf\x49\x8b\x25\x50\xea\x82\xe8\x9d\xf6\
+\xe2\x2e\x02\xc8\x21\xe1\x25\x0d\x9d\x12\x7e\xe6\xd1\x05\x00\xb3\
+\x7f\xde\x5c\xb5\x67\xb2\xc2\xe0\xca\x46\x97\x9d\x3e\xcd\x04\xe7\
+\x55\x03\x1c\xd5\x45\x11\x8c\x80\xc6\xf1\x4a\x6d\x3a\x91\x5c\xfb\
+\x1b\x6b\x31\x8d\xc8\xc3\x13\x6e\xa0\xc7\x8b\xad\xa8\x02\x28\x85\
+\x06\x32\x5f\x3a\x1c\x31\xcb\x80\xed\xe8\x52\x2c\x87\x14\xe2\x7b\
+\xc9\xd3\xb7\xf5\xe4\xcf\xd9\xf8\xf2\x42\x9c\xd1\x4c\x3c\xc1\x2a\
+\x1a\x62\xed\x48\x48\xdb\xdc\xb6\x44\x48\x21\x73\x72\x1f\x5c\x77\
+\x1c\x41\xd1\x61\xbd\x30\x39\xcc\x98\x04\x3d\x3a\x4d\xc4\x1f\x08\
+\xd0\x10\x6e\xa5\xaf\xa1\x98\x8f\x2b\xe6\xf3\xfa\xa3\xff\x62\xdd\
+\xf1\x6f\xd3\xfc\xe1\x06\x32\xcf\xee\xbf\xde\xd0\x27\x13\x43\xbe\
+\x85\xfe\xd3\x8e\x07\x8b\x4c\xae\x62\x26\x74\xed\x37\xf8\x87\xd9\
+\x48\x74\xc4\x3a\x15\x0f\xc5\x6b\x03\x90\x5c\x9d\xed\x0a\x39\x31\
+\xf0\xb9\x37\xfd\x41\xdb\xf4\x98\x46\xe4\x01\x7c\xb3\x3f\x00\xd2\
+\xa6\x73\x18\xde\x31\x8f\x2a\x48\x77\x9d\x97\x37\x11\xdb\xec\x1b\
+\xeb\xf2\xba\xfb\x74\xc1\xa0\xed\x6a\x20\xd6\xc9\x6c\x76\xbb\x75\
+\x34\x9b\x2c\x7e\x06\x4c\xf3\x13\x45\x41\xe9\x6e\x24\xb3\x7c\x1c\
+\xba\x1c\x23\xa6\x61\xdd\x3e\x53\x5a\x23\x34\xfe\xe5\x2b\x62\x4f\
+\x7b\x10\xfd\x09\x72\x74\x76\x54\x55\x63\xb8\xb9\x27\xf3\x3a\xbe\
+\x27\xa1\x25\x10\x48\xba\xf7\x0a\x09\x32\x26\xf7\xa2\xdf\x95\xe3\
+\x18\xff\xf6\x9f\xc9\x9f\x32\x18\x82\x0a\xaa\x01\x8c\x07\xe7\xa3\
+\x3a\x24\x42\x91\x08\xeb\xc2\x55\x9c\x66\x3f\x9c\xc6\xfa\x26\x56\
+\xdd\xf9\x09\xeb\xa7\x7e\xdc\x47\x97\x69\xc4\x7a\x94\x73\x85\xbe\
+\xc0\xc2\x90\x17\x27\xa3\xda\x25\xa4\x60\x82\xc4\x8d\x4b\xd1\xa6\
+\xf4\xc0\x71\x52\xef\x74\x80\xd4\xf8\x49\x39\xc5\xfb\x7e\x29\xa2\
+\xda\x7f\x6c\xaa\xd5\x62\x9b\x58\x0e\x29\x44\xca\x36\xbd\x4f\xb2\
+\xac\xa0\xcb\x01\x02\xf0\xba\x63\x72\xaf\x74\x73\x1f\x4b\xe0\xfb\
+\xdf\x06\x7e\xee\x82\xd0\x4f\x94\x71\x09\x7f\x8c\x44\xfb\x76\xc7\
+\xcb\x71\x5a\x1f\x82\xa3\x6c\xb4\xdd\xb5\x80\x28\x71\xe2\x79\x32\
+\x59\xcf\x8d\x45\x2e\xb4\xcc\xd3\x39\x0c\x2f\x86\x57\x35\x4f\x6c\
+\x3d\xe7\x2b\x84\x15\x3e\xd4\x84\x46\x42\x55\xc9\x91\xec\xf8\x23\
+\x41\x7a\xcb\x85\x84\xd4\x28\xdf\x87\xbd\x18\x44\x29\x69\x8e\x33\
+\xf5\x18\x5d\x59\xdb\x01\xbf\xa6\x91\x96\xa8\x0f\xa5\x9f\x95\xc1\
+\x6f\x9e\xca\x8d\xdf\x3e\xce\x80\x27\x8f\xa3\xaf\xa5\x14\x6f\xa4\
+\x11\x01\x30\x20\xd3\xf4\xd5\x66\xd6\x9d\xf1\x3e\xd1\xcd\xbe\x21\
+\xfa\x9e\x19\x1f\x18\x07\x64\x63\x79\xf4\x20\x34\xa3\x8e\xdc\x36\
+\x89\x55\xd7\x7c\x4c\xee\xfd\x87\x22\x17\x5a\xb7\x87\x81\xf5\x21\
+\x48\xee\x2c\xb1\xaf\xe5\xfc\xf6\xb7\xd6\x6f\x0d\x18\xb6\xeb\xea\
+\x94\xde\x00\xaf\xfc\xfc\x70\x68\xcf\xe5\x13\xcb\xe1\xdd\xb7\x86\
+\x98\xdb\x4d\xfe\x9b\x6b\x49\x74\xc4\x6e\x73\x79\xdd\x19\xfb\x70\
+\x7a\xb9\x2d\x5e\x1b\x98\x51\x7f\xf3\xbc\x6d\x8e\x9e\x68\xd3\x93\
+\x7f\xdf\x58\xee\xba\xe4\xaf\x4c\x5c\x79\x2d\x93\x2b\xff\x86\xfd\
+\xbe\x83\x90\x0b\x2c\x6f\x00\x9f\xfb\x17\xd5\xfd\x65\xcd\x05\x1f\
+\xd0\x51\xdb\x9e\x16\xee\x29\xa8\xf4\x19\xdc\x87\x0e\x4b\x8c\x83\
+\x75\xbd\x98\x5b\xb7\x94\xc6\x40\x2b\x62\x44\xc5\x34\x38\x07\x43\
+\x6e\x32\x8f\xd4\x52\xd7\x4c\xf3\xaa\x1a\x34\x0d\xe4\x11\x49\x7f\
+\xcb\x94\x65\xc1\x51\x90\x41\x6e\xcc\x8a\x8a\x86\x51\xd0\x23\x21\
+\x91\x21\x9a\x31\x55\xc5\x59\x72\xf6\x5b\x44\xd6\xb7\x9d\x20\xe8\
+\x75\x77\x5a\x46\xe6\x2f\x2e\xb8\xe5\x60\x5a\x94\x00\xf6\xb5\x41\
+\x16\x3c\xfc\x05\x59\x17\x6f\xe7\x35\xeb\xf8\x60\x13\xed\xaf\x79\
+\x2e\x71\x79\xdd\xaf\xb9\xbc\xee\xb1\xfb\x48\x55\x19\x89\x8e\xd8\
+\x9d\xa9\xa6\xf1\x6d\xa2\x77\xda\xb1\x8e\xef\xce\x9e\xf8\x86\x7b\
+\x03\x90\xa0\x68\x96\x6e\xc9\x3c\xab\x7f\x7a\x82\xa4\x31\x84\xef\
+\xcd\xb5\xb0\x87\x15\x4c\x3b\x01\xa3\x97\xcb\xeb\x7e\xd6\xff\xc9\
+\x96\xbf\x57\xfc\xf1\x3d\x02\x3b\x34\x48\x65\x5f\x32\x84\xaf\xff\
+\x39\x9b\xeb\x03\x47\xf3\x74\xc9\x95\x94\xdf\xf7\x0f\xcc\xc3\xba\
+\x3d\x0b\x7c\x1a\xd9\xd8\x7e\x77\xcb\x75\x0b\xb0\x84\x45\x3a\x62\
+\x41\x12\x5b\xd7\xc4\x12\x1a\x82\x43\x26\xef\x95\xa3\x18\xf4\xf1\
+\x99\x8c\xfe\xd7\x19\x1c\x7c\xf5\xb1\xd4\xf6\x4f\x20\xc8\x3a\xf4\
+\x23\x73\xb7\x9d\xbf\x61\x49\x05\x34\x46\x31\x19\x8d\xd8\x46\x6e\
+\x9f\x4a\x5b\xbf\xa9\x82\xa8\x86\x28\x08\x98\x04\x03\x16\x9d\x11\
+\x9f\x12\xa2\x3a\xd2\x42\xac\x25\xc0\xca\xa9\xef\x13\x6f\x08\xdd\
+\x05\xdc\x6d\x3f\xb9\xd7\x2c\x79\x52\x11\x71\x34\xc2\xff\x59\x4d\
+\xdb\x40\x13\xb6\x89\xa5\xc9\x5b\xf1\x45\xa9\xbb\x79\x1e\x35\x57\
+\x7e\x79\x46\xa2\x25\x32\xd7\xe5\x75\x5f\xb3\x0f\x00\x72\x99\x6f\
+\xfa\x3a\x94\x86\x74\x7f\x27\xf3\x9c\xfe\x08\x46\xe9\xfa\x9f\x9b\
+\x03\xd9\x5b\x80\x00\x94\x67\x9c\xd9\x0f\x29\xc7\x94\x76\xb0\xf5\
+\xc5\x55\x24\x7c\xd1\x7b\xf7\xc6\x17\x71\x79\xdd\x7f\xd2\x62\x89\
+\x0d\x0d\xf7\x7c\x3b\xb5\xfa\xe2\xcf\xd3\x06\x6d\x1c\x94\x83\x76\
+\x7c\x11\x9b\xcb\xe7\x60\xd0\x19\x38\xe7\xe8\xd3\x18\x74\xf1\xb8\
+\x0f\x81\xe7\xd4\x50\xfc\xa5\x35\xd7\x7d\xcc\x96\xc6\x2a\x32\x65\
+\x2b\x85\x86\x4c\x88\xaa\x68\x8a\x8a\x16\x56\x90\xfa\x65\x80\x43\
+\xc6\xdc\xcd\x86\xf1\xa8\x22\x8e\xf9\xfb\x99\x1c\xf6\xee\xf9\x64\
+\x7e\x72\x34\xa6\x53\xcb\xb6\x27\xb3\x36\xb4\x23\xc6\xc0\x62\xb7\
+\x20\xa7\xa6\x1d\x05\x8d\x8e\x25\xb5\xa8\x22\xc8\xa2\x0e\x49\xd0\
+\x11\x4a\x44\x08\x69\x51\x44\x04\x64\x74\x74\xd4\xb4\xd2\x72\xf3\
+\x37\xa0\x6a\x33\x80\xab\xf3\x6f\x1e\x8d\x5c\x64\x45\x44\x65\xe5\
+\xa3\x73\xc8\x3c\x3b\xfd\x85\xea\x78\x7f\x23\x15\xa7\xbc\x4f\xe8\
+\xdb\xba\xc7\x5c\x5e\xf7\x13\x2e\xaf\x7b\x4f\xf3\x48\x7d\x12\xed\
+\xd1\x7b\x5a\x9e\x4f\x2f\xde\x92\x0b\xad\x64\xfc\xa9\x1f\x24\x2b\
+\xe5\xd8\xdf\x00\x69\xd1\x65\x1a\xff\x96\x7d\xc9\x90\x74\x07\xac\
+\x2e\x48\x8a\x6c\xf6\xf2\x3d\x04\xc7\x1d\xb1\x8a\x8e\xd7\x2b\xcf\
+\xfe\x88\xd6\xe7\x3b\x57\xab\x65\x9e\xd3\x9f\x25\x8f\xcd\x46\x0a\
+\xa8\xcc\x8b\xac\x41\xba\xce\x05\xc9\x25\xf8\xa9\x8b\x6f\xff\x88\
+\xd7\x17\xcd\x40\x1f\xd4\xb0\x5f\x3c\x00\xeb\x29\x3d\xd1\x0f\xcd\
+\x46\x2a\xb2\x20\x66\x1b\x90\x5d\x9d\x67\x3e\x9d\xa8\x43\x57\x62\
+\x45\xcc\x32\x6c\x3b\x36\xe0\xa2\xb1\x8c\xfe\xe2\xcf\xf4\x7e\x6c\
+\x22\x64\x26\xfb\xae\xdb\x1b\x9b\x89\x2d\x6d\x46\xd4\x09\xe8\x05\
+\x89\x04\x2a\x31\x2d\x4e\x87\x12\x24\x8e\x42\x54\x55\x10\x54\x81\
+\xfa\xef\x2a\xa8\x4b\xf2\x94\x5d\xa0\xb3\xeb\xaf\xec\x7d\xcd\xa1\
+\x44\x54\x85\x9c\x25\x7e\xd6\xf9\xea\xb1\x9f\x98\x4e\x36\x13\xdb\
+\xe2\xa3\xf2\xec\x99\xb4\x3e\xbf\xf2\x2a\xa0\xdc\xe5\x75\x1f\xba\
+\x07\x6a\xbb\xbc\xf9\xa9\xa5\xdb\x42\xe9\x6d\x96\xf6\xd2\xa1\x88\
+\x36\xfd\x2d\x24\x8b\x98\xf6\x3b\x40\x00\x1e\xce\x3c\xa7\x3f\x86\
+\xde\xe9\xd9\xc2\xb6\x97\x57\x13\x5e\xd2\x70\x85\xcb\xeb\xbe\xec\
+\x67\x00\xc3\xea\xf2\xba\x9f\x09\x7c\x59\x79\x97\xf7\xd4\x19\xec\
+\xec\x89\x03\xc8\x25\x76\x22\xa3\x32\x71\xce\x8f\xe1\x32\x97\x72\
+\xe2\x69\x93\xb1\xf4\xcd\xfe\x07\x60\xf2\xcd\xab\x9a\x6a\xff\xd4\
+\xc7\x0d\xb9\xa7\xe2\xb2\x97\x22\x0e\x70\x20\xe6\x18\x30\x8c\x2b\
+\xc0\x7a\xf9\x00\x0c\xe3\x0a\x08\xbd\xba\x81\xb6\x3f\xcf\x26\xf0\
+\xcc\xf7\x24\xea\x76\x9f\xc4\x94\xad\x06\xb2\x86\x15\xe2\x38\xa6\
+\x07\xa9\x20\x07\x93\x64\xa4\xf8\xd8\x81\x08\x19\x32\xba\x80\x8a\
+\xa0\x82\x4f\x4d\x4e\x61\x02\xa0\x0a\x1a\x31\x14\xec\x82\x11\x75\
+\x7a\x25\x31\x4f\xdb\xb5\xc0\xb2\xcc\xe3\x7a\x7e\x93\x37\xb4\x14\
+\x49\x27\x51\xf5\xf2\xe2\x4e\x56\x64\x6b\x3e\xa7\xe1\x9e\x6f\xa9\
+\xbd\xea\xcb\xb3\x54\x7f\x6c\xbe\xcb\xeb\xbe\xe0\x67\x3c\x83\xa9\
+\xe1\xa5\x0d\x57\xb4\xbd\xbc\xba\x53\x12\x31\x65\x3d\x1e\xdd\xf3\
+\x9c\xfd\x5e\x8a\xc7\x59\x1e\x12\x8c\xd2\x15\x79\x77\x1c\xd2\x29\
+\xb3\x5a\xf7\xd7\xb9\x24\xda\xa3\xcf\xb8\xbc\xee\x73\x7e\x02\x38\
+\x5c\xc0\x43\x2d\xff\x5c\x71\x59\xd5\x5f\x3e\xed\xb4\xc3\x82\x94\
+\x6b\xc6\xe0\xca\xc2\xf1\xc7\x5e\xd4\xbc\xe7\xc1\x29\x64\x50\x60\
+\xc8\xc4\x74\x56\xaf\x6d\xd6\xa3\xfe\xd9\x65\xf8\x12\x21\x6a\x42\
+\xad\xb4\x65\x29\xe8\x07\x64\x61\x3a\xa3\x17\xba\x12\x2b\x52\x2f\
+\x3b\xf1\x95\xad\x68\x41\x85\xe8\xfc\x06\x82\x0f\xae\xc4\x77\xd5\
+\x82\x64\x3f\xf0\x4f\x14\x4b\x96\x8d\xc1\x4f\x1c\xc7\xa1\x5f\xfc\
+\x85\xc2\xcb\x46\x80\x00\x6a\x54\x45\xd2\xe9\xd0\x09\x3a\x44\x04\
+\x0c\x82\x4c\x7d\xac\x9d\xf5\xfe\x2a\xbc\xcf\x2c\x84\x24\x4f\xd9\
+\xf3\xbd\xce\x1d\x45\x5c\x4b\xa0\x5f\xe5\xa7\x4e\x0c\x60\x1e\x5d\
+\x80\x69\x78\x5e\xa7\x6b\xf8\xde\xdb\x48\xaa\x43\xe0\x5f\xa9\x5e\
+\xde\x1f\x93\x01\x6a\x58\x79\xb6\xee\xe6\x79\xe9\x65\x86\xa2\x40\
+\xde\x9d\x63\x10\x64\xf1\x62\x92\xcd\xdf\xfc\x52\x16\x04\x8f\xb3\
+\xfc\x19\xcb\xb8\xe2\xe7\x33\xce\xe8\x97\x76\x3c\xba\xbe\x8d\xaa\
+\x0b\x3e\x41\x8b\x26\x5e\x71\x79\xdd\xfd\x7e\x00\x1c\x93\x34\x45\
+\x5d\xd3\x70\xe7\x82\xa9\x8d\xf7\x2d\xec\x94\x01\x34\x0e\xca\xc1\
+\xf9\xd6\x09\x98\x47\x15\x90\x73\xcd\x08\x32\xbe\xf2\x13\x41\x41\
+\x1e\x9e\x8d\xd1\x95\xe5\x01\x72\xfc\xf3\x6b\xfe\x50\xbf\xd4\x8b\
+\x41\x90\x31\xc8\x32\xb5\x2d\xf5\xb4\x3d\xb4\x8c\xe8\xc7\x55\x48\
+\xfd\x32\x48\x34\x84\x51\xaa\x02\xa0\xd7\x21\x9a\x64\x9a\x8c\x11\
+\x22\xcd\x41\x88\x76\x2e\xea\x6a\x5e\xe8\xa5\xe9\x93\x0d\x84\x1b\
+\xfc\xdb\x1d\xdc\x1d\xc4\x94\x67\xa3\xe8\x96\x43\xe8\xf5\xda\xf1\
+\x14\x96\x16\x91\xab\x58\xd1\x09\x02\x71\x2d\x81\x24\xe8\xd0\x34\
+\x90\x54\x89\xea\x59\xeb\x09\xaf\x69\x39\x13\x58\x99\x39\xb1\x07\
+\xfa\x02\x1b\xb2\x26\xb2\x7a\xc6\x62\x6c\x47\xf7\xa0\xf0\xd1\xc3\
+\xc9\xbb\xfd\x90\x64\x0f\xcc\x8e\xf9\xa4\x15\x4d\x78\x4f\xff\x80\
+\xd0\xc2\xba\xfb\x5c\x5e\xf7\x23\x3f\xe6\x98\x36\xde\xb7\xb0\x53\
+\xf5\x58\xe6\xd9\xfd\x31\x8f\x2e\xf8\x07\xb0\x57\x45\xaf\xfb\x92\
+\x1f\x64\x5a\xde\xad\xa3\xd1\x97\xa5\xaf\xd1\x84\x97\x36\xd0\x78\
+\xff\x42\xd8\x0d\xc9\xbd\xcb\xeb\xfe\x4b\xc2\x17\x9d\x59\xed\xfe\
+\x8c\xd6\x7f\x7f\xdf\xe9\x73\xc7\xa9\x7d\x70\xbe\x79\x02\xfa\x1e\
+\x8e\xff\x09\x7a\x1d\xad\xab\xea\xb1\x55\xc6\x48\xa8\x1a\xba\x89\
+\x85\x5b\x33\x83\x7f\xf6\xbe\xb9\x1c\x04\x0d\x50\x59\xde\xb2\x11\
+\x79\x62\x11\x59\x37\x8d\x20\x3a\xaf\x9e\xe0\x53\xdf\x13\x7e\x7d\
+\xd3\xb6\x8c\xa9\x86\x86\x4e\x01\xc3\xc0\x6c\x30\xea\x3a\x5d\xb3\
+\xea\x1f\x8b\x59\x72\xc6\x74\xe6\x1c\xf9\x02\x2b\xcf\x7d\x97\x45\
+\xef\xcc\xdd\xde\xb1\xb3\x23\x50\x86\x77\x63\xf0\xbf\xfe\x88\x94\
+\x69\xa4\x3d\x1a\x20\xaa\xc5\x11\x11\x31\x08\x32\x76\xc9\x44\x86\
+\xce\x4c\xcb\xdb\xeb\x00\xce\x10\x65\x91\x6e\xe3\x7b\x22\x09\x32\
+\x96\x6f\x83\x64\x9c\xd7\x9f\x84\x3f\x46\xd6\x85\x83\x9e\x29\x79\
+\xe9\x58\xa4\x7c\x4b\xa7\x4c\x6b\xd5\x79\x1f\xe3\x7b\x77\xc3\x75\
+\x2e\xaf\xfb\x59\x97\xd7\x6d\xdf\x85\xee\x1e\xe8\x78\x7f\xe3\xd4\
+\xb6\x57\xd2\xa7\x96\x64\x0b\xc8\xbe\x21\xef\xdb\x67\x00\xf1\x38\
+\xcb\x97\x89\x36\xfd\x35\x85\x8f\x8e\x4f\x76\x86\xed\x18\xd5\xbc\
+\xf4\x3d\xbe\xf7\x36\x4e\x75\x79\xdd\x0f\xed\x34\xc0\x1b\xe2\x55\
+\xfe\x17\x2a\xcf\x9a\x49\x60\xd6\x4e\x3d\x1d\xa2\x40\xb7\x5b\x47\
+\x53\xf8\xe8\xf8\x97\x45\x8b\x3c\x1a\xb0\xe8\xcb\x1c\xd4\x7f\xbb\
+\x05\xa3\xa8\x47\x91\xc1\x78\x68\x01\xc0\x92\x58\x6b\xf8\xdc\x8a\
+\x05\x1b\x40\x85\x75\xe1\x5a\x04\x19\x7a\x4f\x1c\x4c\x68\xa6\x17\
+\xc7\xa3\xa3\xb1\xdf\x3d\x72\x5b\x89\x80\x80\x40\x54\x8b\x13\x8e\
+\x47\x31\x8f\xec\x6c\xe2\xa3\x81\x08\xcd\xeb\x6a\x49\xd8\x74\xf4\
+\x8c\xe7\x50\xf9\xd5\xf7\x7c\x7a\xe1\xbf\x98\x71\xea\x33\xf8\xeb\
+\xda\x3b\x7d\x5f\xdf\x3b\x83\xbc\x0b\x86\x12\x0a\x86\xb1\x88\x06\
+\x34\x55\x45\x10\x20\x9c\x88\xa3\x24\x12\x74\xcc\xa9\x44\x8b\x25\
+\xae\x43\x14\xd6\xe5\x1c\x59\x46\x42\x4b\x90\x5f\x27\x51\xb5\xb6\
+\x6a\x6b\x45\xea\x7b\x96\xc3\x8b\x6f\x73\x4e\x3f\x61\x6b\x2a\x7c\
+\x9b\xa8\x61\x85\xda\xab\xbf\xa2\x65\xda\xf2\xa9\xc0\x03\x2e\xaf\
+\xbb\xd7\x0e\xba\xbb\x3a\xb2\xa6\xe5\xa6\xba\x5b\xd2\xcb\x0a\x45\
+\x93\x44\xe1\x23\x87\x23\x5a\xf5\x57\xfd\xdc\x85\xb9\xae\xb6\x20\
+\x78\x9c\xe5\x4f\x98\x86\xe7\x3d\xd1\xed\xc6\xce\xcc\x8f\xf5\xb7\
+\xcc\x23\xb2\xaa\xf9\x06\x97\xd7\x7d\x43\x6a\x80\x7f\x8b\x78\x5a\
+\x1e\xf2\x9e\xf1\x21\x3b\xd7\x98\xea\x32\x0c\x74\x7f\x7e\x22\xd9\
+\xee\xc1\xff\x00\x2e\xf3\x38\xcb\x17\x02\x76\xe3\x31\x4e\xe4\x95\
+\x41\x34\x41\x25\xd1\xd3\x82\xde\xac\x07\x28\xaa\x59\xb0\x91\x98\
+\x2f\x84\x26\xa8\x44\x95\x28\x99\x05\x39\x58\x87\xe6\x21\x0d\xcd\
+\x44\x59\xd3\x46\xeb\x05\xb3\x89\x2d\x6e\x42\x30\xe8\xd0\xd0\x30\
+\x20\x53\xe8\xc8\x46\x1a\x9a\xd5\x19\x20\x1b\xda\x09\xd5\x75\x10\
+\xd5\xc5\xc9\x36\xda\xd9\x44\x23\x3d\x32\xbb\x93\xf5\x4d\x9c\x8d\
+\xb7\x7f\xb5\xcb\x71\x3b\xc6\x97\x60\xb3\xda\xb0\x0a\x26\x0c\xa2\
+\x4c\x5c\x53\x90\x74\x22\x11\x35\x86\xaf\xa6\x05\xff\x77\x75\x00\
+\x51\xeb\xd0\x3c\xf2\x0b\xf2\xc8\x91\x6d\x34\xaf\xa8\x43\xec\x97\
+\x01\xe0\xf0\x38\xcb\xef\xd5\x3b\xed\xe7\x94\xbc\x7a\x5c\xa7\x94\
+\x3c\x40\xe3\x03\xdf\xd1\x70\xcf\xb7\x53\x81\x5b\x5c\x5e\xf7\x41\
+\x2e\xaf\xbb\x97\xd2\x1c\x7e\xbc\xe6\xb2\x2f\x3a\xb5\xa0\xda\x8e\
+\x2e\xc5\x34\x3c\xef\xbf\xc0\x53\xfb\xe2\x99\x76\x05\x05\xd5\xad\
+\x59\x17\x0d\x7e\x63\xe7\x50\x4e\x0d\xc6\xa9\x9e\xfa\x39\xf1\xba\
+\xe0\x43\x2e\xaf\xfb\xfe\xd0\xa2\xfa\x3b\xab\xce\xfa\xa8\x53\xbb\
+\xa6\xbe\xcc\x41\xc9\xeb\xc7\x63\x3d\xd2\x79\xaf\xc7\x59\x7e\xb9\
+\xc7\x59\xbe\x35\x6e\x73\xb4\x6f\x69\x41\xac\x08\x21\x20\x10\x18\
+\x68\xde\xaa\x9c\x81\x75\xf3\x36\x63\x12\xf5\x84\xd4\x28\x3a\x51\
+\x47\xa4\x2d\x4c\xc3\xd3\xcb\xa0\x3a\x8c\x58\x68\xc6\x76\xd5\x20\
+\xb4\x84\x86\x40\xb2\xaa\x4a\x50\x35\xa4\x7c\x33\x52\x2f\x47\xa7\
+\x9b\x0f\x2f\x6d\xc4\x91\x30\x30\xd0\x52\x4a\x4c\x8b\xd3\x14\x6f\
+\xa7\xaf\xbe\x08\xd1\xa6\x27\xfa\x7d\x0b\xf1\x70\x67\x7f\xcf\x9c\
+\x65\x45\x36\xea\x51\x35\x8d\x98\xa6\x6c\x2b\xdd\x12\x05\x50\x05\
+\x08\x26\xa3\x31\x4b\x74\x5d\x2b\xe6\x3e\xd9\x04\x12\x61\x82\xab\
+\x1b\x69\xa9\x6e\x86\x64\x4b\x29\x1e\x67\xf9\xab\xa2\x59\x3a\xb4\
+\xf0\x89\x09\xaf\xe6\x5c\x31\xac\xd3\x35\x5a\x9f\x5f\x49\xdd\x0d\
+\x73\x2e\x20\xa1\x7d\x07\x5c\xd9\xf2\xdc\x0a\x76\xae\x8d\x91\xf2\
+\xcc\xfb\x9c\x17\x76\x9f\x03\x24\x55\x00\x33\xad\xe0\xc1\xc3\x3b\
+\x79\xe9\xf1\x2a\x3f\x75\xd7\xcd\xc6\xff\x59\xc5\x5f\xab\x2e\xf8\
+\xa4\xd3\xc2\x9b\x71\x40\x0e\x25\xff\x3d\x0e\x63\xff\xec\x6b\x3c\
+\xce\xf2\xdb\x76\x3a\xb5\x35\x54\xe1\xc3\xd4\xaa\x12\x51\xe3\xc8\
+\xbd\xac\x5b\xa7\x8d\x1e\xb1\xf5\x1d\x88\x80\xa2\x25\x50\xe3\x09\
+\xb2\xfb\xe4\x91\x75\x4c\x19\xc8\x22\x1d\x77\x2e\xc1\x77\xfb\x62\
+\x04\xbd\x8e\x90\x1a\x43\xd5\x34\xb4\x98\x8a\xe4\xca\x40\x30\x77\
+\xae\x76\x68\x9b\x5b\x41\x77\x5d\x0e\xf9\x92\x83\x66\xc5\x87\x2c\
+\x48\x94\x18\xba\xa1\x85\x15\x32\x9d\xb9\x48\x26\x7d\xa7\xdf\x28\
+\x6d\x11\x74\xb1\x24\xfb\x8d\x84\x48\x96\xce\x8a\xa0\x09\xc8\x82\
+\x84\xaa\x69\x34\xac\xad\x06\xb0\xc4\xeb\x82\x18\xcb\x32\x70\x48\
+\x16\x6c\xb5\xe0\xab\x6c\x4b\x5b\x8f\xf1\x38\xcb\x17\x00\x7f\xcd\
+\xbd\xfe\xa0\x69\x79\xb7\x8d\xee\x74\x9d\xf6\xe9\xeb\xa8\xbd\x69\
+\x0e\xbe\x77\xd6\x5f\xd1\xf6\x52\xba\xbf\x26\x48\x22\x85\x4f\x1c\
+\x81\x5c\x6c\xbb\x1d\xf8\xfa\x57\x0b\x90\xd4\x40\xe7\x8a\x66\xe9\
+\xdc\xe2\xe7\x8e\xda\xda\xaf\xb2\x3d\x3f\xff\x75\x0d\xd5\x17\x7d\
+\x86\xea\x4f\x7f\x13\xcd\xa3\x0b\x29\x79\x75\x12\x72\xa1\xd5\xed\
+\x71\x96\x3f\xb1\x8b\xd3\x9a\xa8\x0c\x22\x87\x54\xda\x94\x20\x79\
+\xdd\x73\xd1\x12\x1a\x5a\x34\x31\x48\x6d\x4c\x1a\x99\xb8\x9a\x40\
+\x96\x65\xca\xee\x1c\x8f\xa1\x31\x81\xaa\x24\x70\xdc\x3d\x12\x35\
+\x9e\x40\xd0\x20\xa6\x26\xf8\xc2\xb7\x94\x5b\xbd\x2f\xe2\x18\xd9\
+\x79\x9d\x2c\x81\x8a\xdc\x3b\x03\x87\xab\x1b\x2d\x6a\x00\x7d\x44\
+\x87\x18\x4a\xb0\xa5\xb5\x96\xbc\xb2\x7c\x9c\x37\x1e\x82\xb0\x8b\
+\x1b\x33\x7c\x1f\xc4\x1c\xd5\x61\xd1\xe9\x29\xd0\x67\x61\x12\x0d\
+\xe8\x10\x69\x8c\xfb\xa8\x8f\xb7\xe0\xab\x69\x01\x30\x26\xda\x22\
+\xe8\x8b\x6c\x84\xd4\x28\x62\x53\x94\x44\x43\x18\x92\xcd\xe9\x3b\
+\xea\xae\x06\xb8\x2e\xeb\xa2\xc1\xd3\x0a\x1f\x9f\xb0\x8d\xf1\x60\
+\x5b\x18\xfc\xd6\x7a\x6a\xaf\x9d\xdd\xa9\x73\x2e\xef\xae\x31\x58\
+\xc6\x14\x3e\x05\xdc\xb3\x2f\x9f\x65\x97\x51\x50\x79\x9c\xe5\xff\
+\x71\x79\xdd\x39\xc5\x2f\x1c\xfd\x58\xe5\x19\x1f\xfe\x20\x99\x8a\
+\xf5\x48\x27\x45\x4f\x1d\x81\x68\x91\x77\xd9\xaa\xe9\xf2\xba\x45\
+\x34\x72\x1d\x6d\x02\x82\xa0\x23\x98\x88\x20\x65\x99\x40\xd5\x48\
+\x04\xe3\x04\xfc\x01\xcc\x82\x48\x22\x91\x40\x70\xe8\x71\x74\xcb\
+\xa0\xe3\x6f\xdf\x11\xab\xf0\x91\x3f\x63\x12\x2d\x96\x28\xba\xb6\
+\x08\x82\xa4\xe3\x60\xab\x8b\x5e\xdd\x0b\x50\x87\x38\xd8\x39\x7e\
+\xd1\x21\xd2\xf3\xb6\x71\x68\xd1\x04\x26\x6f\x3b\x5a\x4d\x98\x33\
+\x5a\x07\x92\x9d\x9d\x49\xce\x50\x27\x64\x74\x56\x97\xa6\xa8\x04\
+\xdf\xdd\x4c\x9e\x25\x9b\x0e\x21\x42\x63\xdc\x47\x44\x8d\x21\x09\
+\x3a\x64\x41\x44\xc0\x88\x35\x28\xa3\xc5\x12\x19\x5a\x48\x41\xcc\
+\xb7\xe0\x8f\x87\xa9\x6f\x0f\x63\x6b\xce\x84\x24\x83\xc1\xce\xba\
+\x0b\x03\x97\xba\xbc\xee\x98\x68\xd3\x5f\x55\x7b\xe5\xac\x1f\xe4\
+\x1d\xc9\xbe\x74\x28\x99\x67\xf7\x9f\x06\x5c\xb5\xaf\x9f\x63\x97\
+\xd2\x60\x7a\x9c\xe5\x8f\x1b\x7a\x65\xdc\xd1\xfd\x95\x63\x91\xba\
+\xed\x7a\x89\xc1\x71\x52\x6f\x8a\xff\x79\x14\xa2\x45\x9e\xf4\x03\
+\x7d\xbc\x02\x80\x81\xa4\x93\x19\xd2\x62\x60\xd0\xa1\x01\x89\x88\
+\x82\x4d\xd3\x13\x51\x63\x04\x89\x22\x37\x28\xb4\xcf\xf6\x62\x9a\
+\x31\x9e\xbc\x05\x27\x53\xbb\xbe\x0a\xc5\xeb\xc3\xa6\x37\x23\x0a\
+\x02\x39\x82\x0d\x57\x49\x6f\x84\xde\xb6\xdd\xde\xb7\x60\xd0\x61\
+\xe9\x93\x8d\x75\x42\x31\x7d\x4f\x19\x49\xce\xf8\x9e\xbb\x04\x07\
+\x1a\x04\x1f\x5c\x41\xc7\xd2\x3a\x9a\x45\x3f\x09\x21\x81\x41\x27\
+\x51\xa3\x34\x53\x1d\x6f\xc6\x28\x1a\xe8\xa6\x77\xa0\x57\x74\x49\
+\xce\x13\x45\x45\xb0\x48\xd4\xc5\x5b\xe9\xf0\xfb\x50\x93\xa5\x0b\
+\xd6\x1f\xd0\xdf\xd5\xb6\xa3\x9c\x8f\x74\x7f\x79\x52\xa7\xe2\xac\
+\x1d\xf3\x1d\xdd\x6e\x1a\xf5\x1c\xd0\x25\x9b\x15\xec\x0f\x96\xc3\
+\xa0\x68\x92\xb7\x56\xbc\xef\xf2\x61\x08\x92\xf8\x06\xc9\xaa\xf2\
+\x1f\x14\x01\x88\x69\x0a\x71\x4d\x41\x44\x40\x15\x00\x41\x43\x10\
+\x04\xda\x94\x20\xb5\x81\x66\x8a\x06\x0c\x21\xb4\xb8\x81\x8a\xd9\
+\xab\x59\x63\xab\x67\xdc\xdc\x5c\x2a\x12\x8d\x98\x30\x11\x4e\xc4\
+\x89\x84\xc3\x14\x1e\xe9\x42\xb4\x76\xe6\xb5\x55\xbe\x6e\x44\x97\
+\x63\x42\x2b\xb3\x20\xca\x3f\xfc\xee\x44\xbf\xac\x21\xf4\xd4\x1a\
+\x1a\x56\x54\xd1\x24\x07\x31\xaa\x32\x9a\x00\xc1\x44\x84\x4c\x9d\
+\x1d\x93\xa8\xc7\x2a\x1a\x51\xb4\x04\x6b\x23\x95\xf4\x4f\x0d\x3f\
+\xa6\xc6\x89\xa8\x31\x64\x9d\xb4\x95\x95\xf9\xc7\xda\x2f\x63\xa2\
+\x59\x42\x10\x76\xa3\xbf\x64\x1e\x47\xfb\x29\xfa\xfb\xd5\x59\x10\
+\x97\xd7\x7d\x96\xd2\x10\x7a\xb4\xea\xbc\x8f\x76\xb9\xe7\x2c\x24\
+\x4b\x06\x6b\xae\x98\xf5\x27\x2d\x96\x98\xe3\xf2\xba\x8f\xdb\xcd\
+\xa9\x34\x80\xb0\x90\x20\x90\x08\xa3\xd7\x24\xb4\xb0\x82\x00\x48\
+\x66\x3d\x92\x5e\xc2\x1f\x0f\x93\x63\xc8\xe0\x88\xe7\xce\xc6\xd1\
+\x33\x97\x8c\x36\x03\x23\x4e\x19\xcb\xfc\xc0\x6a\x0e\xb5\x0d\xc0\
+\x13\xaa\x44\x14\x05\x96\x05\x36\xd1\x31\xdc\xd0\xe9\x02\x71\x5f\
+\x84\x79\x97\xbc\xce\xa2\x49\xaf\x30\xf3\xc8\xa7\xf8\xf2\xa6\x37\
+\x50\xbf\x6e\x02\x65\xd7\xcf\x4f\x57\x68\x41\xd3\x8b\x08\x08\xf4\
+\x32\x15\xa0\x43\x47\x42\xd5\x88\x69\x09\xda\xe2\x1d\xcc\xeb\x58\
+\xc1\xaa\xd0\x66\xde\x6c\xfd\x8a\x46\xa9\x03\x51\xaf\x4b\xd2\x7b\
+\x06\x12\x54\x2a\x0d\xac\x10\xbc\x44\x1c\x1a\x80\xff\x07\xf4\xf7\
+\x70\xf0\xeb\x9a\x5b\x2a\xcf\x9c\xd9\xa9\xcd\x75\x5b\x74\xf3\xc2\
+\x2a\x9a\x9f\x5e\x3a\x15\xf8\xfb\x6f\x0a\x20\x2e\xaf\xfb\x10\x35\
+\xac\xbc\x5a\x7d\xe9\x17\xc4\x2a\x3a\x7e\xf0\xbb\x1d\x33\x36\x51\
+\x7d\xf1\xe7\xa8\x81\xd8\x87\x2e\xaf\xfb\xac\x5d\x98\x5a\x15\x81\
+\xc6\x86\x9c\x38\xed\x89\x10\x76\xd9\x4c\xa2\x35\x0a\xa2\x80\x68\
+\x91\x89\x59\x05\xc2\xf1\x08\x79\x45\x79\x48\xd9\x66\xb4\xf7\xaa\
+\x51\xeb\x03\x0c\xea\x37\x00\x25\x4b\xa2\x26\xd2\x8c\xd3\x90\x47\
+\x7d\xb4\x15\xd9\x22\x63\x1e\xda\x39\x41\xd6\xe6\x69\xa0\xa3\xd5\
+\x8f\x5e\x92\x29\xa9\x36\x13\xfe\xf7\x06\x5a\x2e\x98\x4d\xeb\x19\
+\x5f\x12\x5b\xd8\xb9\x4a\x4f\xea\x97\x41\xd6\xab\x47\x50\x73\xb0\
+\xc0\xe6\xf6\x1a\x10\x20\xa8\x46\x71\xe8\xac\xd8\x45\x2b\x66\xd1\
+\xc4\xda\x48\x15\x27\x64\x8e\xe6\xbc\xb2\x49\x08\x92\xd8\x2a\x5a\
+\x65\xaa\xea\x6b\xd9\x10\xad\xa1\x38\xbb\x98\xcc\x6e\x39\xec\x6e\
+\x95\xd5\xe5\x75\x3f\xe5\xff\xb4\xe2\xfa\xea\x0b\x3f\x4d\xe3\x81\
+\xdd\x95\x34\x3d\xb2\x18\xdf\xff\x36\x4c\xdd\x9b\x45\xb9\xfd\x0a\
+\x90\x54\x4d\xc3\xd4\xfa\x5b\xe6\x11\x5e\x9c\x4e\x2c\x68\x1a\x91\
+\x47\xe1\x13\x13\x10\x4d\xe9\xb3\x5b\xe0\xcb\x4a\x2a\xcf\xfb\x84\
+\x44\x4b\xe4\x55\x97\xd7\x7d\xf1\x2e\x4e\x1b\x56\x4a\x0c\xf8\xcc\
+\x71\xec\x3a\x33\xf1\x6a\x3f\x82\x4e\x40\x90\xc4\xc5\xe1\x5c\x91\
+\x98\xa8\x92\xd3\xae\x47\xdb\xdc\x41\xfe\xa7\xc7\x33\xf8\xfd\xb3\
+\x68\xfe\xbe\x86\x91\xbe\x62\x66\x05\x96\xd1\xd3\x98\xcf\x20\x43\
+\x09\x87\xf6\x19\x46\x76\xef\xce\xed\xa9\xfa\x15\x7e\x74\x8a\x46\
+\xb3\xe6\xa7\x87\xa3\x90\xcc\xcc\x0c\xd6\x0b\xf5\x24\x56\xb6\xd1\
+\x76\xfe\x6c\x22\x33\xbd\x9d\xef\x48\x2f\x30\xf4\xd6\x89\xac\x13\
+\xea\x40\x53\x11\x45\x68\x4f\xf8\x11\x75\x02\xa3\x6c\x7d\x99\x9c\
+\x79\x28\x66\xc1\x4c\x4d\x6e\x18\x20\xac\xcb\x32\xa2\x55\x87\x28\
+\xd3\x17\x61\xc8\x75\xe0\x28\xc8\x04\x68\xda\x49\x77\x76\x97\xd7\
+\xfd\x6c\xfb\x6b\x9e\x2b\x6a\x2e\xfd\xa2\x93\x73\x6a\x3f\xae\x8c\
+\xfc\xbf\x1f\xd6\xe9\x56\xea\x6e\x9e\x47\x78\x69\xc3\xb5\xc0\x8d\
+\xbf\x05\x0b\x72\x7f\xeb\xf3\x2b\xcf\x49\xd5\xa7\x6e\x7f\xeb\xf2\
+\x2d\x14\x3d\x31\x01\xc7\x49\xbd\x1f\x2d\x7e\x7e\x22\x3a\x7b\x7a\
+\x4e\x21\xbc\xb8\x9e\xca\xb3\x66\x12\xdb\xec\x7b\xce\xe5\x75\xdf\
+\xbe\xd3\x39\x03\xf6\x1e\xd9\x04\x73\x45\x14\x12\xb4\x79\x1a\xb6\
+\x76\x8a\x6d\x2c\x1a\x50\x82\x2c\xea\xc8\xd1\xd9\x69\xbd\x62\x3e\
+\x1d\x77\x2d\x21\xf8\x6f\x0f\xe1\x5b\x97\xd2\xae\x04\xc9\x97\xb3\
+\xf9\xb2\x76\x09\x4a\x4b\x04\xfa\x3a\xd0\x19\x3b\xfb\x1f\xd2\xf2\
+\x0e\x06\xdb\x7b\xd0\x16\x0f\x50\x15\x6d\xa4\x58\xee\x86\x24\xc8\
+\x60\x4a\x2e\xbe\x05\x9e\x5a\x0d\xbb\xa0\xa0\xca\x1a\x54\x48\x6b\
+\x99\xca\xb4\xea\xf7\xf9\xb0\xed\x1b\xe6\x05\x56\xb1\x39\x5a\x47\
+\x45\xb4\x91\xba\x78\x0b\x0d\x6a\x2b\xc6\x01\x59\x00\x01\xb9\xd0\
+\x4a\xe5\x5a\x2f\xad\x8a\x9f\xa2\xde\x25\x64\x3b\x73\x61\x87\xe6\
+\x48\x97\xd7\x3d\x18\x78\xb0\x65\xda\xf2\xa9\x9d\x56\x67\x53\xe0\
+\x28\x7c\x62\x02\x99\xe7\xf6\x9f\x96\x73\xf5\x88\xf4\x39\x38\xa2\
+\x50\x73\xc5\x97\x28\x4d\xa1\x07\xd9\x87\x9d\x05\x62\x17\x58\x8f\
+\xcb\xc2\x8b\xeb\xaf\x6c\x7c\xe0\xbb\x4e\xce\x68\xd1\x33\x7f\x40\
+\x2e\xb1\xdf\xe6\x71\x96\x5f\x6f\x19\x5b\x7c\x7f\xf7\x97\x3b\x47\
+\x37\x11\x4f\x0b\xde\x29\x1f\x10\x5c\x50\x7b\xb7\xcb\xeb\xde\x91\
+\x46\xc2\x97\x57\x9a\x87\xa1\x77\x16\x4d\xf1\x76\x1a\x17\x7b\x11\
+\x6d\x7a\x80\x95\x25\x47\xf6\xc7\x22\x1a\xc9\x34\xda\x48\x44\x13\
+\x84\xde\xd9\x82\xff\x45\x0f\x19\xed\x32\xce\x51\x7d\x38\xf6\xe3\
+\xcb\x29\xf8\xdb\xa1\x18\xce\xef\x89\x79\x17\x8b\xca\x09\x7f\x8c\
+\xd6\xd5\xf5\x58\x8d\x16\x4a\x0c\xb9\x6c\x8e\xd4\x13\x56\xa3\x6c\
+\x8a\xd4\x20\x22\x22\x48\x22\xaa\x2f\x86\x1a\xd8\x25\x65\x3c\xc7\
+\x97\x1c\xc6\xd9\x99\x47\x32\xd8\x5c\x86\x0e\x81\x95\xc1\x8d\x7c\
+\xed\x5f\xc5\x02\xff\x6a\xbe\x0f\x78\xc9\x1c\xe3\x04\x08\x4a\x05\
+\x16\x3a\xd6\x37\x32\x39\x63\x34\x3d\x87\x95\x91\x51\xec\x00\xa8\
+\x4e\xe9\xed\x24\x4d\x51\x57\xd4\xdf\x3a\xff\x92\x9d\x75\x07\x90\
+\x71\x46\x3f\x8a\x9e\xfe\x03\x82\x5e\x77\x04\x70\x57\xee\x35\x23\
+\x5e\xb3\xef\x54\x34\x1e\xaf\xf6\x53\x7f\xeb\xfc\xad\x25\x06\xbf\
+\x3e\x80\xb8\xbc\x6e\x97\x1a\x52\x9e\xd9\x15\xfa\xf3\xef\x3a\x14\
+\xf3\x41\xf9\x8f\x7b\x9c\xe5\xf7\xa6\xfc\x8a\x5b\x4c\xc3\xf3\xee\
+\x28\x79\xfd\x78\x0c\xbd\x32\xd2\xa3\x89\xc6\x10\x55\xe7\x7d\x4c\
+\xdb\x7f\x3d\x57\xa7\x56\x32\xfb\x03\xfe\xe8\x3b\x9b\xc9\x3a\xa8\
+\x3b\x76\xcc\x84\x36\xb7\x12\x6b\x0a\x02\x84\x33\x46\x16\x52\xd4\
+\xbd\x90\xda\x58\x2b\x3a\x49\x44\x30\x4b\xc8\x56\x23\xed\xb1\x20\
+\x79\xa7\x0d\x20\xa3\x7f\x1e\x07\x5d\x72\x24\x39\x0f\x8c\x21\x7b\
+\x70\x61\xa7\xfb\x6e\xf9\xbe\x96\xca\x35\x15\x28\xd1\x38\xa5\xba\
+\x5c\x14\x35\x41\x44\x8d\x22\x09\x22\xcb\x03\x1b\x91\x03\x1a\x72\
+\xff\x4c\xc4\x4c\xc3\x2e\xc7\x1d\xf0\xf9\x69\x49\x04\xc8\xd0\xd9\
+\x99\x60\x1b\xce\x30\x53\x5f\xba\xe9\xb2\x41\x10\xc9\xec\x9b\x8f\
+\x7d\x50\xb7\x45\x80\x35\xbc\xb4\x91\xa3\x74\x03\x28\x32\xe5\x62\
+\x1b\x9c\x4b\x64\x79\x13\x40\xc0\xe5\x75\x5f\xa7\x34\x84\xfe\x57\
+\x75\xfe\x27\x49\x8e\xd9\x9d\x24\xe7\x8a\x61\x14\x3c\x30\xee\x79\
+\x74\xc2\x70\x8f\xb3\xfc\x2b\x8f\xb3\xbc\x01\x98\x56\x70\xff\xd8\
+\xad\xcc\x06\xdb\xc4\xff\x69\x05\x6d\xaf\xae\x39\x09\x78\xe8\xd7\
+\x68\x41\x2e\x6f\x7a\xf0\x3b\xa2\xeb\xdb\xd2\xd1\x3f\xa5\x1f\x19\
+\x67\xf4\x7b\xd1\xe3\x2c\xbf\x76\x27\xe7\xf3\xef\x86\x5e\x19\x57\
+\x96\xbc\x71\x3c\xe6\x9d\xf6\x59\xd1\x62\x09\xea\x6f\x99\x47\xfd\
+\x1d\x5f\x4f\xd5\x62\x89\xd5\x40\x30\xb6\xa5\x83\xc2\xd1\x4e\x72\
+\x4c\x19\x84\x94\x08\x1d\xc9\x22\xe6\x83\x44\xa3\xf4\x70\xff\x89\
+\xc3\x59\x15\xad\x20\x98\x48\xd6\x87\xd6\x07\x5b\x09\xf7\x35\x20\
+\x4f\x2a\xfe\xd1\x9b\xb6\x94\x66\xd2\xe7\x9e\x23\x30\xfc\xa1\x10\
+\x43\xb1\x9d\xde\xb6\x22\x36\x04\x6b\x18\x28\x97\xb0\x2e\x5e\x83\
+\x76\x78\x0e\x8e\x3b\x46\xec\xf2\xb7\xf1\xc6\x20\x81\xcd\xad\x58\
+\x8d\x66\x82\x6a\x84\x16\xa5\x03\xb3\xac\x27\x4b\xb2\x91\x2b\x67\
+\x71\xe4\x49\x47\x03\xbc\x85\xaa\xf5\x89\x7f\x55\x4f\x40\x0b\xb3\
+\x24\xaf\x09\x93\x5f\xdb\x1a\xe6\x1e\x17\x5a\x54\xff\x88\xf7\xb4\
+\x19\x5b\x1b\xcf\xd2\xd2\xe7\xf9\xf7\x1e\x46\xee\xf5\x07\x4d\x03\
+\xee\xf2\x38\xcb\x97\xed\xa0\xbb\xf9\xa2\x45\x76\x17\x3e\x75\xc4\
+\x56\x4b\xba\x7d\x71\xef\xbe\x85\xc4\x36\xb5\xdf\x00\x9c\xb6\xb7\
+\x0f\x74\x5f\x32\x2d\x5f\x1e\xfc\xba\xe6\xe9\xca\xb3\x66\xa6\x11\
+\x0c\x18\xfa\x64\x52\xfa\xde\x1f\x11\x2d\xf2\x48\x8f\xb3\x7c\xc9\
+\x6e\x2c\xcf\x69\x5a\x44\x99\x5e\x7f\xfb\xd7\xb4\x4f\xef\x4c\x6b\
+\x65\x3e\xb8\x80\xc2\x27\x8f\xa0\xe5\xd9\xe5\x38\xfe\xd8\x8b\x35\
+\x0f\xcf\xc6\xbe\x26\x46\x63\x2f\x8d\x43\xff\x77\xee\x66\xe0\xf8\
+\xc8\xda\xd6\x35\x1f\x9c\xf8\x0c\x0e\xcc\xf4\x36\x14\x30\xbb\x69\
+\x05\x87\x1f\x3d\x9e\x8c\xab\x87\x90\xd5\x2b\x0f\x6c\x3f\x2d\xe5\
+\xa3\x85\x13\x24\x1a\x42\xb4\x37\xb6\x26\x73\x1b\x39\x06\xcc\x3d\
+\x1c\xe8\x74\xbb\xfe\x7d\xfd\xdb\xab\xa9\xbd\x76\x2e\x15\xba\x56\
+\x7c\x4a\x08\xbd\x2e\x99\xcc\x43\x15\x90\xad\x06\x4e\x9e\x75\x35\
+\x72\x81\xa5\x2c\xe1\x8b\x6e\x9e\x7f\xcc\x8b\xf8\x9b\xdb\xc9\xbe\
+\x78\x14\xbd\x75\x99\xd8\x4f\xea\x4d\xe8\xdb\x5a\xea\xef\xf8\xba\
+\x33\xd7\x5b\xb6\x89\x82\xc7\xc6\x63\x1d\xdf\xfd\x29\x8f\xb3\xfc\
+\xaa\x1f\xb0\xda\xf7\xfb\xde\x5e\xff\xd7\x1d\x29\x41\x21\x49\x49\
+\xee\x7c\xed\x38\x10\x05\x1d\x7b\x41\x75\x25\xee\xa3\xa9\xc5\xa1\
+\x45\x94\xa7\x1b\xee\x5c\x90\x06\x0e\x41\x12\x29\x78\x70\x1c\xa2\
+\x45\xbe\x7c\x77\xe0\x48\x81\xec\x2d\xc1\x28\x8d\x2a\x78\xf8\xf0\
+\x97\x72\xae\x1c\xde\xe9\xf3\xd0\xc2\x3a\xbc\xa7\xbc\x4f\x78\x79\
+\x23\xbe\xf7\x36\x12\x1e\x9f\xcd\x27\xed\xdf\x21\xae\x09\xe0\x5f\
+\x58\x5b\x06\xe4\x19\xfb\x65\xbd\xd4\xff\xd8\x11\x34\x29\x3e\x36\
+\x45\xea\xd1\x0c\xb0\x65\xae\x87\x05\x27\xbe\x84\x7f\xf2\x2c\x7c\
+\x53\xbf\x66\xee\xe3\x33\x08\x2f\x69\x84\xe0\xee\xd3\xd6\x82\x49\
+\x87\x54\x6a\x23\x67\x94\x13\xeb\xe8\x42\x6c\xbd\xb2\x77\x0b\x0e\
+\x35\x96\xa0\xee\xe5\x95\x04\xc5\x28\x6c\xed\xf2\xd5\xc0\x24\x48\
+\x88\x32\x0c\x3d\xfd\x10\xe4\x02\xcb\x43\xc0\xe4\xe6\x0f\x37\xe0\
+\x6b\x6e\xa7\x4d\x0b\xd3\xe3\xf8\x01\xf8\x67\x79\xa9\xbf\x65\x1e\
+\x75\x37\xcd\xed\x04\x0e\x43\xef\x4c\x4a\xde\x3c\x1e\xeb\xf8\xee\
+\x77\xfd\x10\x38\x52\x72\xb3\xe3\xd4\x3e\xd3\xed\xc7\x97\xa5\xeb\
+\xec\x9b\x5a\xda\xdf\x5c\x07\x49\x2a\xee\x5f\x7c\x8a\xb9\xa1\xf5\
+\x5f\xdf\x13\xdd\x90\x3e\xb5\x64\x5d\x38\x08\xd3\xf0\xbc\xa7\x3c\
+\xce\xf2\x7f\xfc\x04\x4b\xb4\x08\xb8\x35\xf7\xba\x91\xd3\x8a\x9e\
+\xf9\x03\x3a\x47\xfa\x7c\x1f\xaf\x09\x10\x59\xd9\x44\xc7\x87\x9b\
+\x18\x78\xea\x41\x6c\x32\xb5\xe1\xd3\xfc\xc4\xfe\xb3\x79\xab\x53\
+\x36\xad\xe7\x55\x87\x62\xb7\xda\x08\x6b\x31\x04\x04\x44\x59\xa4\
+\x4d\x0c\xd3\x5c\xd7\x84\x7e\x76\x1b\xb5\xf7\x7d\xcb\x8c\xa3\x9e\
+\x80\x99\xdb\x8b\xa1\xdb\x7d\x3e\x94\xc8\xcf\xe7\x57\xd7\x80\xea\
+\x07\xbe\x61\xc9\x82\xc5\x04\xe4\x38\xa2\x00\x3a\x9d\x80\x4e\x14\
+\x68\x50\x7d\x44\xed\xd0\xf3\xb2\x43\x00\xa6\x69\x09\xed\xf1\x65\
+\xff\x9e\x43\x75\xac\x09\x71\x4c\x77\x2c\xde\x28\x91\x55\xcd\xbb\
+\x64\x48\xb6\x4d\x2c\xa5\xe4\xcd\xe3\x31\xf4\xce\xbc\xc2\xe3\x2c\
+\xff\xdb\x4f\xbc\x9d\x69\x79\x77\x8c\x41\xca\x36\xed\x94\x1f\x59\
+\x44\xa2\x25\xf2\x37\xa0\xec\x17\x03\x88\xcb\xeb\x2e\x53\x1a\x43\
+\xb7\xb6\xfc\x33\x9d\xa2\x4b\xef\xb4\x93\xb2\x06\x8f\xff\x8c\xe9\
+\xaa\xce\xe3\x2c\xbf\xd4\x7e\x42\xcf\xfb\x9c\xd3\x4f\x60\x67\x02\
+\x38\x80\x44\x6b\x04\xe5\xf5\x4d\x1c\x7a\xee\x24\xbe\xf3\xaf\x45\
+\x5a\xd0\x46\xe8\x9b\xda\xd3\x81\xc1\xc6\xb2\x8c\x07\x0e\xba\xec\
+\x48\xfc\x5a\x88\x42\x7d\x0e\x6b\x23\x95\xf4\x32\x16\xb1\x30\xb2\
+\x81\x35\xd4\x30\xbe\x60\x38\x15\x52\x33\xad\x3b\x94\xaa\xbc\x7d\
+\xdf\xbf\x79\x6d\xf4\xdf\x51\x1f\x5c\xfb\xd3\xb7\xe9\x68\x53\xd8\
+\x72\xeb\x6c\xd6\xff\xf3\x5b\x04\x8b\xc4\xa6\x48\x0d\x35\xf1\x16\
+\x5a\x13\x01\xbc\xb1\x46\x1a\x13\xed\x4c\xfc\xeb\x69\x48\xb9\xa6\
+\xdb\x80\x73\x2b\x5f\x5b\xce\xb2\x35\xdf\xa3\xa9\x1a\x63\x2e\x3e\
+\x62\x97\x8e\x28\x02\xe4\x5c\x3d\x82\xe2\xf2\x89\x48\xd9\xa6\x63\
+\x3c\xce\xf2\x67\x7e\xc6\x63\x98\x2d\xe5\x99\xef\x4a\xd5\x82\x6c\
+\x77\xf6\x9b\xc3\xb4\x3c\xb7\x7c\xaf\xa2\x9a\x7d\x61\x41\xa6\xb6\
+\x3e\xbf\x32\xad\x67\x16\x20\xf7\x86\x83\x10\x2d\xf2\xf5\x1e\x67\
+\x79\xc5\x1e\xf8\x35\xb7\x1a\xfa\x65\x5d\xe8\x7c\xf3\x04\x76\x2e\
+\x84\x06\x68\xfb\xcf\x1a\x26\x5d\xfe\x47\xfa\x97\xba\xd0\x89\x22\
+\x81\x87\x57\xa2\x46\x94\x17\x80\x69\xf9\x17\x0f\xff\x4f\xef\x63\
+\x47\x10\x26\x46\x96\x64\x63\x79\x68\x03\x06\x41\x4f\x45\xb4\x89\
+\xb9\xed\x2b\x39\xc9\x35\x01\xa9\xf7\xf6\x12\x84\x49\x1b\x7b\xf3\
+\xd5\x8a\xf9\xd4\x2c\xde\xb4\xad\x78\x58\x6b\x8e\xd2\x78\xe5\x1c\
+\x96\xdd\xf5\x31\x6b\x5e\x98\x4f\xf8\xfd\x0a\xc2\xef\x57\x10\x7a\
+\x69\x1d\xbe\x1b\xbe\xa5\xed\xc4\x4f\x69\x7a\x75\x15\xdd\x33\xba\
+\x31\xc8\x5c\x4a\xae\xce\x41\x4f\x7d\x21\x19\x3a\x0b\x09\x11\x26\
+\x9f\x77\x2a\x45\x53\x06\x4d\x03\x16\x46\xab\xfc\x77\x7d\xf2\xd0\
+\x5b\xa0\xa9\x58\x8f\xeb\x47\x56\x83\x46\x70\x6e\xba\x33\x2a\x17\
+\x58\xe8\xfe\xaf\x63\xc8\xbd\x66\xc4\xb3\x08\xe4\x7b\x9c\xe5\x9f\
+\xee\xc1\x73\xf8\x7b\xc6\xe9\x7d\x93\xfc\xad\x3b\xea\xea\xbf\x1e\
+\xe2\x35\x81\xeb\x81\x9e\xbf\x04\x40\x0a\x94\xc6\xd0\xf5\x3b\x6f\
+\x2a\x68\x1a\x92\x8b\xfd\xb8\xb2\x9f\x65\x3d\x76\x01\x92\x17\x45\
+\x8b\x3c\xbc\xe0\x81\x71\xcf\x17\x3e\x36\x1e\x5d\xc6\xf6\x29\x47\
+\x69\x08\x11\x7c\x66\x25\x7d\xae\x9e\x90\xe4\xf5\xa8\x08\xd3\xfe\
+\xd8\x0a\x80\xdb\x80\x69\x23\x1e\x3c\x8e\xa2\x41\xa5\x64\xca\x0e\
+\x7a\x19\x8b\xa8\x8d\x35\x52\x1b\x6f\x44\xa7\x08\xb4\xf6\x14\xb1\
+\xdb\x93\x2b\xec\x5a\x6d\x98\x6e\xb5\x32\x93\xb2\x0f\x66\xce\x90\
+\xda\xed\x2f\xf3\x5a\x3f\x5b\xa6\x2f\x63\xf5\x33\x73\xf8\xe0\x86\
+\x7f\xd3\x74\xed\x7c\x3a\xae\x5b\x48\xc7\x3d\xcb\x08\xbf\x57\x41\
+\xac\x2d\x4c\xc2\xac\xc3\x26\x19\x91\x05\x99\xba\x78\x1b\xb3\x3b\
+\x56\x10\x23\xce\x61\x87\x8f\x61\xe8\xdd\xc7\x00\x3c\x01\xfc\xfd\
+\x9b\xdb\xde\xc7\xd7\xe6\xc3\x66\xb7\x73\xec\x4d\x27\xd1\xf6\x9f\
+\x74\xeb\x61\x3d\xd2\x89\xf3\x7f\x93\xb1\x1e\x51\x72\xab\xc7\x59\
+\x7e\x59\x2a\x84\xdd\x13\x49\xa0\x13\xfe\x9c\x3d\x75\x68\xba\x9f\
+\x14\x8c\xd3\x9a\x2c\x2e\x9a\xfa\x4b\x00\xe4\x3c\xdf\xdb\xeb\x49\
+\x74\xa4\x17\xff\x64\x9e\x3f\x10\x44\xe1\xcf\x7b\xbb\xa9\xa0\xc7\
+\x59\xbe\xcc\xe3\x2c\x77\x3b\x4e\xe9\x73\x6f\xe9\xff\x26\x6f\x25\
+\xc9\x4b\xe6\x2e\xca\x57\xd2\xc3\x91\xcb\xf7\xa3\xe2\x58\x04\x19\
+\x65\x7a\x05\x81\x8f\xbd\x17\x01\x37\x88\x16\xf9\xec\x61\xff\x3c\
+\x89\x98\x53\xa6\xd8\xd8\x8d\x71\xb6\x61\xd8\x74\x66\x9a\x22\xed\
+\x38\x46\x6e\x0f\xa7\x37\x2c\xf3\x30\xb3\x62\x01\x43\x32\x7b\x73\
+\xcc\xe1\xdb\xf9\x4a\xb7\x2c\x5c\x4b\x9e\x29\x1b\xc9\xa2\x27\xcf\
+\x9e\xc3\x5c\xd5\x83\xc5\x66\x45\xb0\xca\x88\x66\x3d\x51\x51\xa1\
+\x43\x09\xb1\x26\x54\xc5\xea\xb0\x97\x88\x16\x43\xd5\x69\x0c\x3c\
+\x78\x28\x87\x3d\xf7\x27\x04\x9d\x78\x2a\xf0\xc6\xba\x27\xe7\x8f\
+\x5e\xf1\xd5\x22\x32\x45\x13\x03\xae\x9d\x88\x38\xb3\x96\xf0\xd2\
+\xe4\xf3\x17\x2d\x32\x79\xb7\x8d\xa6\xfb\x8b\x47\x7f\x20\x17\x5a\
+\x27\x79\x9c\xe5\xf7\xed\x03\x6b\xfe\xaa\xf5\x28\x27\x86\xbe\xe9\
+\xb5\xb6\xbe\x77\xd6\x93\x68\x8b\x5c\x07\x64\xef\x4f\x80\xe8\xb5\
+\xb8\x7a\x7f\xfb\xdb\xe9\xe4\xc6\x72\x91\x15\xdb\x31\x3d\x20\xc9\
+\x6c\xbc\x4f\xc4\xe3\x2c\xbf\x4d\xdf\x33\xe3\x8c\xc2\x47\xc7\xa7\
+\x55\x58\xb5\xfd\x67\x0d\x3d\xef\x1c\x47\x75\x66\x14\x83\x28\x13\
+\xba\x67\x39\xd1\x95\xcd\x27\x01\x97\x4b\xb9\xe6\x5b\x8e\x7e\xcb\
+\x4d\x78\xb8\x95\x4d\xf1\x5a\x26\xd8\x87\xd0\xa1\x0f\x53\x3b\x7d\
+\x15\x0b\x6f\x7a\x17\xed\x8b\x06\xb4\xd9\x4d\x6c\x0a\xd5\x30\x5d\
+\xbf\x08\xd3\xc0\xed\xfe\xce\x37\xf3\xbf\xe1\xa3\xe0\x22\xfa\x9b\
+\x4b\xb0\xe8\x4c\x78\xa3\x0d\x6c\x88\xd6\x60\xd7\x99\x90\x85\x64\
+\x4f\xae\xa8\x81\x4d\x34\x51\xac\xcf\x61\x9c\x63\x10\x67\x9c\x3e\
+\x85\x51\x2f\x9f\x8e\x68\x95\x9f\x25\xa1\xbd\xdd\xfe\xc1\xa6\x61\
+\x8b\x9e\xfc\x14\xd0\x30\x1f\xd9\x93\x11\x45\x3d\x69\x7a\x74\xf1\
+\xf6\xb4\xf9\x09\x3d\xc9\xba\x68\xf0\x0b\xc0\x9f\x3c\xce\xf2\x7d\
+\xc5\xc8\x14\x17\x24\xf1\xb2\xcc\x73\xfb\x77\xf2\xdb\xfc\x9f\x54\
+\x00\x9c\xbc\x3f\x01\x32\x3a\xbc\xb4\x81\x9d\x39\xc3\xec\x93\x7b\
+\x21\x9a\xa5\x07\x76\x28\x36\xde\x57\xf2\xb1\x2e\xc3\x80\x2e\x63\
+\x7b\xe1\x4c\x70\x5e\x35\xbc\xb2\x09\xf9\xfe\xe1\x44\x8c\x1a\x72\
+\x58\xc3\x77\xd5\x37\x44\x96\x35\x8d\xd6\xc2\xca\x7d\xb2\xc3\xf8\
+\xaf\xe1\x2f\x9d\xc4\x50\xf7\x78\xb2\x64\x1b\x23\x33\xfb\xb1\x71\
+\xfd\x46\x36\xbe\xf4\x1d\x75\x97\x7c\x49\xce\x27\x21\xdc\xa5\x27\
+\x72\x55\xde\x64\x3c\x9f\x2d\xc5\x57\xd5\x86\xd6\x18\xe1\x94\xd6\
+\x61\x20\x0b\xb4\x24\x3a\x30\x08\x12\x03\x4d\x3d\xf8\xc8\xf7\x1d\
+\xaf\xb7\xce\x66\x63\xb4\x16\xab\x68\xc4\x2c\x1a\x69\x51\x83\xa8\
+\x7a\xe8\x71\xcd\x18\x86\x3d\x75\xe2\x47\xa2\x51\xfa\x38\x5e\x13\
+\xb8\xb4\xe3\xa3\x2d\x28\x7f\x5f\xc5\x69\x99\x63\x98\x30\x66\x02\
+\x27\x3f\x75\x7e\x72\x6a\xd9\xa1\x21\x2c\xc5\x15\x52\xd5\x05\x24\
+\x76\xd3\xed\x93\xca\xd2\xa6\x64\x00\x5f\x72\x67\x8c\x33\xf6\x27\
+\x40\x4e\xf4\xef\x44\x73\x84\x28\x60\xef\xba\x2d\x49\x3b\x44\xab\
+\xbe\x53\x65\x55\xcb\xb3\xcb\xb1\x2f\xf6\xd1\x7c\x67\x4f\x14\x1d\
+\x48\xbe\x04\x1d\x57\x2c\x20\xbc\xa8\x91\xe8\xfa\xb6\x3f\x03\x94\
+\xde\x38\xe6\x73\xc3\xf3\xa3\xc9\x1e\xdc\x1d\xb3\xd9\x4c\x6e\x56\
+\x36\x46\xab\x09\x45\x54\x09\x0a\x51\xd4\xe6\x28\xeb\xaf\xfc\x98\
+\xa7\x0e\xbb\x81\xd8\xf9\xdf\xe2\x6b\xef\xe0\xc4\xec\x31\x2c\x0c\
+\xae\xa3\xb7\xa9\x88\x36\x25\x80\x4b\x5f\x4a\x86\xce\xc6\x2c\xff\
+\x72\xaa\x95\x66\xfa\x58\x0a\x31\x1f\x9c\xc7\xa8\xb7\xce\xa2\xc0\
+\x3d\xec\xbb\x78\xb5\x7f\x52\xf0\xeb\x9a\x63\x43\xb3\x6b\x08\xdf\
+\xb9\x1c\x83\xa2\xc3\x5b\x10\xa6\xe8\xf1\xc3\x69\xbb\xed\x5b\x42\
+\xdf\xd6\xa6\x5b\xda\x24\x40\x2a\xbb\x40\x4f\xcd\xba\x2c\xe3\x1b\
+\x96\x71\xe9\x19\xe4\xc8\xf2\x46\xe2\xb5\x81\x09\xfc\x4c\xda\xab\
+\x3d\x05\x88\x88\xaa\x5d\xb7\x73\x73\xb5\xbe\xd4\x8e\x21\xc9\x3a\
+\xb8\x62\x5f\x8f\xda\xe3\x2c\xd7\x10\xf8\x48\x2e\x48\xaf\xd0\x73\
+\x9c\xd2\x87\xd0\x77\xf5\x14\xd4\x40\xdd\x1d\xa5\xa8\x12\x48\x21\
+\x8d\xc0\x75\x0b\x89\xce\xab\x27\xbc\xb2\x89\x88\xa7\xe5\x28\xfd\
+\xf0\x9c\xf9\x43\xde\x38\x8d\x63\x9e\x39\x1f\xe7\x88\x3e\x68\xba\
+\xd4\x96\xa3\x82\x40\x44\x8c\x73\x62\xd1\x61\xd4\xf8\x1b\x58\xb0\
+\x76\x09\x7a\x51\xc2\xae\x33\x33\xd8\xd8\x83\x15\xa1\xcd\x1c\x9d\
+\x39\x82\x7c\x63\x06\x11\xe2\x8c\x76\xf4\xa7\x61\x88\x88\xe3\x99\
+\x31\x8c\x7f\xf5\x5c\x8f\xb9\xc4\x41\x60\x96\x77\x94\x2e\xdb\x44\
+\xf4\x8b\x5a\xa2\x8f\xac\xc6\x82\x4c\x6d\xa1\x42\xf6\xb4\xc3\x49\
+\xfc\x7b\x03\x72\xb1\xb5\xd3\xa2\xa4\xdc\xdd\x0a\x50\x41\xd7\xc8\
+\xc7\x29\xc2\x98\xed\xce\x6a\x58\x21\xbc\xa4\x01\xe0\x90\xfd\x01\
+\x90\x3c\xa5\x39\xdc\x69\x7a\x31\x0e\xca\x45\x90\xc4\xd7\x77\xb7\
+\x53\xf6\x3e\x90\xcd\x72\x49\x7a\x2d\xa9\x68\x95\xe9\xfe\xd2\x31\
+\x33\x05\x49\xa4\x7b\x87\x81\xd0\xe3\x83\xf1\x67\x68\x18\x54\x91\
+\xc8\x33\x1e\x82\x4f\xae\x46\x67\x90\x08\xcc\xaa\x3c\x2c\xb6\xc5\
+\x47\xee\x31\xbd\x66\xf7\xfd\xef\x89\x8b\xed\x6f\x4e\xc0\xe8\xee\
+\x8b\x34\x38\x13\x21\x53\x4f\x8c\x04\xee\xfc\xe3\x79\xd3\x3f\x07\
+\x85\x04\x51\xe2\x8c\xb1\xbb\xa8\x57\xda\x89\x64\x08\x0c\x3e\x74\
+\x18\xe3\x6f\x3a\x89\x43\x3e\xba\x80\x09\xaf\x9e\xb7\x52\x3f\x3a\
+\x0f\xff\x2c\xaf\x2b\x38\xb7\x1a\xc9\x66\xa4\x79\xca\x17\xa8\xef\
+\x57\x63\x16\x64\xd6\x0d\x8a\x93\xf7\xaf\x23\x10\xe7\x36\x92\x71\
+\x7a\x5f\xb2\xfe\x32\x88\x84\x6f\xbb\x23\x2f\xc8\x22\x52\xbe\x75\
+\xdb\x4a\x6e\x17\xc8\x62\xe3\xe0\x5c\x76\x26\x3b\x4e\x6d\xd3\x7a\
+\xf0\xcf\x39\xd1\x9e\xd6\xa4\xf6\x88\x57\xf9\x51\x83\xe9\x38\x30\
+\x26\xbd\xe7\x55\x74\x9d\xac\x33\xf4\x4c\x5f\xf9\x4d\x11\xfb\x3b\
+\x32\xcf\xed\xff\xf7\x78\x6d\xe0\x76\x79\x6d\x2b\xca\x2b\x13\xa8\
+\xff\xeb\x02\xf2\xd7\x80\xb2\xa2\x9d\xb6\x33\xbf\x42\xff\xc7\x12\
+\x8c\x03\x72\x08\x7e\x53\x3b\x5e\x5f\x68\x45\xee\xe5\x78\x4b\xee\
+\xe5\xa8\x63\xea\x80\xfe\x6a\x20\x7e\xa4\x52\x13\x60\x60\x43\x94\
+\x9b\x5a\x0e\x01\x45\x8f\xc5\x64\x42\x74\xe8\x71\xe7\x8d\xc5\x50\
+\x64\x43\x67\x91\x3f\xb3\x42\xbb\xa6\xa8\xa7\x07\xe6\x56\x0f\x16\
+\x44\x01\x43\x77\x07\x81\xe7\x3d\x84\x66\xd5\x61\x10\x24\x54\x55\
+\x60\xdd\xa9\x26\x86\xfe\x75\x12\xb1\x95\xcd\x98\xce\x72\xcd\x04\
+\xa6\x05\xe7\x55\x7f\xb8\xe3\xde\xbe\x52\xae\x79\xab\x45\xa9\xeb\
+\x22\x3d\x55\xcb\x05\x16\x74\xd9\xc6\x34\xe2\x9d\xd8\xc6\x76\x80\
+\x7e\xfb\x03\x20\x05\xbb\x6a\x63\xd0\xe5\x9a\x20\x49\x61\xdd\x65\
+\x00\xd9\xb9\x39\x3c\x96\x04\xea\x61\xa2\x45\x3e\x56\x2e\xb4\xbe\
+\x2e\x17\x5a\xa7\xc6\xbd\x1d\x57\xf4\xfe\xf7\x31\x6c\x7c\x71\x29\
+\xd6\x97\xab\x30\x45\x20\x3e\xdd\x4b\xeb\x8c\x2a\x0c\x13\x0a\x50\
+\x4f\x2a\x41\x57\x68\x39\x4d\x0b\x2a\xe8\x32\x0c\x73\x44\xab\x7c\
+\x96\xbe\x6f\xe6\x72\xfa\x52\xd8\x83\xfc\xad\x54\xdc\x31\xa0\x8d\
+\xe4\x5e\x33\xa7\x6b\x09\xed\x96\x78\x8d\x1f\x39\xc7\x8c\x2e\x21\
+\x10\x9a\xbe\x85\xe0\xb7\x8d\xe8\x54\x11\x23\x12\x75\x85\x0a\xfa\
+\xeb\x07\x31\x20\x37\x0b\x41\x01\xd3\xf0\xbc\x4b\xcf\xb4\x9c\x39\
+\xed\xb5\xe0\x6b\x57\x44\x77\xb2\xb4\x72\x91\x15\xd1\x2c\xcd\x1f\
+\x67\x3c\x22\xf8\xcf\xbc\x3f\x75\x85\x9e\x02\xa2\x59\x46\x97\x99\
+\x0e\x90\x54\x32\x33\x7b\x7f\x00\xc4\xb6\xab\xe2\x19\x41\xec\x72\
+\xae\xf6\xcd\x72\x89\x3d\x8d\xb5\x38\xd1\x1c\x26\x5e\x17\xc4\xd0\
+\x2b\xa3\xfb\x58\x69\xb2\x67\x80\xd4\xed\xca\xe7\x22\xcf\x7f\x0a\
+\x5c\x5e\x3a\xb1\xf7\x31\xb1\xe3\xfa\xd0\xf8\x8f\x15\x58\x3e\x6f\
+\x42\x8e\x68\x24\x3e\xa9\xa5\x71\xc6\x3a\x42\x79\x12\x79\xa3\x9d\
+\x24\x86\x39\x0e\xcf\xe9\x57\x70\xb8\x94\x6f\x46\xb4\xe9\x11\xf4\
+\xe2\x0a\x40\x01\x4c\x5a\x34\xd1\x5f\xf5\xc5\x88\xd4\x74\xd0\xe1\
+\x69\x22\xb0\xa8\x96\xcc\x15\x71\xd4\xb6\x68\x92\x82\x4a\xd5\xe1\
+\xcb\xd0\x08\x9d\x59\x40\xcf\x73\x86\x22\x46\x55\x74\x0e\xc3\x5d\
+\xc0\x23\xc7\x18\x4e\x0a\x64\x49\x26\x80\x81\xd1\x75\xe9\x6b\x54\
+\xfa\xb2\x0c\x80\x75\x5d\xa8\x27\x15\x51\x58\x21\x08\x42\x1a\xf5\
+\x93\x1a\x55\x20\xb9\x9b\x68\x97\x03\x44\x41\xea\x5c\x86\x9f\x2a\
+\xae\xcd\xec\xc2\x81\xd7\xc8\xf9\x16\xa4\x3c\xf3\xb6\x9e\x5e\x4d\
+\x51\x89\x6d\x6a\xc7\xd0\x2b\xa3\x2f\xe0\x01\xb8\xcc\x78\xc9\x4c\
+\xb3\x66\x98\xf9\x70\xf4\xc9\xbf\xc8\x09\xf5\x85\xe2\x0b\x07\x11\
+\xbd\x48\xa3\xf5\xad\x0d\xc8\x5f\x34\x42\x3d\x44\x6a\xda\x91\x3f\
+\x32\xb1\xec\xcd\xf9\x64\x48\x66\x72\x33\x72\x68\x31\x84\xd0\x8c\
+\xe2\x10\x8b\xc1\x4c\x46\xc2\x84\x21\x2e\x22\xf9\x35\xa2\xc1\x10\
+\x33\x7c\xdf\x32\xc4\xd2\x93\xb8\x68\x22\x4f\xce\xa2\xd9\x29\xa2\
+\x9e\x54\x84\xf3\x44\x17\x52\x0c\x24\xa3\xfc\x2c\x46\x9e\x99\x62\
+\x3e\xd3\xd3\x90\x68\xc6\xb8\x7d\xd7\x8d\x81\xa9\xdd\x1d\xb6\xc9\
+\x9e\x50\x62\xff\x4c\x31\x69\x11\x65\x48\x62\xe7\x1d\x39\x84\x9f\
+\xd4\x66\xb1\x4f\x00\xd2\x9c\x22\x86\x4f\xf7\x07\x92\x24\x26\x83\
+\xba\x6a\xd4\x1e\x67\x79\xc4\xe5\x75\x7f\xa6\xef\xe1\x98\xb8\x63\
+\xd3\x77\x64\x75\x33\xb6\xa3\x4b\x87\x01\xef\xed\xf8\xfd\x3b\xcc\
+\xd7\xbe\x78\x77\xe8\xb1\x57\xf4\x3d\x33\xae\xd6\xf9\x63\x0f\x49\
+\x47\x39\xd1\x5f\x39\x8c\xf6\x85\xf5\xf8\x66\xaf\x26\xb4\x46\xa5\
+\x74\x73\x11\x6b\x5b\x2b\xa0\x1d\xcc\x3a\x23\x68\x2a\x35\x31\x2f\
+\xf3\xa2\x0d\xe4\xc9\x0e\x0a\xe4\x4c\x0a\xe5\x2c\x7a\xe7\x96\xe1\
+\x2b\x35\x61\x3e\xb4\x0f\xda\x11\x3d\xe9\x35\x30\x9f\xb8\xb7\x03\
+\xbd\xc3\xf4\x0e\xf0\xe4\xf9\xa6\x29\xf3\x34\xcc\xec\xd8\x9b\xf9\
+\x5a\xf0\xb5\x8c\x44\x47\x6c\x4c\xac\x22\xbd\x70\xdd\xd0\xaf\xcb\
+\x7d\xb5\xc2\x78\x43\xa8\x13\xdb\x61\xaa\x17\xc8\xbf\x3f\x00\x52\
+\xa1\xef\x6e\x43\x90\xc4\xb4\xd2\xc2\xe0\x37\xb5\x68\xb1\xc4\x9f\
+\x5d\x5e\xf7\xa5\x1e\x67\x79\xb4\x8b\x06\xbf\xc2\xe8\xca\x9e\xb8\
+\xe3\x82\x57\x6a\xcf\xde\xe1\xbb\xfa\xf2\x63\x96\xdb\xe2\x56\x41\
+\xff\xb0\x3b\x70\xc7\x0b\xa6\xe1\x79\x97\x26\x5a\x23\xf7\xc8\xbe\
+\x18\xbd\xfa\x95\xe2\xb8\xbd\x2f\xe1\xfa\x00\xb9\x15\xed\xd4\x78\
+\xeb\xc8\x6a\xd5\xa1\xf3\x25\x88\x86\x1b\x29\x36\x8e\x24\x9a\xa9\
+\x23\xab\x20\x0b\x53\x49\x06\xa3\x9d\x99\xb0\xc9\x47\xbc\x26\x80\
+\x5e\xd5\x21\x88\xc2\x27\xfa\x1e\x8e\x67\x6f\xb3\x5f\xf7\x41\x7c\
+\xf7\xb3\x6a\xef\xd8\x16\x5f\xda\x2e\x9b\xa2\x49\xda\x4a\xb2\xd3\
+\x95\x53\xcc\x84\x50\xf2\x59\xec\xe4\xfb\xd8\x20\xb9\x91\x73\x97\
+\x03\x64\xb3\x5c\x62\x47\x2e\xb1\xa7\x6d\xf9\x19\xaf\xf2\xe3\xff\
+\x78\x0b\xf6\xc9\xbd\xae\xa1\x8b\x5a\x01\xb7\x86\x70\x3b\x5b\x2e\
+\x2d\xa2\x1c\x3f\x4f\x79\xdf\xc4\xee\xb9\x40\xdb\x80\x7b\x75\x59\
+\xc6\x72\xfb\xe4\x9e\x6e\x35\xac\xdc\xd3\x78\xef\xb7\xf8\x3f\xab\
+\x40\x2e\xb2\x91\xdb\x3d\xb5\x45\x7a\xbe\x89\xde\x72\x0f\xd4\x48\
+\x02\xa5\x31\x84\xb2\xae\x91\x70\xe5\x46\xc2\x5a\x72\x85\xda\x71\
+\x4a\x9f\x4f\x48\xd2\x2b\xcc\x00\xb8\xa7\xe3\x07\x5b\x51\x86\x47\
+\xbe\x6f\x4e\x2b\xa2\x92\x4b\xec\xc8\x79\x16\x80\xda\xb9\x91\x2f\
+\xf7\x7a\x5b\xd4\x5d\x2d\x81\xa0\x6a\xcf\xef\xaa\xa4\xc0\x74\x50\
+\x3e\xc0\xfc\xfd\x01\x90\x98\x20\x8b\x8f\x59\xff\x50\x72\x6d\xeb\
+\xe6\x74\x0f\xbd\xf1\x81\xef\x30\x8f\x29\xbc\xdf\xe5\x75\x6f\xde\
+\xd7\x7b\xe6\xa6\x64\xa5\xd1\x95\x95\x66\xbd\xe2\x75\x41\x62\x5b\
+\x3a\x30\xb8\xb2\x7a\x03\x2b\x77\xf1\x1b\x2b\x30\x16\xf8\xa3\x1a\
+\x88\xb9\x83\x0b\x6a\xf1\x7f\xbc\x85\xd0\xb7\x75\xc4\xeb\x02\x49\
+\x7f\xe6\xdb\x1f\xbe\xa8\x60\x94\x68\x7a\x74\x31\xe1\x65\x8d\xc7\
+\xd8\x8e\x2e\x3d\xc6\xd0\x27\x73\x1e\xf0\x2e\xf0\x21\xc9\xed\x57\
+\x77\x25\x87\x6c\x5d\x9c\xdb\x96\x0a\xe8\x9f\x0d\x3a\xe1\xd5\x2e\
+\x74\xe6\x1f\x6c\xfd\xf7\xea\x4e\xa4\x3c\x82\x51\xc2\x7a\x78\x31\
+\xc0\xcf\x2a\x25\xd8\x9b\x54\xfb\x8b\x19\x53\xfa\x76\xa2\x27\x88\
+\xd7\x06\xa8\xb9\xfc\x4b\x54\x7f\xec\x4d\x97\xd7\x7d\x61\x57\x45\
+\x32\x72\xf1\x0e\x19\x55\x55\x23\xbc\xa2\x11\x60\xd4\x8e\xf3\x30\
+\x70\x2a\xf0\x2f\x35\x18\xf7\x07\x66\x57\x7d\x54\x77\xf3\x3c\xf7\
+\xe6\xa3\xde\xa6\xfa\xa2\xcf\xf0\xfd\x6f\x43\x72\xc7\x85\x9f\x58\
+\x23\xa4\x45\x14\xc2\x4b\x1b\x68\x7a\x64\x11\x5b\x8e\x7d\x07\xef\
+\x69\x33\xc6\xb6\xbe\xb0\xea\xb1\xd8\x16\xdf\x7a\x60\x1e\x70\x4b\
+\xea\xfa\x5b\x9d\x33\x49\x53\xd4\xf3\x22\x2b\xd2\x37\x7f\x34\x0d\
+\xef\x06\x3f\x0a\xc7\x3d\x07\x47\x70\x6e\xf5\xd5\x29\x4e\xb8\xf4\
+\xb0\xf3\xe8\x52\xe4\x62\xdb\x6b\x3f\x37\x7b\xbb\xb7\x45\xcb\xcf\
+\xd6\xdf\x36\x7f\xea\xce\x35\x0e\x00\xc6\x81\x39\xe4\xdf\x37\x96\
+\xd4\x46\xbe\xd3\x3c\xce\xf2\xb5\xfb\x4a\x0b\x2e\xaf\xfb\x8d\x9a\
+\xcb\x67\x4d\xe9\xf8\x60\xfb\x56\x2b\x8e\x53\xfb\x50\xf8\xe8\xf8\
+\x97\x49\xee\x39\x37\x5e\x69\x08\x4d\x08\x2d\xa9\x27\xf8\x55\x15\
+\xc1\x05\xb5\x3f\xbe\xc5\x3a\xc9\xae\xbf\x8c\x3f\xf5\xc3\x50\xe6\
+\xc0\xf7\xfe\x46\xfc\x1f\x6d\xd9\x6d\x4f\xec\x36\x05\x1a\x74\x98\
+\x06\xe7\x62\x19\xdf\x1d\xcb\x61\x45\x18\x5d\xd9\x08\x06\xdd\xcb\
+\xc0\x17\xf1\xca\x8e\xff\x6c\x9a\x90\xbe\x35\x7b\xe9\xfb\x27\x61\
+\x1a\x92\x3b\x2a\x55\x62\xb9\x2f\x74\x01\xc9\x1d\xc6\xa7\xfa\xde\
+\xdb\xf8\xa7\xfa\x5b\xe6\x75\x4a\x60\x8a\x26\x89\x1e\x33\x4f\x46\
+\xdf\x33\x63\xec\xcf\x9d\x62\xf6\x16\x20\x83\x12\xbe\xe8\x4a\xef\
+\xc9\xef\x13\xdd\xd8\xbe\x4b\xe5\x65\x9e\xe9\x22\xf3\xdc\x01\xe8\
+\xcb\x1c\xef\xa6\xa2\x8c\xf9\xc0\x16\x8f\xb3\x5c\xdb\x0b\xa5\x5c\
+\xd8\xfe\xc6\xda\xe7\xeb\x6e\x9a\x9b\x16\x3a\xe6\xfd\x6d\x0c\xe1\
+\xc5\x0d\x04\xbf\xa9\x25\xf2\x7d\x73\x27\x92\x9a\x5d\xce\xb1\xd9\
+\x26\xac\x47\x97\x92\x71\x6a\x1f\x4c\x23\xf2\x16\x00\x2f\xa6\x1c\
+\xc8\x33\x13\x2d\x91\x4b\x3b\x3e\xd9\x82\xef\xed\xf5\xec\x3c\x55\
+\xec\xd6\x01\x28\xb5\x63\x1a\x99\x8f\xe5\xb0\x22\xe2\xd5\x01\x9a\
+\x1e\xd9\x8e\x03\xb9\xd8\x46\xcf\x39\x53\x10\x24\xd1\xe0\x71\x96\
+\xc7\xf6\x12\x18\x45\xa9\x69\x73\x4a\xc4\xd3\xf2\xc7\x96\x67\x97\
+\xd3\x31\x63\xd7\x7b\x13\xe5\xfd\x6d\x0c\x59\x17\x0c\x7c\x14\xb8\
+\xfe\xe7\x5e\x67\xaf\xdb\x1e\x80\x2b\xa2\xeb\xdb\x9e\xaa\x3c\x6b\
+\xe6\x6e\x49\x62\x44\x93\x84\x65\x5c\x31\xb6\xa3\x4b\x31\x1f\x94\
+\x8f\xdc\xdd\x0e\x02\xaf\xa7\xe6\xee\x4a\x92\x7b\x98\xf8\x80\x68\
+\x6a\x6e\x16\x52\xd9\x4c\x33\xe0\x00\x72\x48\xae\x42\x96\x01\x65\
+\x89\x8e\xd8\xa8\xe0\x9c\x2a\x6a\x2e\x9f\xb5\x47\x37\x2c\x9a\x24\
+\xcc\x07\x17\x60\x3f\xa1\x27\x96\x09\xdd\x91\xb2\x4d\xe5\xc0\xcb\
+\x29\x0a\xa8\x1d\x1f\x42\x2e\xc9\x1a\x8a\xf3\xc2\x2b\x9a\x0e\xf1\
+\xcf\xdc\x8c\xff\xf3\x8a\x4e\xdc\x60\x3f\x55\xcc\xa3\xf2\x29\x7c\
+\xf2\x08\xa4\x5c\x33\x82\x2c\x7e\x4e\x72\x97\x4d\x2f\x50\xbb\x83\
+\x0e\x82\x29\x3d\xa8\x29\x17\xc0\x00\xd8\x76\xd0\x41\x5f\x60\x98\
+\x52\x1f\x3c\x34\xf4\x5d\x3d\x1d\x1f\x6e\x22\xf0\x65\x65\xda\xfe\
+\xb8\x3b\x4a\xd6\xf9\x03\xc9\xbb\x6b\xcc\x56\x72\x99\xf8\x7e\x07\
+\x88\xc7\x59\x8e\xcb\xeb\xbe\x3d\xba\xb6\xf5\xee\xea\x4b\xbf\xe8\
+\xb4\x80\xb7\x2b\x67\xcf\x50\xe6\x40\xdf\x3b\x13\x7d\xa9\x1d\xb9\
+\xc8\x8a\x94\x6d\x4a\x65\x31\x75\x08\x3a\x21\x49\x7e\xac\x24\x37\
+\x49\x54\xfd\x31\x94\xd6\x08\x4a\x6d\x80\x58\x65\x07\xf1\x4a\x3f\
+\xf1\x9a\x00\x89\xb6\x48\xa7\xee\xbd\x1f\x04\x85\x59\xc2\x34\x2c\
+\x0f\xdb\xd1\xa5\x58\x27\x74\x47\x2e\xb1\x7f\x0c\xbc\x09\x7c\xec\
+\x71\x96\x37\xfe\x84\x37\xf6\x20\xe0\x34\x2d\x9a\xb8\x21\xb4\xb8\
+\x1e\xff\x27\x15\x04\xe7\x56\xfd\x28\x73\x41\xfa\xe0\x93\xe0\x94\
+\xf2\x2d\xc8\x45\xb6\xed\xe3\xcf\xb3\x24\x75\x60\xd7\x23\x9a\xa4\
+\x24\x8d\xa8\x28\x80\xaa\xa1\xc5\x12\xa8\xc1\xf8\x36\x1d\x44\x37\
+\xb5\x13\x59\xdd\x42\x74\x5d\xeb\x8f\x5a\xc8\xec\x4b\x86\xd0\xed\
+\xe6\x83\xa7\x01\x4f\x6f\x4d\x22\xfe\x22\x00\x49\x29\xf0\xca\x44\
+\x4b\xe4\xc9\x86\x7b\xbe\x61\xe7\xa6\xed\x5f\x4a\xa4\x6c\x13\xa6\
+\xe1\xdd\xb0\x4c\x28\xc1\x3a\xae\x18\xb9\xbb\x6d\x56\x6a\x9a\xfb\
+\xc8\xe3\x2c\xdf\xbc\x87\xa6\xdd\x08\x1c\x0a\x9c\xa4\x45\x94\xcb\
+\x42\x4b\x1b\x09\x7c\x59\x49\xe8\xeb\x1a\xa2\xeb\xdb\x7e\x16\x68\
+\x77\x8d\x64\x21\x49\xb6\x23\x08\xa0\x69\x68\x09\xad\x13\xf3\xf4\
+\x8f\x89\x5c\x68\xa5\xdb\xcd\x07\x63\x3f\xb1\xe7\x34\x92\x2d\x98\
+\x15\x7b\x7a\x3b\xfb\x0c\x20\x29\xe5\x1d\x02\x4c\x0d\x7c\x55\x79\
+\x4e\xcb\x3f\x96\x13\x5a\x54\xbf\xdf\x41\x61\x1a\xda\x0d\xf3\xc1\
+\x05\x98\xc7\x14\x62\x1a\x9c\x8b\x2e\xcb\x38\x03\xf8\x08\xf8\xcc\
+\xe3\x2c\xdf\xb2\x2f\xaf\xe5\xf2\xba\x4d\x24\xeb\x2b\x26\xa1\x71\
+\x5d\x74\x63\x1b\xa1\xef\xea\x09\x7d\x5b\x4b\xf0\xeb\x1a\x12\x2d\
+\x91\xfd\x3a\x76\x5d\x86\x21\x59\x5e\x70\xc9\x10\xa4\x6c\xd3\xcd\
+\xfb\x22\x17\xb5\x4f\x01\xb2\x83\xe2\xce\x04\xa6\x86\xbe\xad\x3d\
+\xcc\xf7\xde\x46\x82\xf3\x6a\x7e\x52\x14\xf1\xb3\x6e\x5c\x16\x91\
+\xf2\x2c\x28\x0d\xc1\xb4\xf9\xb7\xf8\xf9\x89\xd8\x26\x96\x3e\x4c\
+\x72\xdb\xf3\xc5\x1e\x67\x79\xf3\xfe\x78\x38\x2e\xaf\x5b\x00\x5c\
+\x29\xc7\xf1\xfc\x8a\x3f\xbe\x97\xb6\xdb\xb6\x94\x6b\x46\x4b\xa8\
+\x69\x59\xd5\x7d\x21\xa2\x49\xc2\x38\x38\x17\xdb\x31\x3d\xb0\x4f\
+\xea\x81\x94\x6f\x79\x24\x95\xc8\xdb\xbc\x4f\xf4\xdc\x15\x00\xd9\
+\x41\x69\xe3\x81\x33\xd5\x90\x72\x51\x74\x6d\x0b\xe1\x65\x8d\x44\
+\x56\xb7\xa0\x06\xe3\x24\xda\x23\x28\x8d\x21\xd4\x8e\x18\x6a\x24\
+\x91\x34\xcd\x5b\x4d\x69\xca\xcc\x0a\x06\x1d\xa2\x59\x46\x67\xd7\
+\xa3\xcb\x32\x22\xe5\x5b\x30\xf4\xcc\xc0\xd0\x3f\x1b\x7d\x89\x1d\
+\xa9\xc0\x42\xcd\xa5\x5f\x10\xf8\x72\x7b\xe5\x9e\x7d\x52\x19\x45\
+\xd3\x8e\x7c\xdb\xe3\x2c\x3f\x8d\x5f\x40\x5c\x5e\xf7\xd0\xc8\x9a\
+\x96\x65\x15\x27\xbc\x9b\x36\xdd\x38\xdf\x38\x1e\x43\xdf\x2c\x62\
+\x95\x1d\xc4\xb6\xf8\x88\x6d\xf2\x11\xf3\xfa\x50\xea\x82\x28\xcd\
+\x61\x12\xbe\x28\x5a\x44\x41\x8b\xa9\x9d\xa7\x29\x01\x04\x83\x84\
+\x68\x96\x90\xb2\x8c\xc8\xc5\x36\x0c\x7d\xb3\x30\x0e\xc9\xc5\x34\
+\x38\x17\xb9\xbb\x6d\x2e\xf0\x3f\xe0\x6d\xd2\x36\x63\xdf\x07\xd3\
+\x74\x57\x2a\xcb\xe3\x2c\x9f\x0d\xcc\x76\x79\xdd\xd7\x98\x86\xe7\
+\x0d\x36\x0d\xcf\x3b\x18\x18\x96\xca\x6c\x66\x6b\x8a\x7a\xb8\x16\
+\x56\x50\x43\x0a\x5a\x2c\xb1\x4d\x31\x82\x4e\x48\x3a\xac\x46\x29\
+\xe9\xb4\x19\x74\xcb\x49\xd6\x65\xd4\x00\x6b\x49\x96\x34\x6e\x02\
+\xc6\xd8\x27\x95\xbd\xb4\x23\x40\x02\x73\xab\x51\x9a\x42\xa7\xba\
+\xbc\xee\x7c\x8f\xb3\xbc\xfe\x17\xc0\xc8\x85\xed\xaf\xaf\x4d\x7b\
+\xc8\x5b\x43\x5f\x41\x16\x73\x4d\x59\xc6\x32\xd3\xd0\x6e\x7d\x52\
+\xd1\x48\x2f\xa0\x58\x53\xd4\xc3\xd4\x60\x1c\x35\x18\x4f\xea\x22\
+\xaa\x6c\xb3\x8a\x82\x4e\x44\xd0\x8b\x88\x66\x19\xd1\xa6\x47\xb4\
+\xca\x08\x92\xf8\x09\xc9\xc5\xbe\x45\xc0\x62\x60\x4b\x57\x0d\x66\
+\x7f\xb0\x1c\x92\xaa\x70\xff\x86\x9d\xf6\x6c\x75\x79\xdd\xb2\x60\
+\xd3\x9b\x45\x9b\xde\x92\x0a\xe7\xb6\xde\x4f\x22\x15\xea\x85\x81\
+\x90\xc7\x59\x1e\xd9\xcd\xdb\xea\xb3\x4c\xe8\x8e\x68\xd3\x6f\xf3\
+\xe8\xd5\x40\x0c\xff\xc7\x15\x64\x9e\xdb\xff\x64\xe0\xd9\xfd\x6c\
+\x3d\xcc\x6a\x58\xb9\x2c\xf0\x45\xfa\x7a\x98\xf5\xa8\x52\x04\x59\
+\x7c\x34\x35\xdd\x35\x03\xdf\xed\xac\x07\x9d\xc3\x60\xd5\x39\x0c\
+\x36\xc0\x92\xca\xc6\xca\x3b\xe9\x22\x98\x0a\x83\x3b\x3c\xce\x72\
+\x65\xa7\x44\x19\xbf\x69\x80\xfc\x00\x70\xe2\xa9\x41\xfb\xf6\xf0\
+\xf7\x0d\x2e\xaf\xfb\x2d\xeb\xb8\xe2\xd3\x3a\x66\x6e\x9f\x72\x7d\
+\x6f\xaf\x23\xf3\x9c\xfe\xff\x70\x79\xdd\xd3\xf6\x26\x21\xb7\x07\
+\x72\x64\x70\x7e\xcd\xd6\x4d\x93\xb7\x4d\x0f\xf6\x13\x7a\x92\x0a\
+\xa9\x7f\x48\x0f\x6d\xa9\xbf\x5f\x95\x88\xfc\xf6\xe5\xc5\x8c\x29\
+\xe9\x65\x96\xe1\x15\x4d\x5b\xdb\x0c\x26\xee\xe7\x7b\x99\xda\xbe\
+\xd3\x2a\xaa\x69\x70\x2e\xa6\x64\x13\xfa\x92\xdf\xa2\x72\x7f\x0f\
+\x00\xf9\xd2\x7c\x68\x61\x27\x1a\xab\xd6\x7f\xed\x79\x3f\xea\x1e\
+\x4e\x2f\xae\xe8\xc6\xf6\x63\x02\x3b\xb1\x04\xd9\x4f\xee\x0d\xa2\
+\x70\xe5\xde\xb6\xa1\x1e\x00\xc8\x5e\x4c\x53\x82\x24\xde\xe8\x38\
+\xbd\x6f\xda\xf1\xc0\x97\x95\x44\x3d\xad\x93\x5d\x5e\xf7\x61\xfb\
+\xe9\x56\xce\xf7\xbd\xb5\x2e\x8d\x42\x42\xb4\xea\xb1\x1f\x5b\x06\
+\x5d\xd3\x48\x76\x00\x20\x3f\x43\xa6\xdb\x8f\xef\x99\xc6\xbd\xaa\
+\x29\x2a\xcd\xd3\x96\xef\x17\x2b\xe2\xf2\xba\x73\x13\xad\x91\x1b\
+\xdb\xdf\x4a\x2f\x12\xb3\x1d\xe5\x44\xca\x33\xbf\xea\x71\x96\xd7\
+\x1e\x00\xc8\x2f\x6b\x45\xbc\x72\x91\xf5\x65\xdb\x71\xe9\x44\x3a\
+\xfe\x99\x9b\x89\xac\x6e\x3e\xd3\xe5\x75\x1f\xdf\xc5\xb7\x70\x65\
+\xdb\x7f\xd6\x74\xca\x9c\x3a\x4e\xe9\x0d\xf0\xca\x6f\x59\xb7\xbf\
+\x17\x0b\x02\x30\x2d\xfb\xa2\xc1\x69\xdd\x64\x9a\xa2\xd2\xf4\xf0\
+\xe2\x2e\xb5\x22\x2e\xaf\x7b\xb0\xd2\x14\xba\xad\x75\xa7\x0d\x7e\
+\x8c\x03\x73\xb6\x32\x37\x7e\x75\x00\x20\xbf\x0e\x2b\xb2\xd0\xd0\
+\x2f\xeb\xd5\x54\x48\xb9\xdd\x17\xf9\xaa\x12\xff\xa7\x15\x93\x5c\
+\x5e\xf7\x75\x5d\x15\xb9\x34\x3f\xb1\x94\x44\x5b\xba\xf5\xc8\x76\
+\x0f\x46\x90\xc4\x8b\x76\xcc\x59\x1c\x00\xc8\xaf\xc0\x8a\xe4\x5c\
+\x39\x6c\x2b\x45\xf7\x36\x69\xbc\xf7\x5b\xd4\x90\xf2\x88\xcb\xeb\
+\xee\xb9\x8f\xad\xc7\xd4\xf0\xe2\xfa\x4b\x76\x66\x58\x32\xf6\xcf\
+\x26\x35\xdd\xbd\xfc\x5b\x57\xe8\xef\x0a\x20\x1e\x67\xf9\x02\x7d\
+\x59\xc6\x63\x99\xe7\xa4\x13\xa8\xc4\xbc\x1d\x34\x3d\xb6\x18\xe0\
+\xda\x7d\x08\x8e\x5e\x5a\x2c\xf1\x6c\xdd\xad\xf3\x3b\xad\x9d\xe4\
+\x5c\x3b\x12\x41\x12\x2f\xee\xc2\x26\xf6\x03\x00\xd9\x0b\x79\x36\
+\xe7\x8a\x61\x5b\xf9\x37\xb6\xe7\x45\x5e\x58\x89\xff\xd3\x8a\x4b\
+\x5d\x5e\xf7\xbd\xfb\xe8\x3a\xd7\x36\x3e\xb4\xa8\xd3\x8e\xd7\xd6\
+\x09\x25\xd8\x8e\x72\x7e\xe4\x71\x96\x97\xff\x1e\x94\xf9\xbb\x03\
+\x88\xc7\x59\xbe\x49\xe7\x30\xdc\xd2\xed\xf6\x9d\x76\x8d\xd4\xa0\
+\xee\x86\x39\x44\x3d\xad\xb7\x6c\xdd\xbb\x77\x2f\xac\xc7\xd3\xed\
+\xaf\x79\xa6\xb6\x3e\x9f\xde\x61\x21\x9a\x25\xf2\x92\xd7\x9d\xf6\
+\x7b\xd1\xe7\xef\xd1\x82\xe0\x71\x96\xdf\x6f\x9f\x54\xf6\x1f\xc7\
+\xc9\xe9\x9b\x14\x27\x7c\x51\xaa\x2e\xfa\x94\x58\x45\xc7\x43\x2e\
+\xaf\xfb\xfa\x3d\x00\x86\xcd\xe5\x75\xff\xc3\xf7\xbf\x0d\x97\xd7\
+\xdd\xd6\xb9\x38\x3c\xf7\x86\x51\xe8\x7b\x66\x3c\xe2\x71\x96\x7f\
+\x78\x00\x20\xbf\x01\x87\x35\xef\x6f\x63\xd0\xef\xc4\x27\x12\xaf\
+\xf2\x53\x79\xce\x47\x44\x3c\x2d\x0f\xbb\xbc\xee\x7b\x7e\x06\x38\
+\x8e\x01\x5e\x6d\x79\x6e\xc5\xa5\xb5\xd7\x7e\xd5\x89\x74\xd7\x36\
+\xb1\x94\xac\x3f\x0f\x7c\x1f\xb8\xf5\xf7\xa4\xc4\x2e\x2d\x18\xfa\
+\xa5\xc5\xe5\x75\x5f\x19\x59\xdd\xfc\xa4\xf7\xb4\x0f\x3a\xf5\x8a\
+\xe8\x32\x0c\xe4\xdf\x7d\x28\xf6\xc9\xbd\xde\x21\xd9\xb7\x33\x6b\
+\x37\xe7\xf8\x23\x70\x46\xd4\xd3\x7a\x7a\xd3\xa3\x8b\xe8\xc4\xcb\
+\x46\xb2\xe5\xc2\x39\xfd\x04\x74\x99\xc6\x63\xf6\x90\x04\x77\x6f\
+\xc6\x78\x00\x20\x7b\xa9\xc0\x07\xfd\x9f\x56\xdc\x58\x7d\xf1\x67\
+\xbb\xec\xa2\xb3\x1d\xdb\x83\x9c\xcb\x86\x61\x1c\x94\xf3\x25\x30\
+\x9b\x64\x2b\x46\x14\xe8\x06\x8c\xf6\x7f\x5a\x71\x7e\xc7\x8c\x8d\
+\xf8\x3f\xf3\x76\x6a\x86\x86\x64\x81\x70\xc9\x7f\x8f\x43\x5f\xe6\
+\xb8\xfc\xa7\x70\xd2\x1f\x00\xc8\xaf\x13\x24\xff\x68\xfb\xcf\x9a\
+\x4b\xeb\x6f\xdb\x75\x53\x99\x20\x89\x98\x47\xe5\x63\x3e\xa4\x10\
+\x7d\xa9\x03\xc1\xa0\x43\x69\x0e\x13\x5e\xd6\x80\xef\xad\xf5\xbb\
+\x3d\xaf\x94\x67\xa6\xe4\xe5\x49\x18\x5c\x59\xb7\x78\x9c\xe5\xf7\
+\xff\x42\x63\xeb\xd2\xf3\x4b\xfc\x3f\x10\x8f\xb3\xfc\x32\x97\xd7\
+\x2d\x88\x26\x69\x6a\xdd\x2d\xf3\x3a\x6d\xbf\xa1\x29\x2a\xc1\x05\
+\xb5\x04\x17\xfc\xf4\x35\x35\x63\xff\x6c\x0a\x9f\x3a\x02\x43\xef\
+\xcc\xdb\x7f\x29\x70\x1c\x70\x52\xf7\x2d\x48\x2e\x75\x9c\xda\xe7\
+\xf1\x92\x57\x26\xa1\xef\xe1\xd8\xab\x73\x65\x9c\xe9\xc2\x39\xfd\
+\x04\x0c\xbd\x33\x2f\xf6\x38\xcb\xef\xf9\x3d\xeb\xed\xff\x0d\x40\
+\x52\x20\xb9\xd6\x3c\xba\xe0\xda\xd2\x77\x27\x93\x75\xfe\x40\x04\
+\x83\xee\x67\xfd\xde\xd0\x2f\x8b\xe2\x17\x8e\xa6\xe0\xfe\xb1\xe5\
+\xa2\x4d\x3f\xec\xf7\x92\x0c\xfb\x7f\xef\x83\xec\x62\xde\x76\x01\
+\x53\xa3\x6b\x5b\xaf\x68\xfb\xaf\x87\xc0\xe7\x15\xbb\xdd\x19\x5c\
+\x90\x45\x4c\xc3\xba\xe1\x38\xad\x2f\x8e\x13\x7b\x22\x18\xa5\x4b\
+\x3d\xce\xf2\x69\xbf\xa2\xb1\x1c\x00\x48\x17\x2a\x77\x10\x70\x71\
+\xbc\xda\x7f\x59\x78\x49\x03\xd1\x8d\xed\x24\x5a\x23\x68\x71\x15\
+\xd1\x2a\x23\x17\x5a\x31\x0e\xca\xc1\x34\xac\x1b\x82\x5e\xf7\x17\
+\xe0\x75\x8f\xb3\x3c\xfc\x2b\x1b\xc3\xaf\x1b\x20\xbf\x13\x29\x25\
+\xd9\x42\xe9\x02\x72\x49\xb6\x1c\xf8\x49\x32\x0f\x2c\x01\x16\xa6\
+\x42\xdf\xff\x77\x72\x00\x20\x07\xe4\x80\x93\x7a\x40\x0e\x00\xe4\
+\x80\x1c\x00\xc8\x01\x39\x00\x90\x03\x72\x00\x20\x07\xe4\xb7\x25\
+\xff\x37\x00\x7f\x6a\xee\x9c\x1e\x29\x89\xcf\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x04\x14\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8d\x89\x1d\x0d\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x91\x49\x44\
+\x41\x54\x38\x8d\xad\x94\x4d\x6f\x1c\x45\x10\x86\x9f\xea\xee\x99\
+\xd9\x5d\xdb\x6b\xaf\x70\x62\x01\x46\xc2\x8a\x65\xc7\x20\x12\x05\
+\x29\x02\x09\x11\x19\x2c\x04\x52\xe0\x5f\x44\x3e\x20\x7e\x04\x77\
+\xee\x1c\xac\x1c\xf8\x0f\x90\x13\x38\x24\x91\x10\x12\x09\xb1\x08\
+\x84\xd8\x89\x43\x90\x0f\xfe\x5c\xef\x87\xf7\x73\x76\x67\xba\x38\
+\xcc\xae\x71\x12\x72\xa3\xa5\x3a\x75\xf5\xd3\x55\xef\x5b\xdd\xa2\
+\xaa\xfc\x9f\xcb\xbd\x68\xe3\xed\x2b\x72\x09\xcc\x92\x75\xba\xa8\
+\xa9\x5c\x00\x10\xab\x6b\x69\x22\x37\xc0\xaf\xde\xbd\xaa\xb7\xfe\
+\xeb\x9c\x3c\x5b\xe1\xb9\xcf\xa5\x14\x25\xb2\x32\x56\x98\xf8\x74\
+\xfe\xec\x82\x9b\x9c\x3c\xed\x26\x27\x4e\x01\x50\xae\x1d\x50\x2e\
+\xef\x27\x1b\xeb\x0f\x92\x46\xbb\xf6\x5d\xec\x74\xf9\xde\xd7\x5a\
+\x7d\x21\xf0\xe2\x15\x79\x0f\x63\xbf\x7d\xf3\x8d\xb7\x0a\xe7\xcf\
+\x5d\x88\x54\x3c\xa3\xd1\x04\x22\x06\x00\xef\x3d\xcd\x5e\x15\xa3\
+\x96\xdf\xee\xad\xc5\xf7\xff\xfc\xbd\x8d\x4f\x3f\xbb\x7d\x55\x7f\
+\x7a\x0e\x98\x55\x66\x1f\x7f\xf8\xc1\x47\xa5\x91\x52\x48\x60\x23\
+\xf2\xc1\x28\x5e\x3d\x8a\xc2\x20\x4f\x51\xba\xfd\x26\x5e\x3d\xad\
+\x6a\x8f\xeb\x3f\x7e\x5f\x8d\x5d\x7a\x66\x58\xa9\x19\x92\xa3\x44\
+\x56\xe6\xe6\xe7\x0b\x61\xd1\x73\xd0\xdc\x22\xf1\x3d\x8e\xba\x87\
+\x34\xe3\x0a\x8d\xee\x21\x8d\xb8\x42\x23\x3e\xa4\xd1\x3d\xa4\x97\
+\x76\xd9\x3d\xfa\x8b\xb0\xe8\x99\x9b\x9f\x2f\x44\x89\xac\x0c\x39\
+\x66\x68\x40\x21\x37\x7a\x79\x6e\x61\x36\x7a\x78\x70\x87\xc0\x45\
+\x34\xfb\x15\xda\x69\x85\x56\x5a\xa5\xe3\x6b\x74\x7c\x8d\x56\x5a\
+\xa3\x99\x54\x69\xf6\x2a\x18\x63\x79\x78\x70\x87\xb9\x85\xd9\xa8\
+\x90\x1b\xbd\x9c\x99\x78\xec\xb2\x59\x9a\x9e\x99\x0e\x0e\xbb\x5b\
+\xf4\x69\x11\xeb\x11\xa9\x3a\x40\x41\x14\x04\x32\x69\x14\x8f\xe2\
+\xf1\x24\xf4\xe8\x69\x8b\x72\x7b\x8b\xe9\x99\xe9\xe0\xc1\xfd\x8d\
+\x25\xe0\x96\x03\xb0\x4e\x17\x8b\xa5\x11\xd7\xd1\x32\x41\xce\xd0\
+\xa2\x4c\x24\x11\x62\x40\xc5\x23\x99\xda\x78\xaf\x78\xa3\x78\x55\
+\x7a\xc4\x04\x91\xd0\xd1\x2a\xc5\xd2\xa4\xb3\x4e\x17\x8f\x2b\xd4\
+\x54\x2e\x8c\x4d\xe4\xd8\x4b\x1b\xb8\x48\xf0\xb6\x4d\x6c\x3a\x88\
+\x11\x8c\x81\x8c\x98\xf9\xa2\x1e\xbc\x28\x82\xe2\xc4\xd0\xd7\x26\
+\xa5\xf1\xd7\x18\xce\xaa\x63\x90\xef\x4d\x1f\x6b\x05\x8c\xc1\x3a\
+\xc1\x3a\x41\x6c\x06\x14\x11\x74\x40\xf4\x29\xf8\x54\x49\xad\x22\
+\x46\x31\x1e\x3c\xfd\xe1\x9d\x03\x0d\xad\xac\xd5\xeb\xf5\x4b\xb9\
+\xa9\x1c\x31\x31\x36\xcc\xa0\xc6\x09\xc6\x66\x40\x00\xf5\x9a\xc1\
+\x12\x10\x03\x22\x10\x69\x9e\xfa\x7e\x0d\xac\xac\x1d\xbb\x9c\x26\
+\x7a\xa3\x5a\x39\xea\x47\x61\x0e\x1b\x18\x6c\x20\xb8\x50\x08\x22\
+\x21\x88\x0c\x41\xee\xdf\x70\x91\xc1\x85\x26\xcb\x73\x42\x14\xe6\
+\xa8\x55\x1a\xfd\x34\xd1\x1b\x27\xe6\xd0\xaf\xee\x3c\xaa\x24\x8e\
+\x88\xc0\x05\x59\xcb\x81\x60\xc3\x0c\x12\xe6\x07\xb0\xd0\xe0\x02\
+\x19\x48\x02\x41\x10\xe2\x08\xd9\xd9\xac\x24\xe0\x57\x8f\x81\x77\
+\xaf\xea\xad\x5e\x12\x5f\xdb\xf8\x65\xa7\x5b\x08\x27\x10\x21\x33\
+\xc4\x66\x87\x5d\x64\x08\xa2\xec\x12\xb1\x82\x98\x2c\x46\x82\x71\
+\x36\x6e\xef\x76\x7b\x49\x7c\x6d\xf8\x59\x1c\xbf\x94\xd8\xe8\xf2\
+\xf6\x66\xbd\xb3\xf7\xb8\x49\xce\x16\x31\x58\xf0\x8a\xf7\x99\x09\
+\x3e\xcd\x34\x44\xc1\x88\x21\x6f\x8a\xec\x3e\x69\xb1\xb3\x59\xef\
+\xc6\x46\x97\x9f\x7b\xcb\x22\x52\x98\xfb\x84\x8f\xc7\xa6\xed\x37\
+\x53\xb3\x85\xdc\xd9\xc5\x89\xd0\x16\x52\xc4\x29\x88\x1f\x28\x63\
+\xf0\x89\x90\xb6\x2d\xeb\x37\x6b\xfd\xfd\xcd\x76\xb7\xf2\x28\xfd\
+\xe2\xc9\x4d\x7e\x00\xca\xaa\xda\x3b\x09\x1c\x07\x26\x47\x5e\x61\
+\xe6\xcc\xa2\x7c\x99\x2f\xda\x8b\x53\x0b\x39\x5b\x7c\x39\xb4\xc5\
+\xd3\x01\x00\x47\xfb\x7d\x8e\xb6\x7b\x7e\x6f\xbd\x9b\xb6\xaa\xe9\
+\xda\xe3\xeb\xfa\x55\xe7\x80\xbf\x81\x32\xb0\xfd\x14\x70\x00\x2d\
+\x02\x93\x40\xe9\xd5\x77\x78\x7f\xe4\x94\x79\x37\xff\x12\xe7\x9d\
+\xe3\x75\x80\xa4\xcf\x56\xbb\xac\x7f\x34\xf7\xf4\xf6\xce\xaf\xfc\
+\x0c\xd4\x81\x03\x60\x4f\x55\x93\xa7\x5a\x3e\xb9\x44\xc4\x02\xf9\
+\x13\x11\x0d\xe6\x3f\x01\xba\x83\x68\x03\xb1\x3e\x03\xf8\x07\x66\
+\x9b\xca\x76\xb3\x47\x45\x9a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x05\xa6\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x05\x6d\x49\x44\x41\x54\x78\x5e\xed\x9b\x5f\x6c\x53\x55\
+\x1c\xc7\x7f\xa7\x0e\x71\xc4\xf9\xb0\x46\xc0\x16\x85\x44\xba\x84\
+\x80\xac\x9d\xc9\x16\x42\x14\x14\x13\x58\x19\x4f\x9a\xf9\x62\x64\
+\x81\x17\x4d\xf0\x0f\xd3\x07\xcd\x78\xe0\x81\x19\x1f\x64\xb2\x82\
+\xb2\xcd\x48\x66\xe2\x8b\x8b\x3c\x68\xd0\x68\xa2\xc6\x61\xd6\x4e\
+\x4c\xd6\x11\x48\x16\x29\x26\x1a\x21\x80\xc9\x6a\xe2\xc8\x36\xb5\
+\xf6\x98\xef\x8d\xe7\xee\xf4\xd0\xde\x73\xaf\x05\xef\xa9\xbb\x27\
+\xe9\x43\x7b\xee\x3d\x3d\xbf\xcf\xf9\xfe\xfe\x9c\xd3\x5b\x46\x8b\
+\xbc\xb1\x45\x6e\x3f\x05\x00\x02\x05\x2c\x72\x02\x1e\x5c\xe0\x60\
+\x28\x1e\x6d\x7c\x9a\x71\xfe\x14\x67\xac\x4e\x70\x6b\xbd\x6f\xad\
+\x31\x08\x19\xb1\x02\xe3\xf4\xfe\xc0\x78\xfb\xb0\xdb\x49\xb9\x02\
+\xd0\x1c\x49\x25\x18\xf1\x13\x8c\xb1\xb8\x3a\x70\xeb\xea\x98\xdb\
+\xef\xfa\xcf\xae\xe3\xc4\x27\x43\x9c\xf6\x0c\x64\x92\x59\xdd\x97\
+\x6a\x01\xac\xbf\xfb\xad\x95\xb7\x2f\xf9\xeb\x3c\x31\x0a\x97\x1b\
+\xcc\x44\x00\xd6\x3c\x39\x4d\xdf\x56\x28\x6e\x78\xfb\xbb\x9d\x57\
+\x9d\x20\x68\x01\x34\x47\x52\x6f\x84\x18\xbd\x54\x69\x10\x63\x01\
+\x80\x41\x91\x1f\x1e\x1a\x4f\xbe\x5c\x15\x80\x44\x34\x75\x91\x88\
+\xee\xaf\x49\x00\xc4\x7f\x18\x4a\x27\x1d\x83\x94\x56\x01\xf1\x68\
+\x6a\x9e\x11\x2d\xad\x45\x00\x44\xfc\xf7\xc1\x74\xf2\x8e\x6a\x15\
+\xc0\x9d\x06\x30\xd9\x05\x30\xef\xc1\x74\xbb\xe3\x22\x6b\x15\x90\
+\x88\xa6\x02\x00\x81\x02\x1c\x08\x04\x2e\x60\x60\x21\x24\xaf\x57\
+\x10\x03\x82\x20\x58\x03\x59\xa0\x29\xd1\x48\x9c\x97\x26\x9b\xdc\
+\xe4\xaf\xba\x32\xde\x55\xbf\x91\x2e\x10\x5e\x59\x4f\x8f\x76\xae\
+\xa6\xa6\x96\x30\xdd\x1b\x6b\xa8\x68\xc8\xf4\x95\x39\x9a\x3c\x7d\
+\x8d\xbe\x1a\xf9\x89\xa6\xaf\xce\xb9\x32\x58\xbd\xc8\x28\x00\xf5\
+\x77\xd6\x51\xe7\xf3\xeb\x68\xd3\xce\xa8\x67\x63\x32\x9f\x5c\xa6\
+\x91\xd4\x14\xcd\x5d\x2f\x78\xba\xd7\x18\x00\xab\x62\x0d\xd4\x7d\
+\xb4\x8d\x96\x35\xd8\x47\x09\x96\x21\x17\xb2\x79\xba\x94\x9b\xa1\
+\xd9\xeb\x7f\xd2\x85\x89\x3c\x35\xb5\x34\x5a\x9f\xc3\x2d\xf0\x92\
+\x1b\x14\x71\xfc\xd5\x09\xeb\x7a\xb7\xcd\x08\x00\xaa\xf1\x58\x45\
+\x48\xfb\xd4\xbb\x17\x1d\xa5\x0d\x57\xd9\xf6\xe4\x1a\xcb\x5d\x44\
+\x9b\x9d\x29\x50\x6f\xd7\x98\x6b\x97\xf0\x1d\x40\x39\xe3\x0f\xef\
+\xfb\xd6\xd3\x2a\x36\x3f\xb4\x9c\xba\x0e\x6c\x24\xb8\x90\xa5\x9a\
+\x89\x3c\xf5\x3d\x77\xc6\x95\x08\x7c\x07\x70\x60\x78\x33\x01\x02\
+\x1a\x56\xde\xab\xf1\xc2\xca\x4d\xc9\x28\xed\xee\x79\xc0\x36\xfa\
+\xbd\xde\x73\x94\xf9\xf4\xb2\x16\x82\xaf\x00\xd4\x49\x1f\x7f\x65\
+\x82\xce\x7e\xf3\xcb\x0d\x93\x86\xd4\x01\x69\x55\xec\x2e\xab\xef\
+\x52\xee\x37\x2b\x36\xa8\x01\xef\xd9\xd7\x5b\x08\x6a\x40\x43\x3c\
+\xe8\x79\x62\xd4\x6c\x00\x6f\x7e\xfe\x98\x2d\x5b\x18\x0e\x00\x72\
+\xd3\x65\x05\x44\xfe\x53\x27\x16\xe2\x04\x20\x41\x51\xa2\x1d\xea\
+\x1a\xd3\xba\x92\x6f\x0a\x50\x57\x5f\x9d\x2c\x8c\xef\x3e\xd6\xe6\
+\x58\x07\x88\x2c\xd1\xb7\x6f\xc1\xdf\x65\x97\x1a\xe9\x9f\xb2\x6a\
+\x04\xa7\xe6\x1b\x80\xee\x63\xad\x76\x1a\x83\x9c\x65\x23\x30\x61\
+\x59\xce\x78\x0f\x7f\x16\x3e\x8d\x7b\x45\x53\xef\xed\x7c\x61\x9d\
+\x9d\x15\xca\xa9\x4a\x85\xe1\x0b\x00\xac\x2e\xe4\x2f\x9a\xba\x52\
+\xc8\xef\xb2\x91\x6a\xff\xc0\xd8\x0e\xfb\x5e\xac\x30\xfa\x45\x43\
+\x4a\x04\x04\xb4\x9f\x73\x33\x56\x4a\x34\x4e\x01\xaa\xfc\x7b\x1e\
+\x1f\x2d\xc9\xdb\xb2\x3a\x50\xe2\xa2\x5f\x34\xd5\xcf\x11\x03\x50\
+\x2f\x88\xa6\xc2\x7b\x66\xf3\x67\xe6\x01\x90\x65\x8a\x48\xbe\x7f\
+\xfb\x17\xf6\x24\x11\xf1\x7b\x4f\x6e\xb1\xdf\xeb\x0c\xd4\xf5\x1b\
+\x09\xc0\xc9\xff\x65\x09\x83\x02\x62\x03\xfc\xbc\xd2\x0a\xeb\xfa\
+\x8d\x04\x20\xa7\x3f\x35\x88\xa9\xc1\x4f\x35\xb0\x63\xef\x5a\xea\
+\xd8\xb3\x70\x94\xef\xd4\xaf\xba\x4f\x39\x5f\xf0\x25\x08\xca\x41\
+\x4c\x05\x20\xab\x03\x13\x56\xe3\x83\x0a\x40\x4d\x9f\xb2\x7b\x95\
+\xcb\x2e\x46\x64\x01\x2f\x00\x10\x1f\xe4\x8a\x0f\xe5\x2e\x82\xa8\
+\x68\xaa\xc4\xe5\x3a\x40\x8d\x0f\xc6\x28\x40\x9e\xa4\x4e\x01\xb2\
+\xc4\x91\x3e\x7b\x3f\xdc\x5a\xb2\x65\x96\x01\xa8\x19\xc0\xd8\x4a\
+\xb0\x24\xcd\x29\x35\xbb\x2a\x71\xb1\xb3\xab\x54\x16\x0b\x17\x51\
+\x2b\x47\x37\xf2\x87\x22\x7c\x89\x01\xaa\x91\xb2\xcc\xd5\x34\xe8\
+\x98\xc4\xff\xd9\xf4\x60\xe3\x13\xbe\xa7\xde\x7a\xa1\xc1\x65\x0e\
+\xed\x76\x77\x26\xe0\x0b\x00\x55\xaa\xea\xd6\x55\x05\x24\x43\x10\
+\xe5\xb0\x1c\x07\xe4\x7e\xaf\x5b\x6a\x5f\x00\x60\xc2\x72\x2a\x2c\
+\xb7\x75\x45\x3d\xb0\x6b\x6f\xcc\xde\x2d\x22\xa5\x7d\xf9\xc1\x8f\
+\xf6\xe6\x06\x90\xb6\x75\xae\xb1\xfb\x31\x26\x6a\xff\x91\x23\x53\
+\xae\x4f\x83\x7c\x73\x01\x7c\xb1\xba\xca\x95\x76\x6e\x28\x7d\xe7\
+\x66\x0a\x15\x8d\x82\xcb\x40\xfa\x72\xb1\xa4\x73\x1b\xb9\xdf\x37\
+\x05\x20\x68\xbd\x76\x72\x6b\xc9\x0a\xba\x3d\xc5\xf1\x62\xa0\xee\
+\x5a\xdf\x00\x60\x62\xea\xa6\x08\x9f\x41\xe6\xc8\xdf\x5e\x8f\xb7\
+\x75\x86\x56\xea\xf7\x15\x40\x25\x08\x38\xd9\x3d\x7b\xfa\x1a\x7d\
+\x9f\xcd\xd3\xf4\x95\x59\x12\xbf\x02\x21\x78\x42\x39\xd6\x91\x78\
+\x4b\x98\x72\xd9\x7c\xc9\x56\xf8\xdf\x40\xf0\x1d\x80\x80\x80\x12\
+\x56\x9c\xea\xba\x35\xc4\x6d\xae\x77\x1a\xcf\x08\x00\x98\x20\x82\
+\x19\x02\x63\xa5\xf4\x56\xce\x88\xff\x15\x00\x61\x20\x54\x10\x7f\
+\x78\x85\x25\x73\x44\x77\xf9\xd7\x1f\xa4\x42\xa4\x4c\x18\x8e\x1f\
+\x4e\xbc\xfc\x02\x64\x6c\x0c\x70\x2b\xf7\x5b\x75\x9d\x31\x2e\x70\
+\xab\x0c\xd4\x8d\x1b\x00\x08\x9e\x10\xa9\x81\x27\x44\x74\x32\xae\
+\xa6\x3f\x70\x81\x6a\x5d\x20\x1e\xe9\xcf\x96\xfb\x9f\x80\x58\x15\
+\x93\x9f\x13\xc4\xff\x06\x86\xd2\xc9\x84\x93\x82\xb4\x8f\xca\x36\
+\x47\x8f\xee\x0f\x11\xef\xab\x34\x88\xc9\x00\x88\xb3\x17\x07\x33\
+\x3b\xfa\xab\x02\xf0\x60\x64\x70\x59\x91\xcd\x67\x88\xd8\xc6\x72\
+\x03\x99\x0a\x80\x73\x7e\xee\x8f\xa5\xf5\xad\xc3\x5f\x3f\x32\x5f\
+\x15\x00\xdc\xbc\x61\xf9\x91\x15\x75\x4b\xd8\x47\x8c\x58\x9b\x3a\
+\x98\x91\x00\x38\x9d\x61\x2c\xb4\x6b\x20\xbd\xfd\xc6\x87\x11\x14\
+\x03\xb4\x2e\xb0\x70\xfd\xc1\x50\x22\x12\xee\x20\x5e\xdc\xc2\x43\
+\x94\x20\x62\x21\xf4\x19\xf3\xa7\x29\xc6\x8a\x8c\x28\x5b\x2c\xd2\
+\xe8\x3b\xe3\xed\x1f\xbb\xcd\x1c\x1e\x00\xb8\x1d\xb2\xb6\xae\x0b\
+\x00\xd4\xd6\x7a\xdd\xfc\xd9\x06\x0a\xb8\xf9\x4c\x6b\x6b\xc4\xbf\
+\x01\x39\xeb\x19\x6e\x29\x62\x5a\x3b\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x08\x86\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x08\x4d\x49\x44\x41\x54\x78\x5e\xed\x9a\x6d\x50\x53\x57\
+\x1a\xc7\xff\x91\x24\x04\x49\x20\x09\x12\xc0\xae\x81\x82\x14\x2c\
+\x16\x43\xa1\x2e\x30\x0a\x74\x96\x45\xbb\x0e\x6d\x67\x76\xa4\x5d\
+\xbb\xd5\xda\xdd\x48\x3b\xfb\x61\x3b\x6d\x67\x5a\x3b\xb3\x53\xfb\
+\x65\xeb\xee\x8c\x33\x75\x77\x76\xdb\xca\xee\x6c\xd1\x9d\x6e\xb5\
+\xce\xa8\xf5\x65\xc5\x97\x81\x46\x2b\xad\x0d\x36\x80\x54\x50\xa1\
+\x25\x20\x6f\x01\x42\x48\x84\x90\xdc\x78\x77\xce\xcd\xde\x90\x9b\
+\x44\x08\x72\xc1\x89\xc9\xf9\xc2\x70\x5f\xce\x39\xcf\xef\x3c\xff\
+\xe7\x3c\xcf\xc9\x15\x20\xcc\x9b\x20\xcc\xed\x47\x04\x40\xc4\x03\
+\xc2\x9c\x40\x44\x02\x61\xee\x00\x91\x20\x18\x91\x40\x44\x02\x61\
+\x4e\x20\x22\x81\x30\x77\x80\xc8\x2e\xc0\x48\x60\x5b\xcd\x1b\xf9\
+\x34\xa2\x2a\xc3\xc9\x1b\x04\x70\x1d\xaf\xd5\xee\x69\x62\x00\x6c\
+\xfd\xc7\x9b\xd5\xc0\x92\x8f\xc2\x09\x00\x4d\xa3\xfa\x80\xf6\xcf\
+\xfb\x38\x00\x9e\x5a\x5b\x88\x84\xb8\xf8\x07\x9a\xc3\xc8\xb8\x05\
+\xff\xbd\xfc\x35\x22\x00\x22\x1e\x70\x9f\x24\x60\x34\x0d\x62\xcc\
+\x6a\xc3\xa0\x79\x84\x23\xb5\x24\x45\x02\x14\x52\x29\x56\xa8\x92\
+\x16\x54\x82\xf7\x45\x02\x0e\xca\x89\x76\xa3\x11\x2d\x5d\x37\x39\
+\xc6\xa9\x95\x29\xcc\xff\xc6\xd1\x7e\xce\xf5\xdc\xf4\x95\xc8\x56\
+\xab\x21\x16\x8a\x78\x87\xb1\xe8\x00\x3a\xfb\x6f\xa1\xa9\xa3\x1d\
+\x0e\x8a\x42\x76\x4a\x3a\xd6\x65\xe6\x23\x3f\x35\x07\x4b\xc5\x31\
+\x1c\xe3\x26\x1c\x93\x68\xea\x6e\xc3\xc5\x1b\x4d\x68\xef\xef\x82\
+\x58\x28\x44\x7e\x56\x36\x32\x52\x1e\xe2\x15\xc2\xa2\x02\x20\x2b\
+\xde\xd2\xd5\x09\xb2\xd2\x5b\x0a\x2b\xb1\x2a\x25\x23\x28\x63\xae\
+\xf5\x77\xe2\xd3\xaf\x8f\x33\x9e\x41\xbc\x21\x37\x3d\xb8\xf7\x82\
+\xe9\x7c\xd1\x00\xb0\xc6\x3f\x9e\x9a\x03\x6d\xc9\x66\xbf\x15\x9f\
+\x6d\xb2\xc4\x23\xf6\x9e\xdb\xcf\x78\x03\x01\x40\x40\xf0\xd1\x16\
+\x05\x00\x71\xfb\xc6\xb6\xab\x20\xc6\xff\xbe\x7c\xeb\xbc\xe6\xfd\
+\xfe\xa9\x8f\x19\x08\x45\x39\xab\x79\x91\xc3\x82\x03\x20\x01\xef\
+\xe8\x45\x1d\x92\xe3\x12\xb1\x73\x53\xf5\x9c\x57\xde\x97\x16\xf1\
+\x84\xf7\x4f\x7e\x8c\x81\x71\x13\x9e\x5d\x57\x32\xef\xc0\xb8\xe0\
+\x00\x58\xd7\x27\xc6\x67\x27\xa7\xcf\x6b\xf5\xd9\x97\x49\x4c\xd8\
+\x7d\x6a\x1f\x2f\x52\x98\x33\x00\xb2\xa2\x5d\x7d\x7d\xe8\x31\x0d\
+\x32\xf3\x89\x95\xc4\x20\x5b\x9d\x0a\xa5\x2c\x2e\xa0\x71\xff\x3e\
+\x57\xc7\x44\xfb\x9d\xbf\xa8\xe6\xc5\x78\xb6\x13\x22\x85\x2e\x93\
+\x11\x55\x65\x3f\x0b\xd8\xef\xa8\x75\x1c\xbd\x26\x13\x93\x5f\x88\
+\x84\x22\x24\x2b\x94\xcc\x3c\x7d\xdb\x9c\x00\x90\x4e\x2f\xb4\x34\
+\xc3\x3a\x39\x81\x18\x91\x04\x4b\xa3\x25\x18\xb1\x8d\x31\x7d\x06\
+\xd2\x24\x49\x72\x74\xcd\x06\x68\x4b\xaa\x98\xed\x8e\xcf\x46\xb6\
+\xc7\x1a\xdd\x21\x94\xac\xd1\x40\x9d\xc8\x4d\x96\xd8\x98\x43\xc6\
+\x23\x3b\xce\xb0\xcd\x8c\x09\x87\x1d\x0a\xa9\x0c\xa5\x9a\x3c\x48\
+\x25\xd3\x5b\xee\x9c\x00\x1c\xfd\x4a\x07\x17\x45\x43\x5b\x5a\xc5\
+\xec\xdd\xa4\x91\xce\x3f\x38\x5b\x8b\x9e\xd1\x7e\x94\xe7\x17\x20\
+\x59\x91\xe0\xb1\x93\x75\xff\x0f\x5f\x7c\x0f\x4b\xc5\x12\x3e\xed\
+\x07\x89\x05\xaf\x1e\xd8\xe5\x27\x03\xb2\x48\xa7\xbe\x69\x64\x80\
+\xbf\x50\x58\xe9\x89\x39\xdd\x23\x7d\xf8\xcb\xb9\xfd\x70\x81\xc2\
+\xa6\xc2\x62\xcf\x5c\x82\x06\xc0\x52\xdd\xf2\xd3\x4a\x6c\x58\xbd\
+\xce\x2f\x69\x79\xfd\xb3\xdd\x48\x90\xc7\xa1\x6c\x4d\x9e\xe7\xde\
+\x99\xa6\xcb\x18\x32\x9b\x51\xfb\x9b\x3f\xf1\x6a\x3c\xdb\xd9\xb6\
+\x7f\xbe\x85\x9f\x24\xaa\xfc\xc6\xbc\xe3\x04\xf6\x3c\xf7\xb6\xdf\
+\x98\x57\xba\xdb\x98\xad\xd4\xdb\x6b\x82\x06\x30\xbd\x9a\xbb\x02\
+\x46\xf2\x3f\x9e\xfc\x08\x1d\x03\x3f\xe0\xd7\xe5\x1b\x38\x00\x94\
+\x12\x05\xef\xfa\x67\x07\xf8\xc3\x91\x0f\x60\xa7\xed\xa8\xc8\x5f\
+\xeb\x19\xf3\x60\xc3\x79\x3c\xf6\x50\x56\xc0\xed\x36\x90\xd7\x84\
+\x19\x00\x3b\x5e\x3d\xf0\x2e\x47\x36\x41\x03\x98\x4d\x02\x6f\x1c\
+\xdc\x0d\x65\xfc\xe2\x4a\x60\x4f\xfd\x3e\xc4\xc5\xc6\x22\x2d\xc9\
+\x5d\x44\x91\x46\xe6\x19\x45\x47\x41\x5b\xb4\xc5\x4f\x02\x37\x87\
+\x7f\xc4\xb1\xd6\x33\x48\x4d\x4e\x46\xfc\x52\x29\x73\x7f\x72\xca\
+\x8e\x1b\xb7\x7a\xe1\xa4\xa8\x3d\x3b\x2b\x7e\xf7\xe6\x8c\x27\x42\
+\x24\xb8\xd8\xa7\x1c\xf8\x6d\x09\x37\x08\xee\x3d\x5b\xcb\xe4\xe9\
+\x8b\x19\x04\x29\x9a\x42\xbd\x51\x77\xd7\xd8\xb2\x5c\x9a\x82\x2c\
+\x65\x26\x84\x02\x21\xf3\x8c\xd5\x69\x83\x61\xa8\x05\x76\xca\x1e\
+\xf0\x9d\xde\x61\xd3\x89\x97\x0b\x9e\xaf\x9c\x11\x00\x89\xb0\xba\
+\x96\x66\xd8\x26\x27\x98\xa8\x4e\x2a\x38\xb2\x0b\xdc\x8f\x6d\xb0\
+\xef\x76\x3f\xda\x86\xaf\xcd\x1a\x5c\x65\x62\x29\x26\x29\x3b\xa8\
+\x3b\xd4\x8c\xcf\x06\x05\x80\xf4\x40\x12\xa1\x4e\xaf\x44\x48\x1a\
+\x13\x83\xec\x15\x8b\x9b\x08\x59\xcd\x03\xa8\x6f\x3b\x83\xf1\xc9\
+\x31\xc4\xc5\x70\xcb\x68\xd6\x4a\xd7\x1d\x17\x28\x97\x0b\xe4\xaf\
+\x00\x02\x44\x45\x45\x05\x4c\x9b\xc9\xfd\x09\xbb\x1d\x13\x96\x91\
+\x06\xea\xbb\x6f\x76\xf1\x7e\x28\xba\x10\xa9\xf0\xf9\x93\x7f\xc7\
+\xf5\x4b\x47\x66\x5d\xfd\x7b\x79\x80\x77\x00\xc4\x63\x8e\x5c\xd4\
+\x21\x85\x29\x86\x5e\x99\x77\x42\x44\xb4\x7f\x4c\x57\x8b\xbe\xce\
+\x56\x24\xda\x87\x20\x72\x8c\x43\x26\x57\xdc\x8b\xad\xcc\x3b\x4e\
+\xa7\x13\xb6\x31\x33\x2c\x2e\xe1\x75\xca\xea\xf8\x0f\xef\x00\xd8\
+\xc8\x4c\xca\x61\x3e\x6a\x02\xfd\xe0\x15\x98\xed\xee\xf4\x1b\x1d\
+\xe7\xa1\x98\xe8\x45\x41\xe9\xcf\xef\x19\x80\xd9\x34\x08\xbd\xee\
+\x1c\x7a\x21\x3d\xf1\xb2\xf6\xf0\xcc\x41\xf0\x9e\x47\x01\x98\xf3\
+\x3f\x72\x1a\xe4\x3e\x10\xa9\x9a\xb3\x27\x4c\x51\x0e\xb4\x8e\x5c\
+\x9d\x36\x3e\xd4\x00\x90\xf9\x7a\x1f\x89\xbd\x50\xf4\x74\xd0\xe5\
+\x71\xfb\x40\x17\x2e\xf7\x18\x90\xa2\x92\x73\xd7\x20\x94\x3c\xc0\
+\x3b\x51\xd1\x77\xb4\x93\xc4\x83\x91\xc4\xfa\xcc\x02\xc6\x2b\x7c\
+\x8b\x25\xb2\xbd\x92\x93\x9f\x0b\x37\xf4\xcc\x5f\xcd\xca\x47\xb0\
+\x3a\xed\xe1\xd0\x07\xc0\x6e\xa5\xed\xc6\x6e\x5c\x33\x76\x33\x20\
+\xd8\x16\xe8\x58\x5c\x24\x14\x62\x95\x3a\x15\xd9\xea\x34\x88\x85\
+\x51\x80\xf7\x0f\xd8\x1d\xe7\x21\x19\xbe\x8e\xf5\x4f\x3d\xc3\x01\
+\x33\x35\x35\x85\xd3\x47\xea\x01\x01\xf7\x73\x87\x04\x95\x02\xeb\
+\x9e\x9c\xae\x1b\xc8\x4b\x6c\x0c\xe8\x96\x66\x1e\xd6\xfe\xea\xaf\
+\x9b\x17\x24\x08\xce\x14\x3b\xdc\x3f\x8c\x58\x31\x60\x1e\xf5\x3c\
+\x46\xce\xff\x95\x32\x19\xe4\x32\x99\x5f\xad\x4f\x7b\x23\x30\xf7\
+\x00\xfa\xcf\xa0\xce\xcc\x42\x56\x6e\x01\x67\x98\x6f\x2f\x19\xa0\
+\xbf\xd4\xcc\xb9\xb6\x79\x6b\x25\x96\xa9\x94\x9e\x6b\x93\xb7\x6d\
+\x30\x34\xea\x60\xb3\x98\xd1\x9d\xf9\xec\x61\x6d\xd9\x2b\x8b\x0f\
+\x60\x3e\x81\x95\xdd\x09\x60\xbc\x02\x45\x62\x12\x34\x45\xeb\x21\
+\x14\x45\x7b\xba\x3c\x54\xfb\x05\x46\x4c\xee\x4c\xb5\xa0\x78\x0d\
+\x9e\x28\xd6\x78\xee\x0d\xf5\xf5\xa0\x4d\xdf\x08\xca\xe9\xc4\x50\
+\xce\x2f\xe1\x10\xcb\x66\x4f\x85\xe7\x3d\x59\xde\x3a\xe0\xf8\x01\
+\x60\x36\x02\xfa\x83\x10\x8a\x44\xd0\x14\x95\x32\x30\x48\x1b\x1e\
+\x1a\xc5\xe7\xfb\x8f\x23\x21\x51\x81\xaa\x6d\x4f\x7b\x46\xef\x68\
+\xd6\xc3\x78\xb3\x03\x90\xc8\x40\xad\x7d\x11\xc2\xe8\x58\x04\x9d\
+\x0a\xf3\x66\x03\xdf\x1d\x91\x22\xa7\xf1\x13\xc0\x6e\x45\xc6\xaa\
+\x5c\xa4\x3f\xfa\x18\x33\x02\x91\xc2\xc3\x2b\xd5\x8c\xeb\x53\xce\
+\x29\x18\x1a\x2f\x30\xba\x87\xfa\x71\x20\x6b\xfa\x3c\x31\x20\x00\
+\xbe\xe7\xc8\x67\x7f\x49\x0a\x05\x4a\x73\xf3\x20\x16\x4d\xff\x5e\
+\x48\xb2\xce\xdb\x86\x93\x50\x98\x6f\xf8\x49\xc2\x3a\x36\xca\x24\
+\x3c\xc4\xe5\x07\x33\x2a\x90\x94\xbe\x86\x33\x1d\x0e\x80\x50\xf9\
+\x44\x26\x56\x22\x91\xe4\x3d\x92\x55\xe8\x0b\x56\xfe\xfd\x17\xcb\
+\xe5\x94\x25\xb3\xa0\xa4\xdc\x23\x87\xce\x6b\x2d\xe8\xfa\xbe\x15\
+\x37\x55\x45\x75\xc2\xe4\x95\x7e\x07\x94\xb4\xcb\xf9\x69\x75\xf1\
+\x4b\xee\x2f\x44\x42\xbd\xd5\xd5\x6c\x24\x55\xdd\xbb\x81\x00\x54\
+\x68\x4f\xcf\x68\x63\xc8\x03\xa8\xff\x57\x99\xdc\xe9\x8c\xae\x87\
+\x40\xa0\x09\x04\x00\x34\xfd\x5a\xc5\x8e\xba\xbd\x77\x5b\xe4\x90\
+\x06\x70\xb6\x66\x63\x19\x4d\xd3\x47\x21\x10\xc4\x93\x20\x48\x82\
+\x21\xdb\xdc\x7b\xfe\x97\xb0\x59\xc6\x40\x9e\x11\x8b\xa6\xb6\x3f\
+\xb9\xbd\xe1\xff\x55\xd5\x34\x8e\x90\x05\xc0\xba\xbd\x50\x28\xa4\
+\x35\xc5\x65\x02\x76\x2b\xf4\x5d\x69\xcf\x16\x48\x0b\x8c\x00\xf5\
+\x4c\xc5\x8e\xb3\x06\xef\x67\x42\x0e\x00\xe3\xf2\x94\x84\x9c\x8e\
+\x94\x29\x96\xa9\xa0\x29\x2e\xe1\x24\x43\x24\x17\xf0\xce\xfe\x88\
+\xb1\x24\x09\xba\xfa\xed\xa5\x3b\x2e\x8a\x5a\xe2\x2b\x89\x90\x02\
+\xe0\xd6\x7b\x4c\x33\x04\xb4\xda\xd7\xe5\x89\xa1\xa4\x26\xf8\xbc\
+\xf6\x04\xb2\x56\x67\x70\xb2\x40\x72\xcf\x5b\x12\xa0\xe9\xbd\x15\
+\x3b\xea\x5e\x23\xd7\x43\x0a\x40\x5d\xcd\xc6\x4f\x04\xc0\xb6\x9c\
+\xfc\x42\x2c\x4f\xf3\xff\x6a\xe4\x62\xfd\x65\xb4\x36\xb9\x0f\x4e\
+\x7d\xeb\x00\xd6\xed\x0d\x8d\x0d\x30\xf5\xdd\x02\x68\x57\x1e\x91\
+\x43\x48\x01\x38\xb3\x6f\x63\x83\x54\x2e\x2f\x2d\x2a\xdf\xe4\x17\
+\xd4\xfb\x8c\x03\x38\x76\xa8\xce\x73\x3d\x41\xa5\x44\xd5\x56\xff\
+\xaf\x7f\xd9\x6a\x10\x34\xb6\x57\xec\x38\x4d\x80\x86\x4e\x23\x00\
+\x14\x89\xaa\x52\xdf\x23\x31\xd6\xf5\xad\xe3\x36\x8e\x31\xbe\x05\
+\x11\xb9\xc9\x02\xa0\x81\xf7\x36\x68\x4f\xbb\x4f\x85\x43\xa5\xdd\
+\x0d\x80\xb7\xeb\xfb\xda\xe2\x2b\x85\x07\x0e\x80\xaf\xeb\xfb\x02\
+\xf0\x95\xc2\x03\x05\xe0\x6e\xae\xef\x0b\xc1\x5b\x0a\x21\x09\xe0\
+\xc3\xb7\xcb\xcb\xb0\x04\xa5\xf1\x71\x4b\x5e\x12\x4b\xc4\x69\x71\
+\xf1\xee\xdf\x05\x4c\x26\x0b\x2c\x63\x13\x41\x29\x78\x85\x7a\x19\
+\xa2\xa3\x45\x70\x3a\x1d\xb0\x5a\xcc\x70\x38\xe9\x86\xdb\x36\xba\
+\x21\x24\x62\xc0\xdf\xde\x29\x67\x8a\x9d\xa0\x2c\x9d\xe3\x43\xff\
+\x03\xb1\x97\x34\x6c\x57\x90\x0c\xa2\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x12\xe7\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x96\x00\x00\x00\x8a\x08\x06\x00\x00\x00\x48\x15\xb3\x02\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x80\x44\x00\x00\
+\x80\x44\x01\x83\x9a\x25\xad\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe0\x05\x1d\x11\x16\x15\xdc\xa2\x29\x6b\x00\x00\x12\x74\x49\x44\
+\x41\x54\x78\xda\xed\x9d\x7b\x78\x14\x55\x9a\xc6\x7f\x55\x7d\x49\
+\x77\x42\x3a\xdd\x49\x77\xe7\x46\x20\x81\x18\x92\x60\x10\x81\x15\
+\x50\x06\x07\x11\x10\x2f\xb8\xc8\x23\xca\x78\x41\x01\x95\x95\x01\
+\x07\x2f\x28\x03\x2e\x88\x22\x8c\x8e\xba\x08\xe8\x30\x03\x84\x9b\
+\xb8\x8e\x3c\xae\x8e\x3a\xba\xae\x77\x19\x47\x19\x47\xd7\x1b\x04\
+\x11\x2f\x2c\x88\xa0\x28\xc8\x2d\x97\x4e\xd7\xfe\xd1\x41\x23\xa4\
+\x4f\xf5\xa5\xba\xbb\xaa\xbb\xde\xe7\xe9\x3f\xe0\x9c\x3e\x55\xf9\
+\xfa\xad\xef\x7c\xf5\x9e\xef\x7c\x07\x4c\x88\x60\x07\xae\x01\xfe\
+\x07\xf8\x12\x68\x01\x0e\x00\x5b\x80\x65\xc0\x60\xd3\x44\x26\xa2\
+\xc5\x2d\x80\x12\xc1\x67\x2b\x50\x67\x9a\xcb\x84\x1a\x64\xe0\xa3\
+\x08\x49\xd5\xfe\x33\xd9\x34\x9d\x89\x70\xc8\x02\xbe\x8a\x81\x54\
+\xc7\x3e\xb3\x4d\x13\x9a\xe8\x08\x2b\xe3\x20\xd5\xb1\x4f\xb9\x69\
+\x46\x13\xed\xd1\x45\x03\x52\x29\xc0\x46\xd3\x94\x26\xda\xe3\x6f\
+\x1a\x11\x4b\x01\xce\x34\xcd\x69\x02\x60\xa8\x1a\x59\x26\x5e\xe4\
+\x56\x5e\x5a\xd1\x55\x59\xb7\xb0\x54\xe9\x5e\x66\x57\x23\xd6\x97\
+\xa6\x49\x4d\xa0\x16\xb0\x2f\x9e\x55\xa4\x28\x0d\x3d\x15\xe5\xa3\
+\xda\xd0\xe7\xf3\x93\x95\x62\x9f\x55\x8d\x5c\x57\x65\xb2\x41\x25\
+\x93\x53\x4c\x01\x96\x84\x6b\x2c\xf2\x5a\xd9\xbd\xa9\x1a\x8e\x04\
+\x7f\xf6\xff\x2f\xfe\xfd\x30\xc3\xae\x11\x3a\xa6\x46\xc0\x99\xa9\
+\x46\xb5\x98\xbc\xe2\x6d\xd1\x03\xf6\x97\x25\x65\x54\xf8\x6d\x27\
+\xfc\x7f\xb7\x2a\x07\xcf\xbd\x72\x90\x5d\x7b\x03\xe1\xbe\x6a\x25\
+\xa4\x89\xbd\x9a\x89\x46\x95\x33\x9c\x54\x8b\x45\xa4\x1a\xd4\x27\
+\x9b\x21\x83\x73\x3b\x6e\x3c\x1a\x64\xed\xc2\x52\xb5\xf1\xff\x1d\
+\x70\x98\x1e\x2b\xb3\xe0\x07\x1e\x17\x75\x78\x7d\x75\x39\x6e\x67\
+\xf8\x67\xaf\xc0\x67\x65\xeb\xf6\x26\x3e\xfa\xb4\x49\x34\x4c\x57\
+\xe0\xbf\x4c\x8f\x95\x39\x58\x27\x6a\x9c\x34\xc6\x43\xd7\x2e\x76\
+\xf1\x08\x2d\x0a\xcb\xe6\x96\xa8\x5d\xe7\x8a\x36\x72\x99\x1e\x2b\
+\x03\x30\x10\xb8\x5b\xd4\xe1\xad\x47\x2b\x22\x32\x4e\x96\x43\xa2\
+\xa9\x49\x61\xe3\xbb\x47\x44\xdd\xfa\x01\xf5\xa6\xc7\x4a\x7f\x3c\
+\x22\x6a\xbc\xf7\xe6\x42\x6c\xb6\x08\x5f\x98\x83\xb0\xe0\xa6\x42\
+\xac\x16\x61\xff\x41\x64\x58\x8a\x4d\x26\x12\xeb\x6a\xa0\x22\x5c\
+\xa3\x3b\xd7\xc2\xcd\xff\xe6\x0b\x29\x51\x91\xa2\x55\x61\xe9\xec\
+\x22\xb5\x5e\xab\x4d\x62\xa5\x37\x1e\x16\x35\xae\x5d\x58\x02\x47\
+\x83\x51\x0f\x7a\xed\xf8\x02\x0a\x0b\xac\xa2\x2e\xe5\xc0\x78\x93\
+\x58\xe9\x89\x85\x84\x52\x63\x3a\xc4\xa9\x35\x0e\xce\x1f\x99\x17\
+\xdb\xc8\x47\x22\x92\x1f\x1e\x36\x83\xf7\xf4\x83\x0b\x78\x46\xd4\
+\xe1\x95\xfa\xae\x78\x3b\xc5\x6e\x92\xee\x55\x0e\xfe\xfa\xf2\x41\
+\xbe\x0a\x2f\x9a\xda\x08\xe9\x66\xaf\x9a\x1e\x2b\x7d\x20\x8c\x71\
+\xc6\x9d\x97\x47\x75\x55\x9c\x5a\x66\x64\xa2\xe9\x1c\x91\xd7\x34\
+\x3d\x96\xb1\xd0\x0b\x58\x24\x94\x17\xd6\x57\x60\x97\xe3\x5f\x3a\
+\xf5\xfa\xac\x34\x7c\xda\xc4\xc7\x62\xd1\xb4\x0c\x78\xd2\xf4\x58\
+\xc6\xc7\x7a\x51\xe3\x1d\x53\x7c\xe4\x64\x6b\x64\x8a\x16\x85\x3f\
+\xaa\x8b\xa6\xe3\xdb\xc8\x65\x7a\x2c\x03\x63\x2c\x70\x7d\xb8\x46\
+\x67\x96\xc4\x0b\x6b\xca\x21\xa0\xdd\x05\xb3\x1c\x12\x4d\x8d\x0a\
+\x1b\xdf\x13\x8a\xa6\x7d\x81\x55\xe9\x6a\xf4\x4c\x48\x9b\xf9\x01\
+\xc8\x0d\xd7\xf8\x9f\xbf\xef\xcc\x25\xe7\xb8\xb4\xbf\xaa\x55\xc2\
+\x52\xfb\x31\x41\xb1\x72\x71\x26\xf0\xba\x39\x15\x1a\x0f\xb7\x8b\
+\x48\x55\x55\x6e\xe7\x92\x31\xee\xc4\x5c\x39\xa0\xf0\xd0\xec\x62\
+\xb5\x5e\xa6\xc7\x32\x20\xec\x80\x30\x82\xfe\xe7\x9f\xbb\xd1\xa7\
+\x36\x81\x59\x2d\x39\x32\x85\x7d\xb7\xb0\xf7\xbb\x56\x51\xaf\xab\
+\x48\x43\x55\x3e\x9d\x3d\xd6\x4a\x51\xe3\x85\x43\x72\xe9\xd3\x3b\
+\xc1\x09\x9e\x87\x33\x57\x34\x4d\xd7\xe0\xbd\x3b\xb0\x5c\xd4\xe1\
+\xcd\x47\x2a\x70\xda\x12\xef\xb0\x33\x55\x34\x4d\x57\x8f\xf5\xa8\
+\xa8\x71\xc6\x84\x02\xf2\x3d\x49\x7a\xa6\x8e\x06\x59\x7d\xb7\xaa\
+\xfc\x30\xa7\x6d\xea\x36\x3d\x96\x8e\x71\x1e\xa1\x82\x1e\x1d\x3f\
+\x49\x32\xbc\xf1\x68\x37\x68\x4d\xde\x0d\xf9\xfc\x36\xb6\x44\x26\
+\x9a\x3e\x65\x06\xef\xfa\xc5\x5e\xc0\x17\xae\x71\xc5\xbc\x12\x26\
+\x5c\xe4\x4e\xfa\x4d\x1d\x38\x14\xc4\x3d\xa0\x41\xad\x5b\x19\xb0\
+\xd3\x9c\x0a\xf5\x87\x1b\x45\xa4\xea\x5c\x64\x63\xc2\x15\xf9\x29\
+\xb9\xb1\x3c\x97\xcc\xad\x13\x0b\xd4\xba\xad\x35\x3d\x96\x3e\xff\
+\x16\xa1\x1c\xf9\xc6\x9a\x72\x06\xf5\xc9\x4e\xdd\x1d\x5a\x25\xe4\
+\xea\x8f\xd5\x72\x08\x07\x03\x6f\x98\x1e\x4b\x3f\xf8\x83\xa8\xf1\
+\xec\x01\x39\x0c\x1a\x98\x93\xda\x3b\x0c\x28\x3c\x74\x7b\x66\x88\
+\xa6\xe9\xe2\xb1\x8a\x09\x6d\x93\x0f\x8b\x9d\x2f\x57\x51\xea\xb7\
+\xa6\xfe\x4e\x73\x64\xfc\x7d\x1a\xf8\xe6\x7b\xe1\xe2\xe4\x78\x60\
+\x8d\xe9\xb1\x52\x0f\x61\xf6\xc2\x94\x71\xf9\x94\x16\x5b\xf5\x71\
+\xa7\x87\x83\xac\x59\xa0\x2a\x3f\xfc\xc1\x94\x1b\x52\x8f\x21\x84\
+\x74\xa0\xb0\xd8\xf4\x58\x37\x95\xe8\x2b\xb9\xa8\xac\x72\xf0\xac\
+\xba\x68\x0a\xf0\x9a\xe9\xb1\x52\x07\x61\x4c\xf2\xe0\xcc\x22\x50\
+\x94\xf8\xad\x24\xc3\xd1\x26\x85\x03\x87\x82\x34\x36\x2b\x60\x91\
+\x62\xb7\x5e\x63\x90\xd5\xf3\x55\xbd\xd6\x5c\x42\xf5\x1f\x4c\x62\
+\xa5\x00\x93\x09\x55\xe2\xeb\x10\x05\x6e\x0b\x53\xaf\xf5\xc6\x1c\
+\x7d\xb6\x06\x61\xcb\x67\xcd\x8c\xbf\xed\x2b\xa4\xda\xcd\x64\xf7\
+\xdd\x82\x7b\x40\x03\xce\x3e\x5b\xb0\xd5\x6d\x66\xc6\xbd\x7b\xd8\
+\xb5\x27\x10\x53\xa4\x5a\x5b\xed\x60\xec\x08\xd5\x74\x9d\x3f\x9a\
+\xc1\x7b\x6a\x20\x74\x45\xcf\x2f\xeb\xc2\x88\x33\x3a\x45\x3d\x68\
+\x6b\xab\xc2\x13\x2f\x1e\x64\xec\x4d\x91\x69\x95\x05\x6e\x0b\x7f\
+\x5b\x57\x41\x8f\x6e\xf6\xa8\xa6\xdc\x08\x45\xd3\xce\xc0\x2e\xd3\
+\x63\x25\x0f\x0f\x88\x1a\x07\xf6\x76\x32\x62\xa8\x2b\xea\xc7\x6c\
+\xe7\x9e\x16\x3a\x9d\xd6\x10\x31\xa9\x00\xf6\xed\x6f\xa5\xfa\xfc\
+\x4f\xf9\xd5\xcd\xbb\x08\x46\x31\xeb\xe6\xb9\x64\x66\x4c\x48\x4f\
+\xd1\xd4\xa8\x1e\xcb\x0d\x7c\x2f\xea\xb0\xed\xb9\x4a\x2a\xcb\xec\
+\x51\x59\xe2\xc5\x37\x55\x8b\xa9\xa9\xa2\xaa\x3c\x8b\xad\x7f\xed\
+\x1e\xb9\xe7\xb2\x49\x48\x3d\x3e\x56\xeb\xf5\x0b\x0c\x56\x34\xd7\
+\xa8\x1e\x4b\x58\x7b\xe1\xea\xd1\x6e\x2a\xcb\xa3\xdb\x61\xb5\xf9\
+\xd3\xa6\xb8\x49\x05\xf0\xc9\x17\x4d\x9c\x77\xdd\x0e\x88\x34\x25\
+\xa7\x25\x22\xd1\xd4\x70\x05\x45\x8c\xe8\xb1\x4e\x23\x54\x85\x2f\
+\xfc\x6f\xf5\x7e\x8d\x5a\x91\x8e\x9f\xc3\x21\xd3\xe3\xec\x6d\x7c\
+\xf2\x45\x93\x66\x37\xf9\xee\xe3\xdd\x38\xb5\x26\xc2\xec\xd4\x4e\
+\x32\xbe\xde\x0d\x7c\xbb\x5f\x28\x9a\x5e\x69\xa4\x69\xd1\x88\x1e\
+\x4b\xa8\x48\x2f\x98\xee\xc7\x6a\x8d\xee\x79\xf9\x6e\x4f\x8b\xa6\
+\xa4\x02\xd8\xf0\xc2\x0f\x91\x77\x3e\x14\x91\x68\x6a\xa8\x37\x44\
+\xa3\x11\xeb\x0a\xa0\x47\xb8\xc6\x9c\x6c\x99\xdb\xa6\xfa\xa3\xab\
+\x14\x03\x04\x02\x8a\xe6\x37\xda\x12\x50\xa2\x9a\x0f\x46\x0e\x73\
+\xd1\xaf\xa7\xd0\xc3\x39\x08\x6d\x0e\x31\x89\x95\x00\x08\xd3\x8d\
+\xd7\x2e\x28\x3d\xa1\xba\x71\x24\xf0\xfb\x6c\x9a\xdf\xa8\xcf\x63\
+\x89\x8e\xe0\x8d\x41\x56\xcd\x57\xcd\x8f\x9f\x87\x41\x56\x4b\x8c\
+\x44\xac\xbb\x10\xa4\xef\x9e\xd2\xc3\xc1\xe8\xf3\x63\xac\x14\x93\
+\xad\x7d\xa8\xe9\xcd\x8f\x5e\x34\xef\x59\xed\xe0\x62\x75\xd1\xf4\
+\x4f\x26\xb1\xb4\x83\x13\x98\x25\xf4\x56\x0b\x4b\x63\xaa\x6b\x05\
+\x40\x10\xf2\xdd\xda\x3a\x02\x9f\x27\x86\xd5\x98\x80\xc2\xf2\x3b\
+\x54\x63\xad\xab\x81\x52\x93\x58\xda\x40\xb8\xef\x6e\xec\x39\x2e\
+\xea\x6a\xe2\xd8\x1f\x18\x84\x62\xaf\x55\x63\x62\xc5\x46\x54\x97\
+\x4b\xe6\x96\xab\x55\x45\xd3\x35\x26\xb1\xe2\x47\x4f\xe0\x62\x51\
+\x87\xfa\x3b\x4b\x21\x9e\x00\x3c\xa8\x50\xa4\x35\xb1\xf2\x2d\x31\
+\x93\xfc\x9e\xdb\x54\xcb\x4e\x9e\x45\xa8\xae\xa9\x49\xac\x38\x20\
+\x2c\x9b\x7d\xfb\x64\x1f\xd9\xf1\xc6\x48\xad\x68\x4e\x2c\xaf\x27\
+\x8e\xf1\x5a\x14\x96\xce\x36\xb6\x68\xaa\x77\x62\x8d\x01\x7a\x87\
+\x6b\xb4\x5a\x25\xe6\xdd\x52\x18\xb5\xbc\x90\x0c\x8f\xe5\x8a\x73\
+\xdf\xe2\xf5\x13\x0b\x28\x70\x0b\xef\xa9\x12\xb8\xdc\x24\x56\x6c\
+\xa8\x17\x4f\x81\x25\xb1\x07\xec\xed\xa1\x68\xef\xb1\xb0\xc7\xe9\
+\x45\x0f\x47\xb4\xd1\x75\xb9\x49\xac\xe8\x31\x13\x41\xa5\x98\x93\
+\xba\xd8\xb9\xfc\x12\x8f\x66\x17\xd3\x32\x78\xf7\xb8\x2c\x9a\x64\
+\xac\x9e\x37\xdc\x45\xbf\x9e\xc2\xfa\x12\x59\xe8\xf4\x1c\x6a\x59\
+\xc7\x84\xbf\x5b\x55\x5e\x38\xa2\x5d\xbe\xb1\x96\x1e\xcb\xeb\xd1\
+\x86\x58\x34\x06\xa9\xbf\x4b\xd5\x6b\xdd\xa9\xc7\xdf\x51\xaf\xc4\
+\x5a\x21\x6a\x3c\xff\xcc\x5c\xfa\xf7\xd5\x76\x7f\xa0\x96\xc4\xf2\
+\x79\x2c\x44\x95\x98\x25\xc0\xc9\x35\x0e\x2e\x1e\x6e\x3c\xd1\x54\
+\x8f\xc4\xaa\x40\xe5\x74\xd2\xd5\x77\x97\x40\x8b\xa2\x5f\x62\xe5\
+\x5b\xb5\xdb\xbc\x11\x50\x58\x3e\x4f\xd5\x6b\x4d\x40\x67\xa2\xa9\
+\x1e\x89\x25\x94\x17\x6e\x1c\x9f\x98\x4a\x31\x05\x9a\x7a\x2c\x6b\
+\xfc\x1b\x38\xda\xbf\x61\xba\x64\x6e\x56\x17\x4d\x57\x99\xc4\x0a\
+\x8f\x91\xc0\xe9\xa2\x0e\xf7\xcd\x2a\x4a\xcc\x56\x2e\x87\x76\xeb\
+\x85\x9a\xc5\x58\x3f\xca\x21\x70\xef\x4c\x55\xd1\xf4\x6c\x74\x24\
+\x9a\xea\x8d\x58\xc2\xa5\x9b\x65\x73\x8a\xa1\x59\x49\xcc\x95\x83\
+\xe0\xcf\xd7\xc6\x6b\x45\x9d\xd9\x10\x09\x9a\x15\x96\xcc\x32\x8e\
+\x68\xaa\x27\x62\xdd\x80\xa0\x52\x4c\xa9\xdf\xc6\xb5\xe3\x0b\x12\
+\x77\x75\x0d\x45\x52\x5f\x7e\x62\xb6\x03\x4e\x99\x54\x40\x81\x78\
+\xb1\x5c\x37\xa2\xa9\x9e\x88\xf5\x1f\xc9\x94\x17\x4e\x40\x2b\x14\
+\xf9\xb4\x21\x84\x37\x51\xd5\x02\x0f\x07\x59\x35\xdf\x18\xa2\xa9\
+\x5e\x88\xb5\x54\xd4\x78\x56\xff\x1c\x86\x0c\x4a\x70\xa5\x98\x20\
+\x14\x15\x68\x43\x08\x5f\x02\xcb\x50\x9e\x3f\x5c\x35\xd3\x34\x0b\
+\x95\x14\xa3\x4c\x21\x56\x11\x82\x93\x23\x42\xf2\x42\x69\xe2\x62\
+\xab\x76\x53\xa1\x56\xea\xbb\xcf\x93\xc0\x9d\xf1\x4d\x0a\xf5\x77\
+\xa9\x2a\x0b\x77\xa5\xfa\xb7\xd5\x03\xb1\x84\x3b\x4f\x26\x8f\xf5\
+\xd0\x39\x19\x95\x62\x14\x28\xd2\x28\x45\xd9\x97\xe0\xc2\xb9\x27\
+\xd7\x38\x18\x33\x4c\x55\x34\x5d\x96\xc9\xc4\x1a\xdc\xf6\x9a\x1c\
+\x16\x0f\xcf\x2d\x4e\x5a\xa5\x18\xad\x82\xf7\xec\xfc\x04\x3f\x08\
+\x01\x25\x92\xa5\x9e\x49\xa4\x50\x34\x4d\x35\xb1\x84\x99\x90\xf7\
+\xcf\x28\x4c\x6a\xf9\x21\xcd\xd4\xf7\x24\x6c\x77\xc8\xcd\x95\xb9\
+\xe9\x2a\xfd\x8a\xa6\xa9\x24\xd6\x24\xa0\x6b\xb8\xc6\xfc\x3c\x0b\
+\xd3\x27\xfb\x92\x1b\xec\x69\x40\x2c\xaf\xc7\x9a\x9c\x87\x21\x08\
+\xbf\x8f\x4c\x34\x3d\x23\xd3\x88\x25\x5c\x38\x5d\xb3\x20\xc1\xf2\
+\x42\x82\x88\xe5\xd3\x5a\x75\x17\xa1\x45\x61\xf1\x6f\x55\xc9\xb5\
+\x2a\x93\x88\x75\x8f\xa8\xb1\x7f\xaf\x6c\xce\x1b\x96\x9b\xf4\x9b\
+\x72\x7b\x2d\x1a\x11\x4b\x49\xda\x3d\xff\x7a\x92\x17\xaf\xba\x68\
+\x7a\x59\x26\x10\x2b\x0f\xc1\xc9\x11\x00\x6b\x17\x96\x40\x93\x92\
+\xfc\x3b\xb3\x49\x48\x71\x2e\x19\x26\x6d\x2a\x3c\x86\x23\x41\x56\
+\xaa\xcb\x0f\x2b\x32\x81\x58\xc2\xf5\xc0\x2b\x46\xe5\x71\x52\x45\
+\x8a\xce\xe2\xd6\x60\x53\x45\xb2\x3d\x16\xc0\x05\x23\x72\xe9\x5b\
+\xab\x2f\xd1\x34\xd9\xc4\xea\x07\x5c\x28\xea\x50\x7f\x67\x09\xb4\
+\x2a\xa9\x21\x96\x06\xeb\x85\xbe\x7c\x4b\xf2\x0b\xe9\x36\x29\xd4\
+\xcf\x8f\x48\x34\x95\xd2\x95\x58\x42\x31\xf4\xae\xa9\x7e\x2c\xd6\
+\x14\x56\x56\x0a\xc6\xef\xb1\xbc\x9e\xd4\xd4\xa3\xad\xd3\x99\x68\
+\x9a\x4c\x62\xfd\x0a\xa8\x0e\xd7\x98\xed\x90\x98\xf5\x1b\xbf\xf6\
+\xe9\x26\x51\x12\xab\x58\x0b\x8f\x95\x0a\x04\x14\x56\xaa\x8b\xa6\
+\xd7\x10\xaa\x69\x9a\x56\xc4\x12\xbe\xf6\xd6\xcf\x2f\xd5\x66\x2b\
+\x57\x5c\x31\x96\x06\x53\xa1\x3b\x75\xc5\x60\x5c\xb9\x32\x37\xaa\
+\xa7\x16\xad\x4c\x27\x62\xcd\xe5\xa7\xa2\xf8\x27\xa0\x57\x95\x83\
+\xb1\xa3\xf2\xd0\x03\xe2\x8f\xb1\x52\x58\x9a\x3d\x08\xf7\xa9\xeb\
+\x5a\xc3\x48\x82\x68\x9a\x0c\x62\x39\x50\x39\x39\x62\xcd\x82\x14\
+\xc9\x0b\x1d\x11\xcb\x17\x6f\x8c\x95\xe2\xf2\x55\x2d\x4a\xe8\xd0\
+\x84\x38\x66\x0f\xa3\x10\x4b\xe8\x7a\xc7\x0c\x73\x71\x4a\x4f\x27\
+\x7a\x41\x71\x81\x81\x3d\x56\x1b\xa6\x4e\xf4\xaa\x11\xbc\xb2\x2d\
+\xe6\x35\x2c\xb1\x6a\x81\x71\xaa\xde\x2a\xa0\xe8\x86\x58\xf1\x7a\
+\xac\x2c\xb7\x0e\x4e\x29\x69\x0c\xb2\xf2\xce\x92\xb8\x1e\x78\xbd\
+\x13\x4b\x28\x2f\xcc\x9c\xe4\x25\xdb\xa9\xaf\xfd\x1c\xf1\xc4\x58\
+\x16\x59\x02\x49\x1f\x0f\xc9\x05\x23\x5c\xf4\xad\x55\xdd\x9e\xff\
+\xdb\x44\x5d\x3f\x91\xa2\xd1\x85\xc0\x93\x61\x7f\x04\x8b\x44\xa0\
+\xa1\x56\x37\xb1\xd5\x4f\xba\x87\x8c\xd4\xed\xa3\x98\xbe\x5a\xe8\
+\xb5\xf2\xf5\xa6\xea\xa4\x2f\x9e\x87\xc3\x87\xdb\x1a\xe9\x35\xfa\
+\xb3\x48\x9c\x8b\xe6\x3f\x42\x22\xdd\x85\xd0\x5b\x2d\xbf\xa3\x58\
+\x7f\xa4\x6a\x7b\xd4\xac\x31\x8a\xb4\x49\xcd\x6c\x88\x00\x75\x35\
+\x4e\x46\x0f\x55\x5d\xcc\x4f\xc8\xd9\x88\x89\x22\xd6\x0c\x04\x95\
+\x62\x2a\xbb\xd8\xb9\x4a\xc3\x4a\x31\x5a\xbf\xb2\x17\xc5\x98\xe5\
+\xe0\xf3\x58\x93\xbe\x4e\x28\x44\x40\x09\xed\x17\x10\xe3\x5a\x12\
+\x20\x9a\xca\x89\x79\xe6\xf9\x9d\x51\xe4\x85\x13\x89\xa5\x50\xec\
+\xb5\xc5\x48\x2c\x7d\x79\x2c\x08\x65\x9a\x4e\xbf\x32\xf9\xa2\x69\
+\x22\x88\x25\x3c\x41\xe1\x9c\x41\x9d\x18\xd8\x2f\x07\xdd\xa2\x15\
+\x4a\x62\x3c\x3b\xba\xd8\xa7\x33\x8f\xd5\xe6\x81\xef\x8f\x4c\x34\
+\x3d\x5d\xcf\xc4\xaa\x20\x94\x72\x1c\x16\x8f\x2c\x2c\xd5\xbc\x52\
+\x8c\xb6\xc4\x52\xa8\xec\x62\x8f\xe9\xab\xdd\xcb\xec\xba\xf3\x58\
+\xc7\xa6\xc4\x45\xea\xa2\xe9\x6a\x3d\x13\x4b\xb8\x39\x62\xda\xe5\
+\xf9\xe4\xe7\xeb\xfc\x60\x05\x85\x48\xea\x51\x75\x88\x4b\x46\xe6\
+\xe9\xf6\xcf\x9a\x36\x21\x22\xd1\x74\x9c\x1e\x89\x35\x0c\x95\x6a\
+\x27\x8b\x66\x15\xeb\xf3\x89\x3e\x0e\xfd\xfb\x65\xab\xed\x36\x3e\
+\x01\xa3\x87\xe6\xe2\xf7\xeb\xf8\x08\xe7\xa6\x20\x2b\xe6\x25\x4f\
+\x34\xd5\x92\x58\xc2\x33\x04\x97\xce\x2e\x4a\x5d\x02\x5f\xb4\x68\
+\x56\x78\xb9\xbe\x3c\xe2\xee\x59\x76\x89\x27\x16\x95\xe9\x6a\x05\
+\xa1\x23\x8c\x3a\xc7\x45\x9f\x1a\xd5\x83\xa0\x66\x6a\xf5\x06\xa7\
+\x05\xa6\x00\x4b\xc2\x35\x96\x16\xda\xd8\xb9\xb1\x4a\xbf\x6f\x82\
+\x61\xf0\xed\xf7\xad\xf8\x07\x6f\x15\xd6\x50\xf3\xe4\x59\xf8\xea\
+\xe5\x2a\x1c\x59\xc6\x38\xfa\xf1\xc3\x6d\x4d\xf4\x1a\xbd\x5d\xad\
+\x9b\x85\x38\xe7\x16\xad\x3c\xd6\x12\x61\x54\x38\xbf\xc4\x70\xa4\
+\x82\x50\xa6\x42\xf3\x7b\x35\xac\xb8\xb3\x04\x59\xfe\x39\x71\x72\
+\x9c\x32\x4f\x2c\x2a\xe3\xdb\x37\x7a\x18\x86\x54\x00\x75\xb5\x8e\
+\x48\x44\xd3\x87\xf5\xe0\xb1\x16\x01\xd3\xc2\x35\x0e\xee\x97\xcd\
+\x6b\xeb\x2b\x12\x5f\xd4\x23\x91\x68\xb3\xd2\xa1\xc3\x41\x5a\x02\
+\x0a\x76\x9b\x44\x4e\x8e\x1c\x5a\x08\x31\xe0\x9f\xf5\xc3\xa1\x20\
+\x79\x03\x1a\xd4\xba\x95\x01\x3b\x63\xbd\x46\xbc\xaf\x68\x45\xc0\
+\xe3\xa2\x0e\x1b\xd7\x56\xe0\x72\xca\xa4\x03\xec\x36\x09\x67\x96\
+\x8c\xdd\x26\x19\x92\x50\x3f\xc6\x84\x0e\x89\x03\x07\x5b\x79\xeb\
+\xfd\xa3\xa2\x6e\x27\xa3\x52\x0f\x36\x91\x53\xe1\x2a\x51\xe3\xa4\
+\x31\x1e\x3a\x97\xd8\x30\xa1\x33\x04\xe1\x81\x99\xaa\x65\x27\x87\
+\x03\x03\x52\xe1\xb1\x4e\x47\xa5\xc8\xff\x3b\x7f\xee\xa6\x97\x2c\
+\x12\x13\x1d\xbd\x78\xb8\x2c\x3c\xbf\xf1\x90\xa8\xcb\x2f\x81\xc5\
+\xc9\xf6\x58\x8f\x8a\x1a\xef\xb9\xa9\x10\x49\x4e\xb3\x5f\x42\x02\
+\x2c\x52\x12\x77\xe7\x25\x10\x0a\xdc\x30\xb1\x40\xad\x96\x57\x25\
+\x70\x69\x32\x83\xf7\xab\x10\x54\xe8\x2d\x70\x5b\xf8\x76\x53\xb5\
+\xb1\x03\xf6\x63\x70\xca\x7c\xba\xb5\x91\xd9\x0f\xee\xe5\xc3\x6d\
+\x4d\x34\x36\x29\x38\xec\x12\xb5\x95\x59\xcc\x9f\xe6\xa7\xaa\xc6\
+\x91\xfa\xdd\x45\x71\xe0\xa9\x57\x0e\xf2\xaf\x53\xff\x4f\xd4\xe5\
+\x28\x90\x9d\x2c\x62\x05\x44\xd3\xe8\x53\x4b\xca\x18\xf5\xcb\x5c\
+\x63\x13\x2a\x4b\x62\xdd\x86\xfd\xfc\x7a\xfe\x6e\x0e\x1c\x0a\x4f\
+\x1c\x57\x8e\xcc\x03\xb7\x15\x31\xe1\x52\x8f\x21\x25\x15\xb2\x24\
+\x4e\xbd\x60\x3b\xff\xdb\xd0\x28\xea\x35\x13\x58\x98\x68\x62\x2d\
+\x00\x6e\x0b\xd7\xd8\xaf\xa7\x83\x7f\x3c\xd9\xdd\x98\x46\x3e\x06\
+\x87\x44\xdf\x0b\xb6\xf3\xee\x96\xc6\x88\xbf\x52\x57\xe5\xe0\x83\
+\x67\x8d\xf9\x77\x47\x28\x9a\x5a\x81\xd6\x44\x11\xcb\x0d\x7c\x2f\
+\xea\xb0\xf5\xd9\x4a\xaa\xba\xda\x0d\x4c\x2a\x99\xe2\x01\x0d\x7c\
+\xfd\x6d\x20\xea\xaf\x1a\x36\x04\xb0\x49\x5c\x38\x79\x07\x7f\x79\
+\xe5\xa0\xa8\xd7\x32\x60\x72\xa2\x82\x77\x61\x39\x9c\x71\xe7\xe6\
+\x51\xd5\x3d\xcb\xb8\xa4\xca\x92\x18\x71\xe5\x17\x31\x91\x0a\x60\
+\xdf\xfe\x56\x06\x8f\xfb\x1c\x1c\x06\x7b\x6b\x69\x51\x58\xa3\x7e\
+\xe8\xe6\x75\x44\x51\xd3\x34\x1a\xb9\xa1\x37\x21\x95\x3d\xbc\xbc\
+\xf0\x58\x37\xac\x06\x7e\x63\x7a\xe9\xcd\xc3\xcc\x59\xfa\x4d\x5c\
+\x63\xec\xd8\xdd\xc2\x69\xd5\x0e\x4e\x32\x98\xd7\x76\x38\x65\xf6\
+\x1f\x08\xf2\xf6\x07\xda\x88\xa6\xd1\xd0\x60\x33\x50\x13\xae\x71\
+\xee\xf5\x3e\xe6\x4c\xf1\x19\x57\x91\x76\xc8\x54\x0f\xdb\xc6\xd6\
+\xcf\x9b\xe2\x1e\xaa\xbc\xd4\xc6\xe7\xaf\xf7\x30\xde\xdb\xa2\x0c\
+\x52\xed\x66\xb5\x5e\x03\x80\xb7\xb5\x9a\x0a\x2f\x16\x91\x2a\xc7\
+\x29\x33\xe7\x06\xbf\xa1\x97\x39\xbe\xd9\xdd\xa2\x09\xa9\x00\xbe\
+\xd8\xd5\xc2\xae\x2f\x9b\x8d\x67\x04\x05\x1e\xb8\x55\x35\xd3\x74\
+\x5d\x84\x1c\x8d\x08\xab\x44\x8d\x7f\xba\xa3\x58\xdf\xe9\xc6\x11\
+\xe0\x99\x57\x0f\x6a\x3a\xde\xd3\x1a\x8f\x97\x2c\x62\xfd\x66\x62\
+\x41\x24\x99\xa6\x63\xb5\x20\xd6\x6c\x04\x02\x59\x6d\xf7\x2c\xc6\
+\x8d\x76\x63\x74\xec\xd8\xdd\xa2\xfd\x78\x46\x8c\x37\x9b\x15\x96\
+\xdf\xa1\x1a\xc8\xd7\xc7\x4b\x2c\x07\xa1\xc3\xac\xc3\xe2\x91\xdf\
+\x95\x1a\x5b\xb3\x6a\xb3\xc2\xbe\xfd\xad\x9a\x0e\xb9\x6f\x7f\xab\
+\x61\x97\x7e\x2e\x3c\x37\x8f\x53\x7a\x08\x33\x4d\xb3\x81\x5b\xe3\
+\x21\x96\xb0\x16\xfb\xa8\x21\xb9\xf4\xae\x73\x62\x78\x04\x63\xdf\
+\xf2\x15\x0e\x25\x7e\xab\x21\xf2\xfb\x3b\x44\x63\x90\xb5\x0b\x54\
+\x95\x85\x85\x22\x55\x41\x44\xac\x1e\xa8\x1c\xaa\xb8\x76\x41\xa9\
+\xe1\x63\xab\x63\xd0\x5a\x1e\x38\xa9\x8b\xdd\xd0\xf6\xa8\xeb\xe9\
+\x88\x64\x59\x6e\x49\x2c\x72\xc3\x3f\x08\x55\x39\xee\x10\xb7\x5c\
+\xed\xe5\x9e\x5b\xfc\xc6\x7d\x2a\x3b\xb0\x84\xd4\x73\xb3\x66\xc3\
+\xb5\xbc\x5f\x8b\xd5\x62\x6c\x93\x7c\xff\x43\x2b\xf9\xa7\x6f\x55\
+\xeb\xd6\x19\xd8\x15\xa9\xc7\x3a\x57\x44\x2a\x9b\x55\xe2\x9e\x24\
+\x1f\xa0\x94\x70\x58\x25\x46\xfe\xa2\x93\x26\x43\x9d\xd5\x3f\x07\
+\xab\xd3\xf8\xb9\x35\x1e\x8f\x95\x69\x97\xe5\xab\x75\x5b\x11\x8d\
+\xc7\xfa\x0e\x08\x5b\xb5\x63\xf9\xbc\x12\x26\x5e\xe4\x26\xdd\xb0\
+\xfb\x9b\x00\x25\x43\x3e\x89\x7b\x9c\xcf\xfe\xfb\x24\x2a\x4a\xd3\
+\x27\x73\x56\x3a\x59\xd5\x93\xf7\x07\x36\xa9\x79\xac\xe9\x22\x52\
+\x95\x97\xda\x98\x38\xce\x43\x3a\xa2\xb8\xd0\xca\xfc\x69\xfe\xb8\
+\xc6\x98\x7d\x9d\x97\x0a\x83\xc7\x57\xc7\xbb\x9e\xfb\x6f\x2d\x54\
+\xeb\xb5\x4e\xcd\x63\x59\x08\xe5\x5a\x85\xc5\xc6\xb5\x15\x9c\x71\
+\xaa\x93\xb4\x85\x5d\xe2\xb2\xe9\x3b\x59\xff\xec\x81\xa8\xbf\x3a\
+\x66\x98\x8b\x0d\x4b\xcb\xd2\x23\xc1\xf1\x38\x9b\x78\x4f\x6b\x50\
+\x93\x64\xc6\xd2\x6e\x63\xcd\xf1\x1e\x4b\x78\xe8\xf7\xd0\x01\x39\
+\x9c\x31\x20\x9b\xb4\x46\xb3\xc2\x23\xf7\x77\xe6\xfe\x19\x45\x51\
+\x7d\x6d\xe1\xf4\x42\x36\x2c\x49\x43\x52\xb5\xd9\x64\x79\x94\xdb\
+\xf3\xdb\xbf\xb7\x74\x41\x65\x1d\xe8\xad\xf5\x15\xe4\xd8\x65\xd2\
+\x1e\x41\x18\xd8\x27\x9b\xc9\x17\x7b\xd8\xb1\xbb\x85\xcd\xdb\xc3\
+\xaf\x21\x8e\x1e\x9a\xcb\x4b\x2b\xcb\x19\x79\x66\xae\xee\xb7\xd8\
+\xc7\x83\xea\x1a\x27\x4f\x3c\xf7\x03\x7b\xbf\x0b\x3b\xa1\xd9\x81\
+\x66\x60\xe3\xf1\x53\xe1\xf3\xc0\x88\x70\xdf\xba\xfe\x52\x0f\x4b\
+\xe7\x94\x18\xa7\xfe\x82\x86\x6f\x8b\x81\xa6\x20\x9b\x3e\x38\xca\
+\x9e\x7d\xad\xec\xda\x1b\xa0\xc4\x6f\xa1\xb0\xc0\xca\x69\x75\x4e\
+\x6c\x0e\x39\xad\x09\xd5\x1e\x1f\x7c\xd2\xc8\x29\x17\xa9\xd6\x34\
+\xcd\x01\x8e\x1c\x23\x56\x0d\xa1\xb4\x98\xf0\x0f\xf1\x87\xb5\x71\
+\x9f\xe5\x97\x2e\xc1\x2c\x99\xba\xa5\xcd\x2e\x31\xea\x9a\x1d\x3c\
+\xfd\x9a\x70\x81\x7d\x31\x30\xed\xd8\xbc\xf6\x90\xa8\xe7\xa2\x99\
+\x45\xe9\xb7\x95\x2b\x56\x64\xf2\x3e\xc9\x66\x85\x55\xea\x99\xa6\
+\x53\x21\xa4\x0d\x77\x41\x90\x19\xea\xcf\xb7\xb2\xe1\xa1\x2e\x19\
+\xe3\xee\x4d\x88\xe1\xcc\x96\x39\x78\x28\xc8\xdf\xc5\xdb\xf3\xb7\
+\xcb\x84\x8e\x1a\x0b\x8b\x07\x67\x16\x41\x53\xd0\xb4\xa8\x89\x1f\
+\x5f\x6c\xee\x53\x4f\x06\x1c\x23\x13\xaa\xc4\xd7\x21\xca\x8a\x6c\
+\x5c\x92\x86\x0a\xbb\x89\xf8\xe3\x01\x95\x4a\xcc\xdd\x65\xa0\x3c\
+\x5c\xeb\xcc\x6b\xbc\x86\xde\xe5\x6b\x22\x71\x5e\x6b\xf8\xe9\xc2\
+\xca\xd7\x65\x32\x10\x56\xaf\xef\x5f\xe7\x34\x8d\x68\xa2\xe3\xd8\
+\x5b\x7c\x4a\x5a\xae\x8c\x60\x09\xa7\xa9\xd9\x0c\xd8\x4d\x74\x8c\
+\xaf\xf6\x0a\x57\xfe\x0e\xc8\xc0\x9e\x70\xad\xff\xdc\x7c\xd4\xb4\
+\xa0\x89\x13\x61\x93\x58\xf7\xb4\x70\x2d\xf5\x6b\x19\x08\x5b\x6a\
+\xe4\xd6\x07\xf6\x72\xb4\x31\x98\x1e\x65\x7b\x4c\x68\x03\xab\xc4\
+\x0b\xaf\x1d\xe2\xb1\xe7\x85\xc4\x7a\x47\x02\x6e\x07\xe6\x85\x25\
+\xa7\x55\x62\xfa\x95\xf9\x0c\x1b\x98\x83\x27\xcf\x8a\xb0\x84\xb0\
+\x89\xf4\x85\x24\xb1\x77\x5f\x80\xf5\xcf\x1e\x60\xdd\x33\xaa\x99\
+\x1f\xa3\x25\x42\x02\xe9\x97\xa6\xe5\x4c\x68\x84\xa3\x40\xb6\x0c\
+\xec\x00\x9e\x32\xed\x61\x42\x23\x4c\x87\x9f\xa2\xa7\x3c\x60\xbf\
+\x69\x13\x13\x71\xe2\x1d\xe0\x5f\xe0\xa7\x44\xbf\x03\x40\x1f\xd3\
+\x2e\x26\xe2\xc0\x7e\x60\xe0\xb1\x7f\xb4\xcf\x59\x78\x8f\x50\x25\
+\x91\x66\xd3\x46\x26\xa2\xc4\x87\x80\x9f\x76\x9a\xe8\xf1\xc9\x30\
+\x6f\x03\x2e\x54\x0e\x5c\x32\x61\xa2\x1d\x6e\x02\x7a\x01\x3f\x2b\
+\x7e\x21\x52\xa8\xdc\xc0\x44\xe0\x6c\xa0\x27\xe0\x23\x54\xcb\xc1\
+\x44\xe6\xa2\x91\xd0\xe6\xd4\xcd\xc0\x06\x42\xa9\xec\x1d\x2e\x26\
+\xff\x3f\x69\xdc\x05\x93\x70\xb6\x44\x78\x00\x00\x00\x00\x49\x45\
+\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x09\xf5\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\
+\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x09\x95\x49\x44\x41\x54\x78\
+\xda\xed\x9c\x5b\x68\x13\x5b\x17\xc7\xd7\x4c\x2e\x8d\xbd\x58\x53\
+\x6d\x14\x15\x6c\x8f\x15\x7c\xb2\x54\x8f\x3d\x58\x8b\x52\x8e\xf1\
+\x41\xb4\xa2\xa2\x45\xab\x05\x15\xef\x88\x68\x95\xaf\xf8\x20\x14\
+\x7c\xa8\xfa\xa2\xe2\x85\xb6\x2a\x2a\x5a\x2b\x28\x8a\xe2\xa5\x36\
+\x05\x41\xe5\xc0\xe9\x27\x05\x6d\x4b\x5a\x7b\x31\xf5\x56\xb0\x46\
+\x2b\x28\x26\x73\xf9\x7f\x0f\xc7\xd9\xdf\x4c\x9a\xd8\x5b\x66\x9a\
+\xa3\xb3\x60\xa0\x49\xa6\xb3\xb3\x7f\x59\x7b\xaf\xb5\xd7\xfe\xcf\
+\x70\x14\x83\x06\xa0\x9c\x88\x7e\x0b\x7d\x9f\xe3\x38\x77\xac\x7d\
+\x57\x2e\xc6\xc0\x61\x40\x5f\x9a\xe3\x38\x13\xe0\x10\xc0\xc5\x22\
+\x48\x7e\x04\xa1\xfd\x07\xdf\x4d\xfd\xbe\x2c\xcb\x24\xcb\x32\x11\
+\x11\xbd\x7f\xff\x9e\x3e\x7d\xfa\xd4\xe7\x7d\x35\xf8\x70\xd7\xf8\
+\xa9\x0d\x11\x4c\x96\x65\x04\x83\x41\x00\xc0\xb7\x6f\xdf\x50\x54\
+\x54\x84\x94\x94\x14\x38\x9d\x4e\xec\xde\xbd\x9b\x7d\x16\x0c\x06\
+\x21\xcb\x72\xa4\xcb\x94\xff\x72\xf0\xd4\x30\x9a\x9a\x9a\xb0\x61\
+\xc3\x06\x10\x11\x88\x08\x36\x9b\x0d\x36\x9b\x8d\xbd\xde\xba\x75\
+\x2b\xbc\x5e\xaf\xe6\x7f\xc3\xc0\x2c\xff\x25\xe0\x05\x02\x01\xf6\
+\xf7\xd5\xab\x57\x31\x6f\xde\x3c\x06\xca\x62\xb1\x80\xe3\x38\xf6\
+\x9a\xe3\x38\xf0\x3c\xcf\x5e\xcf\x9f\x3f\x1f\x57\xae\x5c\x09\x7b\
+\xad\x9f\x16\x62\x38\xcf\xab\xae\xae\xc6\xd8\xb1\x63\x41\x44\x1a\
+\x40\xfd\x1d\xca\xb9\x89\x89\x89\xb8\x78\xf1\x22\x04\x41\xe8\xe3\
+\x89\x3f\x2d\x40\xc5\x5b\xe2\xe2\xe2\x98\xb7\x59\x2c\x96\x01\xc3\
+\x53\x7b\xa9\xd5\x6a\x05\x11\x21\x25\x25\x05\x4d\x4d\x4d\x23\x02\
+\x90\x1f\x01\x90\x44\x44\x14\x08\x04\x88\xe7\x79\x92\x24\x89\x24\
+\x49\x1a\xf4\x75\x24\x49\x22\x51\x14\xc9\x6a\xb5\xd2\xa7\x4f\x9f\
+\xe8\xdd\xbb\x77\xbf\x56\x1a\xa3\x86\x39\xdc\x6b\xf0\x3c\x4f\x23\
+\x95\x12\xf2\x64\x9a\x09\xd0\x04\x68\x02\x34\x01\x9a\x66\x02\x34\
+\x01\x9a\x00\x4d\x80\xa6\x99\x00\x4d\x80\x26\x40\x13\xa0\x69\x26\
+\x40\x13\xa0\x09\xd0\x04\x68\x9a\x09\xd0\x04\x68\x02\x34\x01\x9a\
+\x66\x02\xd4\xd7\xac\x7a\x37\x30\x52\x2a\x01\xa5\x5d\xbd\x25\x70\
+\xd6\x9f\x05\x5c\x24\x4e\x7a\x83\xe4\xa3\x0c\x6d\x56\x7f\x9a\x3f\
+\x41\x10\xe8\xf2\xe5\xcb\xd1\x6c\x93\x24\x49\x22\x8f\xc7\x43\xbd\
+\xbd\xbd\x7d\xda\x53\x83\xfc\x6e\xce\x98\x9c\x0b\xfa\xd3\xfc\x01\
+\xc0\xbe\x7d\xfb\x30\x6e\xdc\x38\xa6\xb8\xa2\x41\xea\x61\xfa\x3b\
+\xc6\x8e\x1d\x8b\x4d\x9b\x36\x41\x14\xc5\x7e\xb5\x84\xb1\x02\xad\
+\xbc\x3f\xcd\x5f\x5b\x5b\x1b\xf6\xec\xd9\xc3\x3a\x69\xb7\xdb\x75\
+\x81\xc7\x71\x9c\x46\x4b\xb8\x65\xcb\x96\x3e\x5a\xc2\x48\x16\x93\
+\xde\x76\xeb\xd6\x2d\x2c\x5c\xb8\x90\xc1\xe2\x79\x5e\x17\x70\xe1\
+\x40\xaa\xd5\x5e\x79\x79\x79\xb8\x7e\xfd\xba\x46\x4b\x38\xa2\x5e\
+\x89\x1f\x98\x20\x08\xb8\x7b\xf7\x2e\x52\x53\x53\x0d\x85\x46\x03\
+\xd0\x12\xba\x5c\x2e\x5c\xbb\x76\x0d\xfd\x99\x61\xf0\x44\x51\x64\
+\xbf\x64\x57\x57\x17\x8e\x1c\x39\x82\x29\x53\xa6\x80\x88\x60\xb5\
+\x5a\x87\xa4\xf9\xd3\xeb\x50\x6b\x09\x27\x4f\x9e\x8c\x43\x87\x0e\
+\xe1\xd5\xab\x57\x9a\xbe\xe8\x0e\x51\xdd\x80\x24\x49\x00\x80\x9e\
+\x9e\x1e\x2c\x5d\xba\x14\x29\x29\x29\x4c\xd3\xac\x7c\xe1\xc1\x28\
+\x4e\x8d\x00\x18\xfa\x83\x26\x25\x25\x61\xc5\x8a\x15\x78\xf7\xee\
+\x9d\xa6\x4f\xba\x41\x54\x5f\xdc\xe7\xf3\x61\xe9\xd2\xa5\x2c\x28\
+\x64\x67\x67\xa3\xba\xba\x9a\x09\xc5\xf3\xf3\xf3\x99\x74\x57\xaf\
+\x88\x3b\x90\xb9\x50\xf9\x7b\xcc\x98\x31\x58\xb5\x6a\x15\xda\xda\
+\xda\x00\x00\xe7\xcf\x9f\xc7\xac\x59\xb3\xd8\xe7\x8b\x16\x2d\x42\
+\x77\x77\xb7\x71\x00\x8f\x1e\x3d\xca\xa4\xb5\x4f\x9e\x3c\x61\xef\
+\xab\x83\x88\xcf\xe7\xc3\xb1\x63\xc7\x90\x94\x94\xc4\x40\x1b\xe1\
+\x95\x16\x8b\x05\x76\xbb\x1d\x44\x04\xa7\xd3\x89\x93\x27\x4f\xa2\
+\xab\xab\x2b\xec\x77\x7c\xfc\xf8\x31\xe2\xe3\xe3\x41\x44\x38\x7f\
+\xfe\xbc\x71\x00\x4f\x9f\x3e\xad\xf9\xd2\x3b\x77\xee\xc4\x87\x0f\
+\x1f\x10\x3a\xcc\x95\xb9\xe5\xe2\xc5\x8b\x48\x4d\x4d\x35\x64\x5e\
+\xe4\x38\x0e\x13\x27\x4e\x64\x2a\x7e\x59\x96\x35\xc3\x53\x92\x24\
+\xf8\xfd\x7e\xec\xda\xb5\x4b\x13\x64\xaa\xaa\xaa\x8c\x07\x18\x9a\
+\x7b\xad\x59\xb3\x06\xb5\xb5\xb5\x9a\x94\x41\x7d\x0b\xc2\x8d\x1b\
+\x37\xb0\x64\xc9\x12\xcd\x7d\x20\xc3\x19\xde\xa1\xed\x2f\x5f\xbe\
+\x1c\xb7\x6f\xdf\x8e\xd8\xfe\xfd\xfb\xf7\x51\x50\x50\x10\xb6\xfd\
+\x11\x03\x18\x2e\x6d\x48\x4f\x4f\xc7\xcd\x9b\x37\xfb\x78\xa2\x12\
+\xf1\xde\xbe\x7d\x8b\xd5\xab\x57\xb3\xf3\x87\xaa\xd2\x57\xda\xdf\
+\xb6\x6d\x1b\x0b\x06\xea\x76\x42\xa3\xeb\x60\x6c\xc4\x00\xaa\xe7\
+\xa0\xf1\xe3\xc7\xe3\xc2\x85\x0b\x9a\x09\x5a\x10\x04\xe6\x15\x7e\
+\xbf\x1f\x3b\x76\xec\xc0\xc4\x89\x13\xfb\xe4\x6d\x3f\xca\xe9\x88\
+\x08\xe9\xe9\xe9\xd8\xbd\x7b\x37\x83\x14\x08\x04\x20\x08\x02\x6b\
+\xe7\xcd\x9b\x37\x88\x86\x19\x0e\x30\x5c\x67\x5d\x2e\x17\x36\x6c\
+\xd8\xc0\x3a\x15\xda\xd9\x9e\x9e\x1e\x54\x57\x57\xb3\x3c\xd2\x66\
+\xb3\x69\xbc\xd2\x6a\xb5\xb2\x3c\x2e\x33\x33\x13\x37\x6f\xde\x44\
+\x6f\x6f\xaf\x26\x30\x28\xc1\x41\x1d\x30\xa2\x69\x86\x03\x8c\x94\
+\x87\xe5\xe7\xe7\xc3\xe7\xf3\x69\x00\xaa\x87\xf9\x83\x07\x0f\x30\
+\x7d\xfa\x74\xcd\x0f\x10\x17\x17\x87\xcc\xcc\x4c\x34\x34\x34\x30\
+\x2f\x56\x07\x06\x51\x14\xd1\xdc\xdc\x0c\xb7\xdb\x0d\x3d\x6d\xc4\
+\x00\x2a\xe7\x2a\x5e\x44\x44\x70\xbb\xdd\x9a\x89\x3b\x74\xc2\x7f\
+\xf4\xe8\x11\xd6\xaf\x5f\x8f\xcd\x9b\x37\xe3\xe9\xd3\xa7\x9a\xf3\
+\xd4\xa9\x48\x65\x65\x25\x72\x73\x73\xd1\xcf\x2a\x13\x41\x8f\x07\
+\x62\x71\x31\xe4\x9c\x1c\xc8\x2e\x17\x60\xb7\x03\x76\x3b\x64\x97\
+\x0b\x72\x4e\x0e\xc4\xe2\x62\x04\x3d\x1e\xfd\x20\x0e\x17\x60\xa4\
+\x61\xee\x74\x3a\x71\xe2\xc4\x09\xe6\x89\xa1\x2b\x82\xd0\xc0\xa0\
+\xd8\xc1\x83\x07\x91\x98\x98\xc8\xae\x15\x71\x7d\x5e\x59\x09\x79\
+\xda\x34\x80\x68\x40\x87\x9c\x91\x01\xa1\xa2\x22\xfa\x10\xf5\x02\
+\xa8\x04\x1c\x9b\xcd\x86\xb2\xb2\x32\x74\x76\x76\x6a\x02\x8e\x7a\
+\xa8\x7b\xbd\x5e\x94\x94\x94\x68\x86\x36\xcf\xf3\xe1\x3d\xae\xa9\
+\x09\xf2\xec\xd9\x03\x06\xd7\x07\xe4\xec\xd9\x08\xb6\xb6\x46\x0f\
+\xa2\x1e\x00\x23\x95\xa0\x4a\x4b\x4b\xf1\xe5\xcb\x17\x4d\xfe\x76\
+\xe0\xc0\x01\x4d\x50\x51\xda\x0e\xeb\x75\x77\xee\x00\x49\x49\x43\
+\x86\xc7\x8e\xd4\x54\x04\xeb\xea\x62\x1f\x20\x11\x21\x21\x21\x01\
+\x6e\xb7\x1b\x5d\x5d\x5d\xe8\xe9\xe9\x01\x11\x61\xde\xbc\x79\xc8\
+\xca\xca\x62\xa0\x1a\x1a\x1a\x90\x93\x93\xc3\xee\xf4\x8c\x34\xd7\
+\xc1\x6e\x1f\x3e\x3c\xe5\xb0\xdb\x21\xd4\xd4\x0c\x1f\x62\x34\x01\
+\xaa\xcb\x4b\x4e\xa7\x13\xa5\xa5\xa5\x78\xf1\xe2\x05\x1b\xb6\x1d\
+\x1d\x1d\x48\x4e\x4e\x66\xe7\x2a\xcd\x2b\xe5\xb3\x67\xcf\x9e\x61\
+\xef\xde\xbd\x7d\xe1\xb5\xb6\x02\xa9\xa9\xd1\x83\xa7\x1c\x49\x49\
+\x08\x36\x36\xc6\x06\x40\x25\x3d\x19\x3d\x7a\x34\x2e\x5d\xba\xd4\
+\x27\x68\x04\x83\x41\xf8\x7c\x3e\xc4\xc7\xc7\x6b\x96\x6a\xe1\x4a\
+\x4e\x7d\xaa\xe2\xc3\x98\xf3\x06\x32\x27\x86\x33\xdd\x37\xd6\x39\
+\x8e\x23\x8b\xc5\xc2\x5e\xcf\x9d\x3b\x97\xaa\xaa\xaa\xa8\xb7\xb7\
+\x97\x0a\x0b\x0b\x49\x14\xc5\xa8\xdc\xf6\x2a\x56\x56\x12\x57\x5f\
+\xaf\x5f\x3f\xea\xeb\x49\xac\xa8\x30\x36\x0f\x54\x22\x2c\x11\xa1\
+\xa8\xa8\x08\xcd\xcd\xcd\xfd\x3d\x30\x62\xc8\x1e\x28\x4f\x9d\xaa\
+\x9b\xf7\x31\x2f\x9c\x3a\x55\x7f\x0f\x54\xef\x5f\x3b\x1c\x0e\xda\
+\xb8\x71\x23\x7d\xfe\xfc\x99\x2e\x5c\xb8\x40\x19\x19\x19\xcc\xdb\
+\x38\x8e\x8b\xda\x4d\xd2\x72\x77\x37\x71\xed\xed\xfa\x6f\xde\xb7\
+\xb7\x93\xe0\xf1\xe8\xb3\xb1\x6e\xb5\x5a\x89\xe7\x79\x02\x40\xd3\
+\xa6\x4d\xa3\x93\x27\x4f\x52\x67\x67\x27\x9d\x39\x73\x86\x12\x13\
+\x13\x49\x92\x24\xb2\xd9\x6c\xba\xdc\x59\x8e\x1b\x37\x8c\x13\x13\
+\xdd\xbf\x1f\x7d\x69\x07\xc7\x71\x24\x8a\x22\xa5\xa7\xa7\xd3\xd9\
+\xb3\x67\x29\x2f\x2f\xef\x87\xf3\x60\xd4\x3b\x55\x55\x65\x1c\xc0\
+\xbf\xff\x1e\xbe\x07\x2a\x5e\x04\x80\xe2\xe3\xe3\x69\xd9\xb2\x65\
+\xe4\xf1\x78\xa8\xa3\xa3\x83\xf2\xf2\xf2\x48\x10\x04\x63\x37\xaa\
+\x5b\x5b\x8d\x6b\xab\xa5\x65\xf8\x1e\x08\x80\xac\x56\x2b\x25\x27\
+\x27\x53\x6d\x6d\x2d\x65\x65\x65\x11\xd1\x3f\x4f\xd2\xb0\x58\x2c\
+\x64\xb3\xd9\x0c\xe5\xc7\x7d\xd7\xc4\x18\xd2\xd6\xf7\x67\x79\x0d\
+\xcb\x03\x67\xcc\x98\x41\x69\x69\x69\xe4\xf7\xfb\x69\xe6\xcc\x99\
+\xb4\x72\xe5\x4a\x6a\x6c\x6c\xa4\x18\x7a\x2a\xdd\xbf\x43\x3c\x54\
+\x57\x57\x87\xc2\xc2\x42\x8d\x8c\xe2\xdc\xb9\x73\x03\x92\x51\x44\
+\x2c\x3b\x0d\x21\x8d\x91\x5d\x2e\xdd\x53\x18\x96\xca\xb8\x5c\xd1\
+\x59\x8d\xa8\x73\xb9\xde\xde\x5e\x14\x14\x14\xb0\x55\x46\x7c\x7c\
+\x3c\x4e\x9d\x3a\x35\x68\x78\x43\x06\x98\x9b\x6b\x1c\xc0\xdc\xdc\
+\xa1\xe7\x81\xa1\x22\x45\x8e\xe3\x48\x96\x65\x1a\x35\x6a\x14\x55\
+\x57\x57\x53\x77\x77\x37\x1d\x38\x70\x80\x26\x4f\x9e\x4c\xdb\xb7\
+\x6f\xa7\x84\x84\x04\x2a\x2d\x2d\x25\xaf\xd7\xfb\xff\x15\x83\x28\
+\x46\x7d\x74\xc8\x7f\xfc\x61\xd8\x48\x8c\x4a\x5b\xfd\x49\xda\x3e\
+\x7f\xfe\x8c\x9a\x9a\x1a\x4c\x9a\x34\x89\xc9\x28\x16\x2c\x58\x80\
+\xe7\xcf\x9f\x33\x2f\x8b\xe4\x4d\x43\xf1\xc0\x60\x6d\xad\x61\x1e\
+\x18\x54\x6d\xdb\x46\xb5\xcc\x1f\xaa\xd2\x52\x3a\x5b\x53\x53\x83\
+\xb9\x73\xe7\xc2\xe1\x70\x80\x88\x90\x9d\x9d\x8d\xc7\x8f\x1f\x6b\
+\xea\x7d\x6a\xf8\xff\xb6\xa5\x9c\x6e\x01\x46\x96\x65\x4d\x09\xbe\
+\xa1\xa1\x01\xc5\xc5\xc5\x2c\xe0\x64\x65\x65\xa1\xac\xac\x8c\xc1\
+\x0b\x04\x02\x90\x24\x09\x82\x20\xe0\xe5\xcb\x97\x11\x01\x8a\xa2\
+\x18\xee\x79\x81\x10\xca\xcb\x75\x07\x28\x94\x97\x1b\xbb\x7f\x1c\
+\x4e\xd1\x25\x8a\x22\xf6\xef\xdf\x0f\x87\xc3\xc1\x8a\x11\x25\x25\
+\x25\xf8\xfa\xf5\x2b\x03\x14\xc9\x03\x15\xf3\xfb\xfd\xc6\x97\xb3\
+\x7e\xff\xdd\x58\x3d\x61\x24\x88\xca\x8e\x9a\x2c\xcb\x38\x7e\xfc\
+\x38\xe6\xcc\x99\xc3\xbc\x72\xfb\xf6\xed\x78\xf8\xf0\x21\x3e\x7e\
+\xfc\xc8\x00\x2a\x15\x1d\x00\xb8\x77\xef\x1e\xd6\xad\x5b\x17\x71\
+\x23\x29\xd8\xd2\xa2\x5f\x41\xf5\xfb\xfc\x6d\xb8\xaa\x75\x20\x69\
+\x4b\x7d\x7d\x3d\x72\x72\x72\x40\x44\x70\x38\x1c\x48\x4b\x4b\x63\
+\x65\x7b\xc5\x4b\x67\xcc\x98\xc1\xaa\xd3\x6e\xb7\x1b\x2d\x2d\x2d\
+\xc6\x95\xf4\xef\xdd\x1b\x79\x6d\xf5\x8f\x02\x8e\xe2\x95\x5e\xaf\
+\x17\x8b\x17\x2f\x66\x12\x61\xf5\x31\x61\xc2\x04\xac\x5d\xbb\x16\
+\x3e\x9f\x8f\x81\x8f\xa4\x7d\x11\xee\xdc\x01\x12\x13\x87\x0f\x6f\
+\xdc\xb8\x88\x9b\x4a\x23\xa6\xf4\xef\x6f\x8e\x04\x80\x8e\x8e\x0e\
+\x1c\x3e\x7c\x18\x0e\x87\x03\x29\x29\x29\xa8\xa8\xa8\xc0\xeb\xd7\
+\xaf\xc3\x9e\x1b\xd1\xb3\x1b\x1b\xff\x99\xb7\x86\x31\xe7\x05\x23\
+\x78\x79\x2c\xdd\x26\x11\x11\xa6\x3a\xb5\x09\xd5\xf8\x85\xeb\x4c\
+\x44\x0f\xaf\xa8\x80\x9c\x91\x31\xb8\x8d\xf5\xf2\xf2\x01\x6f\xac\
+\xc7\xec\xa3\xe0\x01\x90\x28\x8a\xac\x96\x18\xae\x50\x11\xba\x32\
+\xfa\x91\x67\x08\x75\x75\xc4\xdf\xbd\x4b\xfc\x5f\x7f\x11\xb5\xb7\
+\xb3\xaa\x0a\xc6\x8c\x21\x9a\x3a\x95\xe4\x39\x73\x48\x5e\xb4\x88\
+\x6c\x7f\xfe\x39\xb8\xd5\x58\x8c\x15\x2c\x66\x11\xd1\x7f\x07\xd3\
+\x81\x81\xfc\x18\x51\xdc\xae\xe8\xd3\x76\x4c\xdd\xee\xca\x71\xdc\
+\xd3\x1f\x00\xaa\x18\xc8\x0d\x83\x7a\xdd\x54\x18\xe9\xba\xff\x03\
+\x9f\xe7\x28\x7f\x42\x09\x7b\x03\x00\x00\x00\x00\x49\x45\x4e\x44\
+\xae\x42\x60\x82\
+\x00\x00\x03\xb0\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x2d\x49\x44\
+\x41\x54\x38\x8d\x55\x93\x4f\x6c\x93\x65\x00\xc6\x7f\xef\xf7\xbd\
+\xed\xda\x6d\x5f\x4b\xc7\xb6\x8c\x09\x68\xc0\x95\x11\x4d\x98\x0b\
+\x33\xc4\x84\x8b\x87\x1d\x17\x12\x35\x98\x10\x92\x25\xbb\x1b\x2f\
+\x26\x5c\x3c\xec\xa2\xd7\xda\x43\x3d\x11\x0e\x1e\xd4\x93\x1e\x34\
+\x46\xd4\x04\xc8\x22\x94\xd1\xcc\x11\x75\x71\xb8\x65\x0c\x5a\x36\
+\x60\xb4\x6b\x69\xbf\xef\x7b\xfb\xfe\xf1\x20\x4b\xc6\xef\xfc\xfc\
+\x9e\xd3\xf3\x08\xe7\x1c\xfb\x99\x9f\x9f\x97\x41\x10\x9c\x56\x4a\
+\x4d\xfa\xd8\x33\x4a\x29\x17\x2a\x5d\x51\x4a\x2d\x45\x51\x74\xbb\
+\x58\x2c\xc6\xfb\xf3\x62\xaf\x40\x08\x21\x4a\xa5\x52\xbe\xdf\x33\
+\xdf\xbe\x31\x3c\x70\x2a\xd5\x37\x48\x6f\x90\xe5\x68\xfc\x90\xe6\
+\xf6\x03\x36\x1f\x55\xdd\x0f\xb5\xf0\xaf\x6a\xdb\x5d\x2c\x95\x4a\
+\xcb\xee\x85\x28\xf7\xe4\x42\xa1\x70\xf1\xe4\x40\xea\xf2\xa0\x97\
+\x91\xd9\xc8\x92\xf0\x35\xed\xe6\x23\xda\x49\x47\x90\xc8\x31\xdc\
+\xe3\xc4\x27\x87\xd6\xde\xfc\xfe\x71\xfb\x76\x3c\x37\xf7\xb1\x10\
+\xe2\x4b\xe7\x9c\x93\x00\xa5\x52\x29\x3f\x7e\x30\x7d\x79\x28\x4a\
+\x48\xdd\x7a\xca\x13\xad\x19\x1c\x1a\xe6\xdf\xd5\x55\xc8\x7a\xbc\
+\x16\x48\xb6\x9f\xb5\x19\xf2\x05\x1f\xa4\x6d\x42\x07\xcd\x2f\x9e\
+\x9f\x3f\xbf\x08\x2c\xfa\x42\x08\x39\x92\x49\x5f\x3d\x9c\xca\x8d\
+\xca\x30\x46\xfa\x3e\xd2\x97\x58\x6b\xd8\x58\x5f\x27\xa7\x9b\x8c\
+\x24\xba\x8c\xb8\x26\x42\x75\xf8\xe5\xa1\xe6\x6c\x5f\xcb\x5b\x8a\
+\xe4\xd9\xd2\x95\xaf\xae\x78\xd9\x6c\x76\x2a\x7f\x30\x33\x61\xea\
+\x4d\x1e\x54\xab\x74\xb5\xc1\xe2\xe8\x6a\x4d\xb3\xd9\x24\x6e\x35\
+\x20\x6c\x41\xdc\xc6\xc4\x11\x07\xe8\x90\x33\x5d\x66\x82\x67\xe3\
+\xbe\xef\x4f\x4b\xad\xf5\x94\x10\x09\xac\xaf\x09\x72\x39\x34\x0e\
+\x61\x2c\x58\x47\x3b\xec\xa0\x92\x31\x28\xc0\x74\x91\x36\xe4\xed\
+\x6c\x44\x2b\x8c\x19\xf3\x15\xc6\xf4\x4e\x7a\x49\x61\xdf\x89\x48\
+\x12\x59\x8b\x48\x26\x50\xd6\xa0\xac\x45\x59\xc3\xd3\x7a\x9d\xd2\
+\xf5\x15\xe6\xbe\x59\xe4\xf3\x5f\x57\xa0\x1b\x53\x0f\x23\x2e\x2d\
+\x76\xe8\xef\x76\xe8\x41\x4f\x49\x6d\x8c\xec\x02\x9b\xb5\x2a\xd9\
+\x54\x9a\xc1\x6c\x16\x9c\x01\x67\x79\x77\x7a\x9a\xfe\xee\x2e\x43\
+\xad\x0d\x72\x9d\x1a\x98\x90\x9c\x17\xf3\xd9\x44\x97\x40\x76\xd1\
+\x5a\xfb\xb2\x15\xaa\xdf\x7d\x1b\xbd\xf7\xca\xb1\x63\x08\xa5\x50\
+\x5a\x83\xf8\x7f\x24\xd6\xf7\xa9\x7b\x07\xd8\x49\x9e\xc2\xcf\x8c\
+\x71\xa4\xbd\x86\xab\xfd\x4d\xd2\xc4\x34\x10\xb4\x94\xbd\x23\x81\
+\x3b\x26\x6c\xa0\x13\x03\x6c\xac\xaf\x31\xd0\x1f\x10\xa4\xd2\x20\
+\xc0\x79\x02\x63\xc0\xe2\xd0\x7e\x0f\x7f\x04\x27\xa9\x44\x8a\xbe\
+\x78\x9b\xc9\x64\x84\xd6\x7a\x49\xee\xec\xec\x94\x97\x89\xff\x79\
+\x7d\x3c\x77\x22\xf6\x3c\x74\x32\x81\x92\x3e\x4e\x3b\xac\xb5\x58\
+\x4f\x60\xac\xa0\x15\x47\xb4\x3a\x21\xf9\xb7\x26\xb0\xc2\xf0\xe3\
+\xad\x6b\xb5\x28\x6a\xfc\xec\x15\x8b\xc5\x78\xb3\x11\x7e\x18\x6d\
+\xdd\x33\xa3\xf9\x31\x64\x36\xc3\xcd\xbb\xcb\xac\x6c\xde\x27\x96\
+\x92\x7b\xd5\x2a\xa1\x10\xac\x6d\x6d\xf1\xd3\x8d\xeb\x44\xbe\x4f\
+\xbc\x51\xb1\x2b\x8f\xdb\x17\xca\xe5\x72\xcb\x03\x28\x14\x0a\xcb\
+\xd7\x56\xb7\x3f\xd2\xf7\x97\x2d\x9e\x63\xe0\xe8\x11\x82\x43\x23\
+\xb4\x05\xfc\x56\xbe\xc5\x76\xa7\xcd\xe1\x13\x79\xce\xbd\x7f\x0e\
+\x36\x16\xed\x77\x77\xb7\x3e\x5d\x58\x58\xb8\xe1\x9c\x73\x2f\x9d\
+\x69\x76\x76\xf6\xf4\x68\x20\xbf\x9e\x78\x75\xe4\xb8\x4d\x67\xd8\
+\xf5\x02\x8c\x35\xa4\xa3\x3a\xae\xb1\xc5\xcd\x3f\xd7\x6a\x2b\x4f\
+\x9e\x5f\x78\x21\xdb\x97\xde\xb8\xc7\xcc\xcc\x4c\x6f\x3a\x9d\x9e\
+\xd6\x5a\x4f\xa6\x84\x3d\x63\x8c\xf6\xeb\x1d\x55\xd6\x5a\x2f\xed\
+\xee\xee\x5e\xad\x54\x2a\x4d\xb7\x4f\xfa\x0f\x87\xf5\xc7\xde\x75\
+\x18\x2c\x4f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x01\x88\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x0e\x08\x06\x00\x00\x00\x26\x2f\x9c\x8a\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x07\x25\x00\x00\x07\x25\
+\x01\x95\x1f\x7a\xcd\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x05\x49\x44\
+\x41\x54\x28\x91\x9d\xd3\xcf\x2b\xc4\x51\x14\x05\xf0\xcf\xfb\x52\
+\x14\x16\x34\x1b\xc9\xc6\x56\x49\x59\x8e\x26\x2b\x92\xe6\x0f\xe0\
+\x0f\xb0\xc5\xc2\xff\xa0\x66\x65\x63\x61\xab\x94\x2d\xf9\x03\x94\
+\x29\x25\x25\xb2\x55\x7e\x94\x94\x15\x2b\xab\x6b\xe1\x29\x33\xf9\
+\x32\xe6\xd4\x59\xbc\x73\xdf\xb9\xb7\xde\x3d\x4f\x44\xf8\x8b\x98\
+\x42\x13\xeb\xa8\xb4\xd4\x4a\x0c\xfd\xd8\xc2\x2d\x2a\xe8\xc1\x02\
+\xf6\x70\x8f\xd9\xd2\x06\xa8\xe1\x3c\x4f\x3b\xc7\x5c\x5b\x7d\x1e\
+\xcf\x58\x6a\x69\x80\x21\xec\x60\x1f\xa3\x59\x3b\xc3\x1c\x46\x30\
+\xf6\xed\x6e\x15\x77\x18\x2c\x52\x4a\x87\x29\xa5\xc0\x2b\x86\x23\
+\x62\x25\x22\x9e\xb4\x62\x00\xa7\x29\xa5\x46\x4a\xa9\x37\x22\x9a\
+\x38\xc1\x46\x81\x3a\x26\xd1\xc0\xbb\x1f\x10\x11\x0f\xf9\x21\x67\
+\xb0\x94\xe5\x03\x54\x8b\x7c\xb8\xcd\x2c\x45\x44\xbc\x61\x1b\xab\
+\x59\xba\xc6\x74\x51\x6e\xe9\x0c\x5f\x0d\x26\x32\x4b\x91\x52\x1a\
+\xc2\x1a\x76\xb3\x34\x85\xcb\x02\x47\xb8\xc1\x26\xfa\x4a\xcc\xe3\
+\xb8\xc2\x05\x8e\xb3\xbc\x8c\x66\xb7\x6b\xac\xc9\x6b\xec\x26\x48\
+\x75\xbc\x68\x0f\x52\x07\x51\x5e\xf4\xb9\xba\x47\xbf\x45\xf9\xbf\
+\x9f\xe9\x03\x3f\x0d\x00\x45\xf9\xda\x88\x64\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x06\x26\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x03\x12\
+\x0b\x1e\x32\x04\x5b\x12\x8e\x00\x00\x05\xa6\x49\x44\x41\x54\x48\
+\xc7\xa5\x54\x7b\x6c\x53\x75\x18\x3d\xf7\xdd\xdb\x76\x65\x6c\xeb\
+\x5e\x65\x8e\x41\xd9\x9b\xc7\x1c\x81\x81\xe2\xd8\x6a\x27\x20\x31\
+\x08\x6e\x08\x11\xc5\x77\xa2\x41\x45\x8d\x28\x60\x82\xcf\xa8\xa0\
+\x18\x7c\x90\x60\x4c\xc0\x11\x98\x6e\x80\x0f\xa2\x0c\xf6\x10\x44\
+\x24\xe0\xc0\x09\x54\xb7\x95\xb5\x2b\xdd\xa3\x5d\x29\xac\xbb\xb4\
+\xb7\xf7\xd1\xeb\x3f\xfd\x43\x07\x4c\x17\xbf\xe4\xfb\xe7\xfc\x92\
+\xef\x7c\xbf\x73\xbe\x1c\x0a\xa3\xd4\x9c\x39\x73\x78\xaf\xd7\xab\
+\x02\x80\x7d\xd1\xa2\x15\x13\x27\x4e\x2e\xa0\x48\x5c\x0c\x06\x83\
+\x2a\xfe\x6f\x2d\xa9\xa9\x79\x69\x6f\xfd\x57\x81\x95\xab\x1f\xdc\
+\x04\x00\xbb\x6a\x6b\x2f\xf9\x7c\x03\xb1\xaa\xc5\x8b\x77\x8c\x65\
+\x0e\x79\xb3\x87\xcc\xcc\x4c\xdb\xf4\xe2\xe2\xe4\x47\x57\xaf\x5e\
+\x7f\xea\xd7\x53\x62\xc9\x8c\x69\x96\x8e\xae\x0e\x22\x1c\x16\xbb\
+\x01\x90\xb7\xce\xbe\xbd\xf4\xbf\x10\x50\x23\x81\xea\xea\x6a\xca\
+\xe1\x70\x68\x49\xa9\xe9\x99\xf3\xe7\xcd\xb5\x03\x31\xca\xe7\xef\
+\xa3\x87\x85\x61\xc2\x64\x4a\x80\xbd\x72\x7e\xc5\xd4\xe9\x25\x4f\
+\xac\xa8\xb9\xef\x29\xef\xe0\xe0\x49\xb7\xd3\xe9\x1a\x8d\x80\x1e\
+\x09\x24\xa5\x65\xb4\xd4\xd5\xd7\x5b\xa7\x4c\xce\x49\x1d\x0c\x0c\
+\x20\x1c\x89\xc0\x60\xd0\x43\xc7\xe9\x10\x12\x42\x30\xf0\x7a\xaa\
+\x64\x46\x71\x26\x4d\xb1\x12\x43\xd2\x36\x00\xcd\x63\x22\xc8\xb5\
+\xe6\xe4\xe7\x64\x4f\x48\xe5\x38\x0e\x62\x34\x0a\x83\x21\x01\x87\
+\x1b\x9b\x7e\x22\x18\x9a\xbf\xbf\x7a\xe9\x4c\x51\x0c\xc3\x94\x90\
+\x80\x17\xd6\x6d\xd8\xde\xd2\xd8\xb8\x7e\xcc\x1e\xec\xfb\xfa\x1b\
+\xdb\x5b\xef\xbd\x7f\x0e\x84\x06\x8e\x63\x11\x09\x8b\x62\x6b\x73\
+\xcb\x82\x96\x43\x87\xcb\xa3\xa2\x24\xb1\x1c\x0b\xde\xa0\x03\xcf\
+\xeb\x2b\x67\xce\x9d\x5b\x3c\x66\x0f\x2e\xb9\xdd\x7e\x4b\x76\x76\
+\x9e\xdd\x56\x51\x26\xc9\x51\x64\xa4\xa7\xd3\x2a\x41\x2d\xcc\x2b\
+\xc8\x7f\x60\xde\xed\x73\xb3\x15\x55\x46\x4c\xd5\x30\xb5\xa0\x30\
+\xcd\x60\x34\x2e\x6b\x6d\x6e\xde\x32\x1a\x01\x31\x12\xb0\x2f\x58\
+\xf4\xf6\x73\xcf\x3e\xbd\xd6\x94\x60\xd0\xe9\xf4\x1c\x34\x4d\x83\
+\xd9\x9c\x0e\x02\x80\x10\x0e\x81\x24\x48\x78\x3c\x7d\xb2\xe3\x8f\
+\xce\x48\xdb\x99\xb3\x67\x5c\x6e\xd7\x9a\x87\x1e\x58\xd5\x7a\xee\
+\xfc\xf9\x3f\x7c\xbd\xde\x8a\xfa\xfa\x7a\x75\x54\x89\x14\xc8\xcd\
+\x83\x81\x80\x7a\xa4\xf9\x68\x8f\x2c\xc7\xe4\x04\x93\x09\xc2\xb5\
+\x21\x08\xe1\x10\x68\x8a\x46\x30\x78\x45\x3b\xdd\xf6\x1b\x61\x9b\
+\x5f\xae\x0b\xf8\x7d\xed\x96\xcc\xcc\x3d\x65\xb3\x4a\x53\xaa\xee\
+\xac\xcc\x1d\x39\xfc\x86\x12\xb9\x9d\xdd\x2e\x4f\x5f\xa0\xb1\x6e\
+\xf7\x17\xaf\x9c\x77\x76\x4b\x53\x26\x4d\xba\x93\x02\x15\x1b\xe8\
+\xf7\x0b\x17\xbb\xdd\x0a\x01\x92\x6d\xfb\xb5\x4d\x2e\x2e\xca\xe7\
+\xec\x76\x5b\x59\xc5\x1d\xf3\xd2\x34\x4d\x03\xc7\xe9\x74\x3d\x03\
+\xfe\xe8\x5d\x0b\x17\xbc\x9a\x96\x95\x65\xe9\x72\x38\x7e\xbe\x21\
+\x01\x00\xf4\xf7\x7a\xfa\x01\x68\x56\x6b\xee\x2a\xdb\xfc\xf2\x92\
+\xe6\x1f\x8f\x75\x7f\xb0\x79\x4b\x4e\xaf\xcf\xbf\x44\x56\x55\x63\
+\x76\xd6\x04\x9d\xc9\x64\x20\x02\x97\xfd\x18\x16\x42\x10\xc2\x02\
+\x28\x9a\x24\xab\x6c\x95\xf6\x59\xa5\xa5\xd6\xf1\xa6\xc4\xe9\xed\
+\x57\xaf\x7e\x74\xd5\xed\x56\x88\xd1\x0c\xb2\x5a\xad\x5c\xba\xe5\
+\x96\x7b\xc0\x82\x66\x48\x46\xfa\xec\xd3\x8f\xeb\xdd\x9e\x1e\xc2\
+\x68\xd0\x63\xc0\xd7\x0f\x8e\x65\x31\x14\x12\xc4\xec\x5b\xb2\x74\
+\x92\x1c\x85\x29\x61\x1c\x06\xfa\x07\xc5\xcf\x77\xee\x7a\xa7\xa1\
+\xae\xee\x75\x00\xda\x0d\xa3\x62\xda\xb4\x69\x86\x67\x5e\x7c\xfe\
+\xd8\x27\xdb\xb7\xf7\x26\x26\x27\x95\x1c\x3f\xd2\xb2\x57\x56\xa2\
+\x13\x0f\x35\x1e\xe9\x31\xa7\x98\xb5\xcb\xc1\x00\xc6\x8f\x1f\x8f\
+\xa6\xd6\xe3\xa7\x37\xbf\xb3\x25\xf9\xbb\xef\x0f\x9f\x48\x4d\x4d\
+\x87\xa2\xca\xa0\x59\x5a\x69\xa8\xab\x7b\x0d\x80\x76\xd3\x2c\xca\
+\xcb\xcb\x13\xab\x2a\x6d\x53\x23\x91\xe1\xe4\xc2\x82\xdc\x95\x55\
+\x0b\xef\xde\xf6\xe4\x23\x8f\x6f\x6a\x6b\x6f\xef\x0a\x85\x42\x51\
+\x96\x65\xc1\xf3\x3c\x28\x86\x92\x00\x80\xd7\xb1\x3c\xaf\xd3\x81\
+\xe5\x58\x18\x0d\x7a\xfd\x96\xad\x5b\x5d\xe5\x76\xfb\x8a\xeb\xce\
+\xb4\xa8\xa8\x88\x9d\x9c\x9f\xbf\x37\x22\x8a\xed\xab\x56\x2e\x5f\
+\x97\x66\x36\xeb\x29\x8a\x84\x1a\xd3\xe0\x72\x75\x43\x92\x63\x51\
+\x9a\xa1\x89\x39\xb3\x67\xb2\xd7\xc2\x02\x52\xcd\x19\x18\x1a\x0a\
+\xc9\x49\x49\x89\x4c\x58\x14\xc0\x30\x2c\xfe\x74\x74\x22\x23\x3d\
+\x43\x5b\xbb\x6e\x63\xf5\x89\xd6\xa6\x7d\xff\x88\x0a\x73\x56\xd6\
+\xd2\x77\xdf\x7a\xf3\xde\x0b\x17\xce\x2d\xa5\x28\x12\x3d\x1e\xb7\
+\x46\x92\xb4\x6c\xb1\x64\xb0\x93\x26\xe5\x60\xef\x97\x07\xd4\xd5\
+\xab\x56\xea\x65\x59\xd1\x0c\x06\x03\x31\x2c\x5c\x05\xab\xa3\x99\
+\xb0\x28\x80\xa2\x28\xf4\xf7\xf9\xe4\xf7\xb6\x6e\xdb\xc8\x50\x8c\
+\xd5\x7f\xc9\x7d\xf0\x3a\x89\x38\x8e\x2b\xee\xed\xeb\x13\x4d\x26\
+\x13\xa2\x92\x84\x63\xc7\x7f\x69\xdd\xb1\xb3\x36\x9f\x24\x29\x25\
+\x29\x39\x19\xaa\x2c\x89\x9b\x3f\xdc\xe6\x7a\xfe\xa5\x8d\xaf\x5f\
+\x0e\x0e\x49\x1c\xc7\x43\xcf\xeb\xa1\x2a\x31\xf4\xb8\xbd\x38\xf4\
+\x43\x93\xef\x44\x6b\xeb\x8e\xa3\x4d\x87\xd7\x38\x9d\xce\xe8\x75\
+\x67\x7a\xb1\xa3\xa3\xa5\xbd\xb3\x6b\x67\xae\x75\xf2\xc3\xe9\x69\
+\x69\xfc\xb8\x71\x89\x46\xa8\xca\xec\x8a\x8a\x72\xab\x24\x89\x10\
+\x84\x61\xde\xa8\x37\x0e\xd7\xed\xae\x5d\x5b\x58\x58\xbc\x9c\x80\
+\x66\xda\xf4\xc6\xbb\x9d\x0d\xfb\x0e\x1c\x2a\x2a\x28\xcc\x65\x58\
+\x86\x73\x76\x7b\x7e\x0e\x06\x06\x7d\x71\x93\xd5\xeb\xd2\x34\x23\
+\x25\x75\x19\xcb\xb0\x3a\x9a\xa6\x31\x6b\x56\x89\xd9\x56\x59\x6e\
+\x93\xa4\x28\x8e\x34\x9d\x1c\xf2\x0f\x5e\x11\x3a\x9c\x4e\x17\xc7\
+\x71\xcc\xc9\xd3\xa7\xbd\xfd\x03\x7e\xf6\xc2\xef\xed\xaf\xf5\x79\
+\xbd\x1d\x04\x41\x5d\x13\x86\x42\x47\x9d\x7f\x5e\x38\x17\xf7\x96\
+\x18\x69\x32\x0d\x20\x79\x69\x4d\x4d\xc3\x0b\xcf\x3e\x73\x5b\xdb\
+\xd9\x33\xa2\xa2\xa9\x52\x96\xc5\x32\xce\xe3\x71\xa3\x76\x4f\xc3\
+\xae\xdf\x4e\x9d\xda\xc1\xf3\xbc\x12\x89\x44\xae\x00\x88\xc6\x15\
+\x20\x01\xc4\x00\xa8\x00\xa4\x38\x1e\x05\x10\x01\xa0\xfd\xfd\x07\
+\x46\x00\xc6\x6f\xf7\xef\x5f\x27\x4a\xd2\x56\x8f\xab\xe7\x6c\x77\
+\x57\xc7\xa5\x97\x37\xbc\xb2\x21\x25\x25\x59\xc7\xb1\x6c\x3e\x00\
+\x26\x12\x89\x08\xf1\x41\xc3\xf1\xa1\x44\x5c\x8e\x18\x00\x39\xde\
+\xb1\x9b\xa5\x69\x62\xbc\x4d\x00\x0c\x00\xb8\xd2\xb2\xb2\xc7\x22\
+\xe1\xb0\xa7\xd3\xe1\x38\xa8\x28\xca\x65\x00\x21\x00\xc1\xf8\x96\
+\xff\x5a\xc4\x28\x38\x1d\x97\x80\x88\x6f\xa4\xc4\x37\x1e\x53\xfd\
+\x05\x6c\xb2\x71\x61\xdf\x53\xd7\x0d\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x08\x71\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x08\
+\x2b\x49\x44\x41\x54\x78\x01\xed\x9c\x79\x6c\x54\x45\x18\xc0\xbf\
+\xed\xd2\xed\xcd\x11\x69\x2b\xd0\x0b\xba\x3d\x68\x85\x9a\xb6\x14\
+\x0a\xa5\x82\x41\x02\x05\x09\x04\x15\x89\x52\x8b\x07\x12\xff\x41\
+\x04\x39\x1a\x10\x28\x42\x49\x03\x18\xa3\x09\x04\x51\x48\xc1\x28\
+\x46\x13\xa3\x02\x46\x11\x10\x28\xe5\x68\x8b\x14\x8a\x54\xda\x40\
+\x0f\x20\xa5\x20\x68\xef\xda\xee\xfa\x7d\x4b\x77\x79\xb3\x6f\xb7\
+\xbb\xf3\xf6\xea\x2e\xef\x6b\xa6\x6f\xae\x6f\xbe\x99\xdf\xce\xbc\
+\x37\x6f\xe6\xbd\xa7\x00\x0e\x69\x6f\x6f\x8f\xc6\xec\x33\x14\x0a\
+\x45\x06\x1e\xe3\xd1\x85\xa1\xeb\x8f\x4e\x89\xce\x1d\xa5\x1b\x2b\
+\xfd\x2f\xba\x7a\x74\x57\xb5\x5a\xed\x29\x3c\x1e\xf2\xf5\xf5\xad\
+\xc2\xa3\x55\xa2\xb0\x94\x0b\x0b\xf5\xea\xec\xec\x9c\x8d\xf9\x96\
+\xa3\x4b\xb7\x94\xdf\x43\xd2\x8b\xb1\x1d\x5b\x55\x2a\xd5\xf7\xd8\
+\x59\x34\xbd\xb5\xa9\x57\x80\x08\x6e\x0c\x02\xdc\x81\x05\xa4\xf4\
+\x56\x88\x07\xa7\x95\x22\xc0\x77\x10\xe4\x39\x73\x6d\xf4\x32\x95\
+\x80\xd0\x14\x1d\x1d\x1d\x2b\xf1\x78\x1a\xd3\x1f\x57\x78\x84\x26\
+\x05\x19\x14\xf5\xb0\x30\xd9\xd9\x44\x91\xa8\xa0\xc4\x9e\xb7\x1b\
+\x95\x73\xa8\x04\x59\x0c\x04\xf6\x62\x4f\x7c\x13\x7b\x24\x9d\x37\
+\x0d\xc2\x00\xa4\x9e\x87\xf0\xf6\x62\x6a\xb6\x21\x87\xec\x11\x12\
+\xd8\x87\x10\x5f\x43\x88\x5a\x7d\x24\x33\x84\x11\xde\x4a\x4c\x90\
+\xe1\xe9\xe9\x88\x8f\x0b\x7a\x18\x19\x52\x0c\x3d\x10\x13\x52\xb1\
+\x07\xd2\xd5\xa7\x9f\x21\x55\xf6\x98\x22\xd0\x8d\x3d\x30\x1d\x7b\
+\xe2\x79\x4a\xd4\xf5\x40\x04\xe7\x85\x6e\x27\x86\x65\x78\xa6\x90\
+\xb1\x71\x4a\x62\x45\xcc\x28\x5a\xf7\x0f\x7b\xdf\x1c\xf4\x3f\xce\
+\x57\x5b\x16\x91\xe5\x50\x72\x0f\xb3\x87\x00\x31\xff\x7b\x96\x75\
+\xe4\x1c\x46\x04\x96\x51\x58\x81\xb7\x67\x6a\x1c\xd3\xd7\x8c\x12\
+\xe5\xa0\x15\x04\x70\x18\xc7\xd0\x39\x2f\xcb\x8a\xbc\x66\xb3\x60\
+\x21\x70\xe6\x6c\x29\x1c\xfb\xbd\x08\xca\xca\xca\xa1\xe1\xce\x5d\
+\xe8\xee\x66\xa6\x4a\xb0\xfe\x83\xe5\x30\x75\xca\x24\xb3\x65\xac\
+\xcf\xdb\x0a\xbf\x1c\x39\x6e\x36\xdd\x5e\x09\x4a\xa5\x12\x42\x43\
+\x06\x43\x72\xf2\x68\x98\xfc\xcc\x04\x18\x37\x36\x05\xb0\xf3\xd8\
+\x52\x7c\x56\x3f\x2c\x20\x53\x6a\x09\x37\x6a\xea\x20\x6f\xd3\x76\
+\xb8\x7a\xd5\x3d\x3a\x30\xfd\xb0\xb7\x6e\x37\xc0\xad\x83\xbf\xc2\
+\x4f\xe8\xe2\xe3\x63\x60\xcd\xea\x77\x61\xc4\xf0\x48\x49\x08\x90\
+\xdd\x44\xba\x88\xc4\x49\xd1\x2e\xbb\x50\x0e\x8b\x16\x2f\x77\x1b\
+\x78\xa6\xda\x48\x3f\xfc\x5b\x6f\x2f\x83\x92\xd2\x8b\xa6\x92\xad\
+\x89\x8b\x27\x80\xe1\xd6\xe4\x14\xe6\xb9\x7e\xa3\x16\x56\xe5\x6e\
+\x82\xe6\x96\x16\x61\xb4\x5b\xfa\xdb\xda\xdb\x21\x77\xcd\x66\xa0\
+\xd1\x24\x41\xc2\x08\x60\x20\xaf\xe2\x96\x82\x4f\x3c\x02\x9e\xbe\
+\xdd\xd4\x11\xa8\x4d\x12\x24\x88\x00\x72\x2d\x86\x16\x9f\x29\x81\
+\x4b\x97\xff\x94\x60\xab\x6f\xab\x94\x5f\xba\xa2\xbb\x18\x72\xd6\
+\x52\x49\x00\xb9\xe4\xe8\x71\x5a\xb4\xf5\x4c\xf9\xed\xd8\x49\xee\
+\x86\x71\x03\x2c\x2b\xbb\xc4\x6d\xc4\x5d\x14\xa4\xb4\x8d\x1b\x60\
+\xe3\xdd\x7b\xee\xc2\x83\xbb\x9e\x52\xda\xc6\x0d\xb0\xab\xab\x8b\
+\xbb\x62\xee\xa2\x20\xa5\x6d\xdc\x00\xdd\x05\x86\xb3\xea\x29\x03\
+\xb4\x91\xb4\x0c\xd0\x46\x80\xdc\x0b\xa8\x1f\x6e\x58\xc5\x6d\x32\
+\x31\x41\xd2\xdd\x22\xb7\x1d\x57\x28\x70\x03\x7c\x76\x72\x86\x2b\
+\xea\xd9\x67\x6d\xca\x43\xd8\xc6\x9f\x46\x06\x28\x03\xb4\x91\x80\
+\x8d\xea\x72\x0f\x94\x01\xda\x48\xc0\x46\x75\xb9\x07\xda\x08\x90\
+\x7b\x1a\xb3\x66\xdd\x16\x6e\x93\x2f\xce\x9d\x05\x49\xa3\x13\xb8\
+\xf5\xdc\x41\x81\x1b\xe0\xd1\x63\xa7\xb8\xdb\x95\x39\x71\x1c\xea\
+\x78\x26\x40\x79\x08\x73\x77\x07\x56\x41\x06\xc8\xf2\xe0\x0e\xc9\
+\x00\xb9\x91\xb1\x0a\x32\x40\x96\x07\x77\x48\x06\xc8\x8d\x8c\x55\
+\x90\x01\xb2\x3c\xb8\x43\x32\x40\x6e\x64\xac\x02\xf7\x3c\x90\x55\
+\x77\x8f\x50\xf4\x88\x28\x48\x4d\x49\x82\x51\x4f\x8d\x84\x88\xf0\
+\x61\x10\x82\x4f\x68\xf9\xf9\xf9\xe9\x2a\xdf\xd6\xd6\x06\x77\xf0\
+\x89\xb2\xda\xba\x9b\x92\x1e\x18\xf0\x58\x80\x7e\x7e\xbe\x30\x7b\
+\xd6\x34\x98\x39\x63\x2a\x0c\x8f\x8a\x30\xfb\x4b\x7b\x7b\x07\x41\
+\xff\xfe\x41\xa0\x56\x0f\x07\x29\x8b\xc5\x1e\x07\x90\x9e\x01\x9c\
+\x3b\x67\x06\x2c\xcc\x99\x0f\x03\x10\x8c\xa3\xc5\xa3\x00\x86\x87\
+\x0d\x85\x0d\xeb\x56\x40\x7c\x9c\xda\xd1\xdc\x0c\xe5\x73\x03\x94\
+\xd2\xcd\x43\x43\x42\x0c\x06\x4d\x79\x12\x13\x62\xa1\xab\xdb\xb6\
+\x0d\xfb\xb8\x58\x35\xbc\xf4\xc2\xf3\xe0\xe3\xe3\x63\xca\x84\xc3\
+\xe2\xe8\x9d\x38\xc3\x5b\x37\x0e\xb3\xe2\xe2\x82\xb5\xa0\x85\x53\
+\x2d\x17\xe1\x70\xd3\x69\x28\x6e\x29\x87\xaa\xce\x7a\x78\xd0\xdd\
+\xa4\xab\xd5\x40\x25\x9e\xff\x54\x61\x90\x1e\x30\x1a\xa6\x07\x8d\
+\x87\x8c\x80\x24\x50\xe0\x9f\xb5\xe2\xd1\x00\x35\xa0\x81\xfd\xf7\
+\x0f\x43\x41\xe3\x3e\xb8\xd6\x51\x6b\x15\x13\xb5\x4f\x38\xac\x08\
+\xce\x86\x05\x83\xa6\xe3\x2b\x0c\x96\x67\x79\x1e\x0b\xb0\xaa\xa3\
+\x0e\x72\xea\x37\xc0\xf9\xd6\x2b\x56\x81\x33\xce\x34\xc6\x3f\x01\
+\xbe\x0c\xdf\x08\x91\xaa\x21\xc6\x49\x4c\xd8\x23\x01\x1e\x69\x3e\
+\x07\x2f\xd7\xe4\x42\x93\xa6\x95\x69\x2c\x6f\x20\xb8\xdf\x40\xf8\
+\x3a\x22\x5f\x37\xac\xcd\xe9\x7a\x1c\xc0\x93\x2d\x7f\x40\xd6\xf5\
+\x25\xd0\xa9\xfd\xcf\x5c\x9b\xb9\xe2\x55\x0a\x6f\xf8\x31\x6a\x3b\
+\x4c\x0a\x4c\x31\xa9\xe7\x51\x00\x6b\x3a\x6f\xc3\x84\xea\x37\xa0\
+\xb1\xeb\x81\xc9\xc6\x4a\x8d\x0c\xf2\xf2\x87\x62\xf5\x17\x10\xe3\
+\x23\x9e\x90\x5b\x3e\x4b\x4a\xb5\xea\x64\x3d\xba\x60\xbc\x52\xb7\
+\xd6\xee\xf0\xa8\x19\x74\x2a\x58\x58\x9f\x87\x16\x34\xa2\x56\x71\
+\xcf\x03\xa5\xec\x89\xd0\xc3\x45\xa1\xa1\xc1\x22\xe3\xfa\x88\x8a\
+\x2b\x95\xd0\xd0\xd0\xa8\x0f\x5a\x3c\x8e\x4d\x4b\x86\x80\x00\x7f\
+\x26\xdf\x3e\xbc\xda\x4a\xbd\x60\x30\x05\x99\x09\x50\xd9\x85\xf7\
+\x0f\x41\xce\xa0\x99\x4c\x0e\xee\x21\x3c\x3e\x93\x2d\x80\x29\xcd\
+\x4c\xc0\x9e\xaf\x7a\xa9\xa3\xa3\xa0\x70\xcf\xa7\x8c\x25\x9a\xe7\
+\x25\x56\xce\x83\x6a\x9c\xdf\x39\x52\xa2\x71\xbe\x58\x11\x77\x80\
+\x99\x27\xba\xdd\x10\x4e\x49\x4e\x12\x31\xa2\x0b\x87\xa3\xe1\x91\
+\x51\xb2\x41\xb6\x84\xe2\x76\x00\x13\x13\xe9\x7b\x3f\xac\xfc\xdc\
+\x44\x2f\xda\x3b\x47\x8c\x6d\xb9\x1d\xc0\xa8\x48\xf1\x9b\x69\x67\
+\x5b\x9d\xf7\xea\xc5\xb9\xd6\xcb\xcc\x2f\xc5\x0d\x90\x96\x8b\x5c\
+\x29\xc1\xc1\x4f\x88\xcc\xff\x85\x77\x1d\xce\x92\x4a\xa3\x5b\x42\
+\x6e\x80\x21\xc1\x83\x9d\x55\x57\x93\x76\xfc\x7b\x56\x92\x85\x89\
+\xff\xf4\x2c\x0c\x08\xe3\x1c\xe5\xd7\x2f\x42\xe8\xcb\xe7\x06\x98\
+\x9c\x3c\x4a\xaf\x2b\x1f\x91\x00\x37\x40\x7a\xd3\xdb\x95\xd2\x8a\
+\x7b\x18\xc6\x32\x00\x97\xa4\x9c\x25\xb4\xfc\x25\x14\x02\xc8\xbe\
+\x9f\x2f\x4c\x35\xe1\x4f\x1f\x97\xaa\x7b\xd3\xdb\x44\x92\x53\xa2\
+\x1a\x1b\xef\x89\xec\xc4\xe2\x12\x94\xb3\xc4\xd8\x16\x01\x6c\xe6\
+\x31\x4e\xdf\x18\x58\x9b\xbb\x14\x02\x03\x02\x78\xd4\xec\x96\xb7\
+\xa6\x56\x7c\xc1\x18\xeb\xef\xbc\xd3\x8a\xb1\x2d\x02\x28\xae\x91\
+\x85\xe6\xd2\x2e\x57\xfe\xa6\x5c\x97\x40\xac\xa8\xa8\x14\xd5\x6e\
+\x5a\x90\xf3\x3e\x6b\x68\x6c\x8b\x00\x8a\x6b\x24\xaa\xa2\x38\x82\
+\xee\x08\x76\xed\xdc\xea\xd4\x0d\x1c\xaa\x45\x49\x29\x7b\x27\x40\
+\x71\x13\x03\x9e\x06\xba\xcd\x72\xb4\x90\x0d\xb2\x25\x14\xfa\xe4\
+\xd3\x09\x61\x04\x8f\x9f\x26\xb5\x9f\xef\xfa\x08\xb6\x15\xac\xc7\
+\xfd\xd7\xe7\x60\xc8\x93\xa1\xe0\xe8\x79\x62\x55\xf5\x0d\xa0\x6f\
+\x36\x08\x85\xf6\x30\x56\x84\x64\x0b\xa3\x1c\xe2\x27\x1b\xc6\xfb\
+\x25\x1e\xf3\xe1\x1d\x5a\x6a\xca\xac\x5e\xe4\xb0\x15\x99\x54\xbf\
+\x91\x70\x22\xfa\x33\x50\x2a\x68\xd0\x3e\x12\xaf\x9e\x0f\xae\x3a\
+\xef\x66\xf2\x91\x6d\xbb\xfa\x68\x03\x68\x7f\x78\x1e\xd0\x32\xbc\
+\xbd\x85\x16\x54\xf7\x84\xaf\x13\xc1\x43\x3b\x7f\xeb\x71\x6e\xb3\
+\xb7\x51\x57\x94\x17\xa5\x1a\x0a\x5f\x45\x6c\x06\x5a\x86\xb7\x97\
+\x50\x59\xdf\x44\xe6\x43\xac\x8f\x78\x35\x1a\x4f\x7f\x1b\x75\x1b\
+\xa0\xe8\xa1\x2f\xf5\xd2\xf7\xf0\x92\xed\x65\xd8\x95\xe5\xd0\xa6\
+\xd2\xbc\x9a\xd5\xd0\xac\x11\x4f\xba\x79\xea\x35\x18\x7b\xf3\x01\
+\xf3\x9b\x4a\x17\xf0\x1b\x82\x69\xba\x1e\x88\x73\x3b\x0d\xba\xc5\
+\x58\xb8\x6d\x8f\x07\xf0\xd4\xce\x81\x79\xa7\x04\xa6\xc1\x19\xf5\
+\x1e\xa0\xf3\x96\x54\x21\xdd\xa2\xe8\xdd\xe6\x76\xe4\xda\xb0\xd3\
+\xcd\x47\x66\x5d\xcc\x16\x3c\x3e\xa5\x40\x2f\x03\xe7\x4b\x35\xda\
+\xd7\xf4\xe8\xc2\x42\x4b\xfd\x05\x8d\x85\x40\xfb\xc4\xd6\x08\x6d\
+\xac\xbf\x1f\xbc\x00\xb2\x07\x65\xe1\x59\x55\x7f\x86\x63\x34\x35\
+\x08\x6f\x0e\x5e\x3b\x7e\xa0\x58\x06\x20\x26\xd0\x47\x68\x0b\x31\
+\xfe\x55\x46\xc5\xcd\x03\xfa\x47\x3b\x0e\x35\x15\xe9\x1e\xed\xa8\
+\xee\xbc\xc9\x3c\xda\x11\xad\x1a\xa6\x7b\xb4\x23\x2b\x68\x82\xa5\
+\x47\x3b\xb4\xd8\xeb\x72\x70\xe8\x12\x23\x9d\x30\x00\x29\x06\x21\
+\xca\x9f\x41\x7e\xc8\xc6\xf8\x3f\x6d\x34\xcf\xc7\x87\x97\xbe\x13\
+\x26\x88\xfa\x28\x12\xee\x46\xc2\xaf\x63\x26\x1a\xce\x1e\x71\x4e\
+\x14\x36\x58\xa2\xff\x26\x76\xac\x51\xc6\xf0\xa8\x2c\x51\x0f\x14\
+\x1a\xc0\xe1\x9c\x86\x8a\x3b\x30\xce\x23\xae\xce\xc2\xb6\x59\xe9\
+\xa7\x2f\xf6\x7e\xec\xed\xed\xbd\x92\x2e\x18\xa6\x74\x7a\x05\x48\
+\x0a\x08\x90\xa6\x38\x73\xd1\xbb\x14\x9d\xf3\xee\xda\xc9\xb8\xeb\
+\x84\xe6\x3f\xdf\xe2\x48\x5c\x82\xe0\xee\xf7\x56\x0d\x8b\x00\x85\
+\xca\xf8\xbd\xd5\x18\x0c\x67\x61\xa1\x19\x78\xa4\x39\xc2\x50\x74\
+\x34\xf5\xe7\x2a\x07\xf3\xf7\x25\xa1\xe7\x23\x3b\xd0\xdd\x45\x57\
+\x82\x1d\x66\x37\x5e\x61\x0f\x5a\x5b\xc1\xff\x01\xfa\x00\x78\x18\
+\x52\x91\xfc\x00\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\
+\x00\x00\x03\xb2\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x2f\x49\x44\
+\x41\x54\x38\x8d\x55\x93\x4d\x4f\x5c\x65\x00\x85\x9f\xf7\xde\x3b\
+\xc3\x0c\x30\xcc\x0c\x0c\x84\x8f\xb6\xa1\xa4\x14\x17\x46\xa5\x96\
+\xe0\xca\x44\x8d\xac\x0c\x0b\x93\xa6\x46\xd3\x84\x95\x4b\xe3\x1f\
+\xe8\x82\x7f\x30\x1d\x93\x71\x23\x76\xdb\x9d\x2e\x34\x96\x26\xb5\
+\x4d\xa5\x69\xc7\x82\xb4\x26\x16\xa9\x14\x2b\xd3\xa1\x4e\xe8\x50\
+\x98\xcb\x9d\xb9\xf7\xbe\x5f\x2e\xa4\x09\x3d\xc9\xd9\x9d\xe7\xec\
+\x1e\x61\xad\xe5\x68\xe6\xe7\xe7\xbd\x4c\x26\x73\x36\x8e\xe3\x33\
+\x8e\xd6\xd3\x52\x4a\xda\x4a\xad\xc4\x71\xbc\x1a\x86\xe1\xaf\xa5\
+\x52\x29\x3a\xba\x17\x2f\x0f\x84\x10\xa2\x5c\x2e\x9f\xee\x52\xea\
+\xca\xeb\xbd\x03\x6f\xa5\x7b\xfb\x49\xe7\x72\x9c\x78\x5a\xa5\x59\
+\xad\xb2\xb5\xfd\xd4\xfe\x10\x1e\xfc\x51\xb3\xf6\x42\xb9\x5c\x7e\
+\x60\x0f\x41\x61\xad\x45\x08\x21\x8a\xc5\xe2\x85\x37\x7b\xb2\x0b\
+\x85\xae\x7e\x2f\xab\x1d\x12\x5d\x69\x82\x38\x66\x40\x2b\x3a\x83\
+\x16\x3b\x8d\x17\xf4\xfd\xfd\x88\xef\x83\xe7\x72\x31\xe1\x7e\xb9\
+\xb0\xb0\xf0\xb5\xb5\xd6\x7a\x00\xe5\x72\xf9\xf4\x84\x97\xf8\x66\
+\xc0\xcd\x79\x72\x67\x8f\x1d\xa5\x29\xf4\xf7\xb3\xf1\x68\x1d\x5c\
+\x87\x51\x2f\x41\x3d\x08\xe8\x37\x1e\xe7\x64\x2a\xa1\xda\xbb\x97\
+\x0e\xce\x9f\xbf\x07\xdc\x73\x85\x10\xde\x60\x3a\xbd\x38\x92\x1f\
+\x1e\xf1\x42\x89\xe7\x7a\x78\xae\x8b\x31\x86\x27\x9b\x9b\xe4\x9b\
+\x3e\x83\xb1\x64\xb0\xe9\x23\xda\x6d\xae\x5a\x4b\x66\x7c\xdc\x69\
+\x04\x7b\xef\x7d\x75\xf9\xf2\x82\x93\xcd\x66\xa7\x5e\x4b\x75\x4e\
+\x9a\xbd\x80\x6a\xad\x86\x54\x0a\x83\x45\x2a\x49\xb3\xd9\x24\xda\
+\xdb\x03\xdf\x87\x56\xc0\x76\x4f\x0f\xcd\x42\x1f\xb6\x33\xc7\x3b\
+\xf9\xbe\x53\xae\xeb\xce\x78\x4a\xa9\x29\xd9\x93\xc3\xd9\x8f\xc9\
+\xe4\x73\x28\x2c\x42\x1b\x30\x96\xa0\xdd\x22\x8e\x22\xe2\x8e\x24\
+\x8f\x47\x47\xd9\xe9\xee\xa2\x60\x34\x7e\xbb\x4d\x34\x74\x0c\xbd\
+\xb9\x71\xc6\x71\xa5\x9c\x56\xae\x47\x68\x14\x22\xe9\x11\x1b\x7d\
+\x58\xc5\xf3\x17\xbb\x94\xd7\x1e\xf2\x79\xe5\x2e\xdf\xde\xbc\x41\
+\xac\x35\xfb\xc1\x01\x3f\x5e\xbb\x4a\x20\x15\x1d\x4a\x4d\x79\x5a\
+\x6b\x4f\x1a\xcd\xd6\x76\x95\x6c\xaa\x8b\x42\x36\x07\x56\x83\x35\
+\xbc\x3f\x33\x83\xb5\x96\x58\xc6\xc4\x4a\x12\x63\x70\x52\x49\xde\
+\xfd\xf0\x03\xe2\x8e\x24\xda\x18\xcf\x09\xb4\xae\x18\x0c\x23\x63\
+\x27\xc9\x14\x7a\x89\x85\xfd\xbf\x58\xb4\x2b\x50\x2e\xd0\x91\xc0\
+\x4d\xa7\x88\xac\xe6\xd9\x6e\x83\x46\xd0\xc4\xca\x08\x5f\xeb\x65\
+\x0f\x58\x76\x6b\x55\xc2\x81\x61\x9e\x6c\xfe\x43\x6f\x77\x0f\x99\
+\x54\x1a\x84\xc5\x3a\x16\xad\x0d\x06\x8b\xc2\x20\x85\x61\xa3\xb6\
+\x45\x68\x35\xd3\xbb\x49\xa4\x94\xbf\x79\x8d\x46\xa3\x72\x3f\x8a\
+\xd6\x4f\x0e\x0d\x4f\x44\x0e\xa8\xa4\x4b\xec\x39\x58\xa5\x31\xc6\
+\x62\x1c\xd0\xc6\xe0\x47\x2d\xfc\x56\xc0\xc4\xe4\x1b\xc4\x8e\xe5\
+\xf6\xcf\xd7\x9f\x85\x61\xb8\xe8\x94\x4a\xa5\x68\xab\xdd\xfe\x44\
+\xfe\xb9\xa6\x87\xc7\xc7\xf0\xb2\xdd\xdc\xf9\x7d\x95\xb5\xad\x4d\
+\x22\x4f\xf0\x57\xad\x4a\x5b\x58\x1e\xff\x5b\xe3\xa7\x5b\x37\x08\
+\x5d\xd0\x77\xef\x98\x87\xbe\xff\x69\xa5\x52\xf1\x1d\x80\x62\xb1\
+\xf8\xe0\x66\xbd\xfe\x85\xba\xbf\xaa\x95\xb0\xf4\x9e\x18\x26\x33\
+\x34\x40\x20\x0c\xd7\x2b\xb7\xa9\xb7\x7c\x8e\x4d\x9c\xe2\xa3\x73\
+\x1f\x63\x57\x96\xcd\x77\xf5\xfa\xc5\xa5\xa5\xa5\x5b\xd6\x5a\xfb\
+\x8a\x4c\x73\x73\x73\x67\x47\x12\x89\x2b\x93\xf9\xfc\x58\x70\xfc\
+\x38\xed\x44\x02\xa3\x14\x6e\xab\x45\x7a\x7d\x9d\x5f\x6a\xb5\xed\
+\xb5\x83\x83\xcf\x0e\x61\xf3\x8a\x8d\x2f\x33\x3b\x3b\xdb\x99\x4e\
+\xa7\x67\xb4\xd6\x6f\x27\x94\x9a\x92\x51\xe4\xf8\xc6\x2c\x2b\xa5\
+\x56\xf7\xf7\xf7\xaf\xad\xac\xac\x34\xed\x11\xe8\x3f\xd1\x06\xe4\
+\x0c\x33\xbb\x13\x33\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
+\x82\
+\x00\x00\x06\x38\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x05\xff\x49\x44\x41\x54\x78\x5e\xed\x9b\x5f\x4c\x5b\x55\
+\x1c\xc7\xbf\xa7\xa5\xff\x5b\x68\x81\x6d\x94\x3f\x42\x23\x2c\x21\
+\x01\x2d\x1b\x99\x89\x89\xa3\x18\xcc\x5e\x5c\x7c\x31\x7b\x32\x71\
+\x1a\x31\xf1\xc1\xb8\x27\xdf\x9c\xf3\xc5\xc4\x18\x83\xc6\xf8\x0f\
+\x8c\xd9\xb2\x2c\x84\x28\x31\x64\x71\x8b\xd9\x14\x16\x32\xb7\x45\
+\x58\x41\x14\x90\x75\xa3\x8c\x8c\xc1\x80\x42\xa1\xb4\xa5\x14\x8e\
+\x39\x85\x42\x0b\xac\xf7\xf6\xd2\x5b\xae\xe3\x9e\xa4\x0f\x6d\xcf\
+\xfd\x9d\xf3\xfd\x9c\x73\x7f\xe7\xdb\x73\x7a\x09\xf6\x78\x21\x7b\
+\x5c\x3f\x64\x00\xf2\x0c\xd8\xe3\x04\x24\x73\x0b\xd0\xce\xb7\x1c\
+\xa0\xc8\x02\x55\xd8\x63\xc6\xc4\x0c\x4a\x37\xde\x13\xd2\xb1\xf1\
+\x1d\x9d\x05\x41\x0f\x96\x95\x23\xa4\xf6\x5b\xb7\xd0\x71\xdc\x15\
+\x00\xb4\xfd\xa4\x19\x0a\xf5\x2b\xa0\xd4\x01\xc0\x0e\x42\x62\x45\
+\x0b\xd3\x42\x69\x07\x08\xe9\xc1\x0a\x3d\x47\x6a\x9b\x7a\xf8\x06\
+\x49\x2b\x80\x88\x70\xa2\x6a\x00\x21\x27\xf9\x76\x50\x50\x3d\x0a\
+\x37\x08\xce\x90\x9a\xc6\x73\x5c\xd7\xa7\x0d\xc0\x9a\xf8\xf6\x94\
+\x8c\x36\x97\xaa\xf5\xef\xe9\x17\xa4\xa6\xe9\x54\xa2\xea\xe9\x03\
+\x70\xf5\xf8\xcb\x50\x59\x2f\xf2\xee\x7b\x2a\x2a\x86\xe6\xe7\xc9\
+\x4b\xcd\x99\xd2\x00\xf0\x0d\x1c\x30\x14\x36\xa1\xa8\xae\x14\x0a\
+\x75\x2a\xe4\x25\x8e\x11\x98\xf2\xe1\xfe\x25\x0f\x79\x3b\x58\x2c\
+\x1d\x00\x40\x3b\x88\x72\x1c\xd9\x95\x53\xd8\x67\xaf\x10\x05\x84\
+\xff\xd1\x43\x4c\xdc\x98\x40\x60\x22\x92\x58\xc9\x3b\x89\xcd\x5e\
+\xfa\x6e\x81\x9f\x0e\x7f\x84\xc9\xee\xd3\x71\xa3\xa1\xb7\x02\x86\
+\x7c\xc0\x60\x05\x54\xa6\xd5\x57\xb2\x25\x38\x05\x04\x3d\x40\x60\
+\x1a\xf0\xb9\x81\xa5\xf9\xb8\x08\xd2\x01\xf0\xf3\x0b\xef\x63\xbc\
+\xf3\x13\x4e\x7d\x11\x10\x46\xce\x6a\xf0\x3f\xe4\xae\x93\x61\x04\
+\xa9\xf7\x25\x1c\xe4\xf4\xcd\x80\x6b\xf5\x27\xe0\xbd\xd3\x82\x87\
+\x7f\x00\x2b\x21\xee\xce\xef\xb4\x86\x26\x07\x28\x70\x80\x1c\x6b\
+\x95\x10\x00\x90\x96\xc8\x14\x9d\xee\x03\x66\x87\xc4\x01\xc1\x84\
+\xe7\x56\x02\x59\x07\x23\x08\x49\x4d\xa3\xc4\x00\xc4\x8e\x2c\x9b\
+\xc6\x0b\x63\x80\x6f\x6c\xf5\xde\x0d\xfb\x92\x1f\x77\x26\x58\x9b\
+\x03\xe8\x72\x00\x53\xc9\x96\x3c\x22\x1d\x00\x57\x5e\x7d\x03\xea\
+\xec\x1f\x38\x15\x32\x10\x4b\x3c\x40\xb0\x04\xca\x55\x96\xe6\x41\
+\xea\x9a\x25\x32\x03\xd8\x2a\x10\x9a\x3f\x0d\xeb\xf3\x10\x65\xf9\
+\xdb\x0c\x23\x38\x0d\x3c\xe8\x00\x79\x73\x5a\x42\x00\xd8\x32\xc8\
+\xb2\x7c\x4e\x25\x60\x3e\x28\x0e\x08\x26\x9c\xe5\x18\xef\xd0\x6a\
+\x0e\x90\xb6\x0f\x58\xf3\x00\xcc\x0b\xb0\xa5\x4f\x90\x0f\x98\x06\
+\x98\xe8\x27\xcb\x07\xf0\x30\x44\xfe\x31\xae\x0c\x00\xc8\x3e\x40\
+\xf6\x01\xb2\x0f\x90\x7d\x80\xec\x03\x64\x1f\x20\x31\x23\x14\xbb\
+\x78\xe9\x65\x1f\xb0\x75\x2d\xe7\xbb\x31\x22\xfb\x00\x0e\x1f\x24\
+\xef\x07\xc8\xfb\x01\xe2\x6f\x88\x38\x07\x86\x4b\x28\x50\x4c\xc8\
+\x8a\x1d\x14\xe6\x8d\x49\x19\x39\xf2\x5a\x2d\x94\xf6\x80\x28\x66\
+\x4d\x9e\xae\xa3\xda\xe0\xc8\x8b\x7a\xea\x81\x92\x86\x91\xa9\x9e\
+\xdb\x7e\x0e\x4b\x79\x3f\xc0\x39\x34\x6c\x47\x38\xfc\xfa\xda\xe9\
+\xce\x86\x48\xee\x9f\x25\xdb\xd6\xd0\x7a\xff\x85\xc6\x7f\x1f\xc6\
+\xe0\x3d\x98\xe9\x03\xa8\x8c\x3c\x36\x43\xf9\xb6\x95\xca\xfd\x00\
+\xe7\x80\x8b\x9d\xe5\x7d\x08\xa0\x84\x6f\xfb\x42\xea\x65\x8e\xb4\
+\xc1\x3a\xf6\x23\xb4\x56\x1b\x88\x9e\xc7\x2f\xc2\xed\x1a\x49\xf5\
+\x7e\x80\xb3\xdf\xf5\x39\x08\xde\x13\x22\x48\xc8\x35\x1a\x4f\x3f\
+\x0a\x7e\x7b\x0d\x9a\xdc\x3c\xa8\x6d\x87\xb9\x41\x30\xc1\x62\xee\
+\x07\x5c\xef\xe9\x9f\xd3\x6b\xd4\x02\x87\x43\x08\x02\x40\xd5\xf6\
+\x31\x74\x0f\x7a\x23\x17\x5b\xf2\xbc\xb0\x14\xfa\xb7\x06\x4a\x97\
+\x0f\xf8\xaa\xa5\x6d\xa2\xf2\x69\x9b\xc9\x64\xd0\xeb\x84\xc9\xe1\
+\x7f\x55\x38\xbc\x8c\x7b\x83\x7d\xc8\xff\xeb\x0a\x72\x26\xef\xae\
+\x5f\x58\xf4\x54\x37\x8c\x96\x19\xfe\x81\x58\xcd\x54\xf9\x80\x86\
+\xf3\xad\x94\xc5\xcb\xcb\xc9\x46\xc1\xfe\x5c\x98\x0c\xa9\xe7\xc0\
+\x84\x8f\x4f\x7b\xe0\x1e\x9b\x40\x78\x79\x19\x47\xbc\x4e\x58\xfe\
+\xbe\xb9\x2e\x38\x43\x19\x40\x59\xc5\x75\x7e\x00\x52\x7d\x2e\x10\
+\x05\x10\x6d\xdd\xa8\xd7\xc1\x6c\x34\xc2\x6c\x32\x42\xab\x51\x81\
+\xbd\x4f\xb6\x04\x42\x21\x2c\x2e\x86\x30\x3b\xef\x5b\x7b\x2d\xc4\
+\x85\x78\x6e\xee\x36\xcc\x7d\xb7\xe2\x3e\xcb\xb7\xf6\x22\x6b\xff\
+\xe4\xd6\xa6\xc4\x3e\x17\xf8\xba\xe5\x22\x5d\x0c\x71\x1f\x65\x99\
+\x4d\x06\x4e\x0e\xc1\xc5\x25\x04\x79\xc4\xaa\x0b\x5e\x46\xbe\xab\
+\x35\x2e\x9e\xa6\x20\x0f\x86\xb2\xd2\x8d\xcf\xd2\x75\x2e\xd0\xd9\
+\xdd\x47\x07\xdc\xa3\x58\xf0\x07\x38\x05\xee\xb4\x82\x52\xa9\x40\
+\x59\x51\x01\x0e\x8d\x36\xc0\xd0\xdf\x1c\x17\x4e\xb9\xaf\x18\x5a\
+\xfb\x31\xfe\x4d\xa4\xca\x07\x38\x07\x5c\x91\x1c\x30\x3e\xe5\xc1\
+\xe8\xa3\x29\x51\x40\x30\xe1\x79\xb9\xd9\xb0\x59\xf3\x90\x91\xa1\
+\x44\xbe\xf3\x83\x2d\x00\x14\x16\x2b\x74\xd5\xc7\xb9\x01\xa4\xdc\
+\x07\xac\x01\x88\xb6\xec\xf3\x07\x30\x3b\xe7\xc3\x8c\xcf\x87\x60\
+\x68\x49\x10\x10\x8d\x5a\x0d\xad\x5a\x0d\x4b\x26\xcb\x25\x86\x48\
+\x3e\x89\x2d\x49\x01\x10\xdb\x07\xdc\xe8\xed\xa7\xac\xb3\x5c\x85\
+\x25\x34\xae\xa2\xd5\xac\x0a\xe7\x2a\xba\xdf\x3f\x85\x6a\xb0\x2b\
+\x3e\x07\x68\x16\x50\x50\x19\xf3\xfb\x21\x5d\x3e\xa0\xb1\xf5\x12\
+\x2d\x2f\x29\x12\x94\xed\xb9\x84\x6e\xfe\x9e\x2d\x87\xae\xd1\x31\
+\x14\x0f\x5f\xdd\xb2\x0a\x68\xd5\x33\xb0\x95\x77\xf3\x0f\x29\x86\
+\x0f\x28\x3c\x90\x2b\x0a\x88\xcd\x3e\x60\xbb\x65\x90\x37\x80\xff\
+\x83\x0f\x60\x4b\x61\x30\x81\x0f\x38\x12\xbe\x07\xcb\xad\x5f\xe3\
+\x46\xfb\xb1\x00\x9e\x44\x1f\x50\xab\x1d\x40\x51\xd7\x67\x71\x00\
+\x14\x46\x03\xcc\xd5\x55\xc9\xf9\x00\x4a\xff\x21\x8e\xa6\x8a\x44\
+\xf7\x0d\xe7\x7f\x84\x76\xc3\x07\x3c\xf3\xe8\x3c\xb2\x9c\x5f\xc6\
+\x03\xe0\xbb\x0c\x46\xaf\xa2\xe8\x02\x59\x39\x41\x6a\xbe\x1f\xde\
+\x11\x80\xdd\xf0\x01\x45\x37\xdf\x85\xf6\xee\x65\x61\x00\x28\x6d\
+\x86\x82\x5c\x20\x47\x1b\x7f\xe1\x93\x31\x39\x67\x40\x14\x40\x3a\
+\x7d\x40\x72\x00\x28\x1b\xe1\x3f\x01\x5c\x80\x81\x5c\x23\xd5\x8d\
+\x5e\x3e\xc2\xa3\x75\x38\x01\xdc\xee\x77\xb9\x09\x41\xc2\xbf\x9b\
+\xb2\x60\xa9\xf4\x01\x07\x5c\xdf\xd5\x67\x8f\xb6\x29\xa0\x54\xea\
+\xa3\x1d\x55\xa8\xf4\x3e\x55\xe9\xa1\x3b\x58\x26\x13\xa4\xb6\x71\
+\x30\x19\x91\x3b\xbb\x05\x86\x86\xed\x74\x39\x7c\x96\x80\x3c\x9b\
+\xaa\x46\x1f\x17\x87\x02\x5e\x02\x9c\xaa\x2a\x2f\x3d\x2b\x76\x5b\
+\xbc\x67\x40\xb4\x22\xdb\x17\xa4\xa0\xa7\xc4\x00\x11\x11\x4e\x71\
+\x16\x3a\xe5\x99\x2a\x9b\x6d\x36\x5d\xe2\x59\x3b\x9c\xb7\xc0\xe6\
+\xce\xac\xee\x0c\xaf\x38\x00\xea\xa0\x84\x96\x08\x01\x42\x29\x46\
+\x08\xa1\xec\x31\x17\xf6\x08\x4c\x47\x55\x79\x59\xcc\xa3\x30\xe9\
+\x94\x2f\x00\xc0\xe3\xba\xe7\x1c\xb8\xc3\x63\x9b\x3c\xc3\x5d\x55\
+\x6e\x13\xfc\x7c\x8f\x18\x68\x92\x9e\x01\x62\x74\x62\x37\x63\xca\
+\x00\x76\x93\xbe\x14\xda\x96\x67\x80\x14\x46\x61\x37\xfb\xb0\xe7\
+\x67\xc0\x7f\x0a\xee\x04\x6e\x55\x4b\xfa\xff\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x0a\xce\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x33\x00\x37\x00\x45\x38\x50\
+\x3f\x23\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe0\x08\x1f\x09\x0a\x0b\x80\x76\xb7\xa7\x00\x00\x0a\x5b\x49\x44\
+\x41\x54\x78\xda\xed\x9c\x7b\x50\x54\xf7\x15\xc7\xbf\xbf\x7d\xdc\
+\x1d\x5e\x75\x71\xf0\x51\x7c\x14\x07\x6b\x43\xa5\x3e\x66\xaa\xf1\
+\x01\xce\x34\xd5\x99\x68\x52\x8d\xd6\x3f\x6a\x54\x5a\x63\x51\xd1\
+\x68\x07\x8d\xe8\x20\x8e\x0f\x06\x15\x62\x0c\x86\xa6\x63\xa3\xad\
+\x2f\x32\x69\x93\xd8\xfe\x91\x8e\xc4\x47\xd3\x26\x22\x46\x8d\x14\
+\x68\x44\x25\x12\x9e\x9d\x38\x80\x42\x07\x76\xc3\x3e\xee\xfd\xf5\
+\x0f\xb8\xeb\xdd\x7b\x7f\x77\x77\xef\xdd\x85\xf2\xd8\x33\xe3\x08\
+\xbb\xec\xfe\x76\x3f\xf7\xfc\xce\xf9\x9e\xf3\x3b\xbb\x84\x52\x4a\
+\x11\x36\xdd\x66\x08\x23\x08\x03\x0c\x03\x0c\x03\x1c\xc6\x66\x52\
+\xbb\xe3\xf1\xe3\xc7\xb8\x51\x56\x86\xea\xbb\x77\xf1\xaf\xf2\x72\
+\x34\x36\x36\xc1\xd1\xdd\x0d\x10\x20\x2e\x6e\x14\xa6\x4f\x9f\x8e\
+\x94\x05\xa9\x58\xb8\x68\x11\x22\x22\x22\x86\x2d\x40\xa2\x96\x85\
+\x17\xfd\xe4\x39\xd4\xd5\xd7\x01\xf2\x7b\x09\xbc\x6e\x7b\x26\xe9\
+\x19\xec\xce\xde\x83\x94\xd4\x94\xf0\x16\x96\x5a\x4b\x4b\xcb\x53\
+\x60\x52\x78\x32\xbb\x7f\xff\x3e\xd2\x5f\x79\x05\x9f\xfc\xfd\x93\
+\x30\x40\xa9\x99\x39\xb3\x12\x1c\x95\x78\x9f\xe4\x36\x97\xdb\x85\
+\xec\xdd\xbb\xf0\xe4\xc9\x93\x30\x40\x2f\xa3\x32\x68\xf2\x6d\x4c\
+\x9e\xfe\xdc\xd6\xda\x86\xab\x97\xaf\x84\x01\x7a\xc1\xf3\x75\x1f\
+\x51\xfe\xdd\xf5\xd2\xd2\x30\x40\xf5\x74\x23\xf9\x07\x76\x5c\xac\
+\xa9\xa9\x01\xcf\xf3\x61\x80\x4c\x40\x54\xc5\xfb\x24\xbf\xdb\xba\
+\xba\xe0\x74\x38\xc2\x00\x01\xc0\x66\xb3\xa9\x6f\x5d\xea\xc3\x43\
+\xc3\x42\xba\xc7\x0e\xe5\x1f\x41\x63\x43\x03\x2e\x5f\xba\x84\x07\
+\xf7\x1f\x28\x63\x23\x85\xef\xdb\x86\xbb\x90\x16\xcd\xe1\x70\xe0\
+\xe4\xef\xdf\x41\xe1\xb1\x63\xea\x5b\xbc\xf7\x19\xe2\xe3\xe3\x71\
+\xe9\xea\x15\x44\x44\x46\x86\xb7\xb0\x68\x16\x8b\x05\xaf\x6e\xdb\
+\x8a\x69\xd3\xa7\xa9\x8b\xea\x61\xb8\x75\x35\x67\xe1\xf8\x71\xe3\
+\x86\x3d\xac\xa0\x00\x0a\xbc\xc0\xd6\x89\xc3\xbc\x9f\x6d\xd2\xf4\
+\xd7\xd4\x87\xb4\x09\x7b\xa0\x06\x41\x1d\xf6\xbc\x20\x00\xaa\xd5\
+\xc6\xc3\x34\x3e\x9a\x74\x79\x20\x95\xc1\xf2\x25\xb0\xc3\x1e\x88\
+\xc0\x92\x07\x0d\x6f\x61\x6d\x41\x30\x2c\x67\x34\x02\xf4\xc4\x39\
+\xea\x3b\x1e\x86\x63\xa0\x1f\x82\xac\xae\xcc\x30\xde\xc2\x81\x03\
+\x94\x97\xcc\x44\x45\x1b\x86\x01\xea\x48\x28\x7d\xe8\x81\xf7\xee\
+\xdd\xc3\xe7\x65\x65\x00\x21\xaa\x17\xf6\x3b\x23\x46\xe0\xe7\x2b\
+\x57\x0e\x70\x80\x44\x26\x61\x28\x43\xda\x84\xd8\xbe\xaa\xa9\xc1\
+\xba\xb5\x69\x68\x6b\x6b\x63\x7b\x7e\xef\xba\x71\x71\x71\xff\x37\
+\x80\x06\x5d\x1e\x47\xe1\xb3\xb5\x1f\x0a\x7b\xf4\xe8\x11\x32\x36\
+\x6e\x44\xdb\xe3\x36\xe5\x85\x92\x5d\xc4\xa8\xe8\xa8\x41\x58\xca\
+\xb1\xe0\x86\x08\xa2\xdd\x66\xc7\x96\x4d\x19\xa8\xaf\xab\x67\x87\
+\x89\x01\x24\xda\x4d\xba\xe1\xf5\x51\xfc\x73\xbb\xdd\xd8\xb2\x39\
+\x03\x95\x15\x15\xbe\x2f\xdc\x00\x81\xa8\xaf\x1b\x13\x08\x54\x9d\
+\x96\xbb\xff\x00\xae\x7d\xfa\x99\x7a\xcc\xc3\xc0\xca\xfe\xc1\x6f\
+\x61\x0a\xc5\xe9\x9c\x5e\x7b\xbb\xe8\xb7\x78\xb7\xb8\x58\x5b\xa6\
+\xa7\x83\x05\xa0\x3c\xfb\x86\xf8\xca\xff\xf9\xbd\x3f\xe1\xcd\x63\
+\x6f\x78\x67\x7c\xf9\xda\x84\x7d\xb1\xba\xbb\x1d\x83\x6c\x0b\xb3\
+\x40\x06\xe1\x09\x57\x2e\x5f\xc6\xbe\xbd\x7b\xd9\x8d\x0a\x86\x60\
+\x9f\x94\x30\x09\x71\xa3\xe2\x20\x9e\x87\x8d\x19\x33\x76\x90\x09\
+\x69\xaa\x92\x7d\x75\x40\xac\xaa\xac\xc2\x8e\xcc\x4c\xb8\x79\x37\
+\x3b\x39\x50\x6f\x8f\x9c\x9a\x9c\x8c\x33\x67\xcf\x21\x76\x64\xec\
+\x20\xec\xc6\x84\x78\xdb\x36\xd4\x37\xe0\x37\x5b\xb7\xc2\x6e\xb3\
+\xfb\x9f\xc5\xa1\x3d\x9e\xf7\xbb\x13\x27\x06\x0c\x3c\x7d\x42\x9a\
+\x84\x26\x90\xb7\xb7\xb7\x63\xf3\xa6\x8d\x68\x6a\x6c\x0c\x68\x8d\
+\xd8\x91\xb1\x78\xfb\xc4\x09\x8c\x13\x4f\x07\x07\x25\x40\x56\x69\
+\xc7\x0a\xfa\x7e\xac\xbb\xbb\x1b\xdb\xb6\xbc\xea\x3d\xf1\xe0\x03\
+\x22\xc7\x71\x28\x7c\xab\x08\x53\x7e\x30\x65\x08\x34\x13\x7c\xc9\
+\x8a\x00\x20\x0a\x82\x80\xdd\x3b\xb3\x70\xa3\xac\x2c\xb0\xae\x0e\
+\x01\xf2\x8e\x1c\xc6\xfc\x94\xf9\x83\xbc\x1b\x43\xfc\x64\x64\xcf\
+\xff\xbe\x29\xe6\xe5\xe6\xe2\x6f\x1f\x7d\x14\xb0\x58\xdf\xb4\x29\
+\x03\xcb\x57\xac\x08\xea\x4d\x3a\x9d\x4e\x00\x80\xd1\x68\x04\x21\
+\x04\x44\xf2\x1a\x29\xa5\xe0\x79\x01\x2e\x97\x0b\x46\xa3\x11\x16\
+\x0b\xd7\x47\x00\xd5\xbc\x4d\x22\x3d\x08\x21\xb0\x58\x2c\xaa\x4f\
+\x71\xe6\x8f\xa7\x71\xf6\xf4\x19\xf5\x72\x4c\x26\x8f\x96\xaf\x58\
+\x81\xd7\x76\x65\xe9\x82\xc6\xf3\x3c\x5c\x2e\x37\x2c\x16\x0e\x1c\
+\xc7\x86\x42\x29\x05\x21\x04\x26\x93\x11\x26\x93\xd1\x73\x9b\xe7\
+\xe5\x10\xd2\x0f\x1e\x28\xb1\xb6\xb6\x36\xac\x59\xf5\x32\x8c\x46\
+\x23\xf3\x0d\x95\xdf\xb9\xa3\x1e\xeb\x64\x1e\x9d\x92\xba\x00\x79\
+\x87\x0f\xeb\x82\xe7\x70\x38\x60\xb1\x58\x60\x34\x1a\x41\x29\x85\
+\x7c\x7e\x4a\xfc\x9d\x35\x57\x45\x08\xf1\x3c\xc6\x60\x30\xc0\x60\
+\x30\x84\xb8\x99\xe0\x23\xe3\x3a\x9c\x0e\xdc\xba\x79\x53\x3d\xb6\
+\xa9\x75\x53\x64\xcf\x99\x94\x94\x84\xc2\xe3\x85\xaa\x9e\xe3\x6b\
+\xab\x9a\x4c\x26\xcf\x2e\x10\x01\x09\x82\x00\x41\x10\xa0\xf5\x73\
+\x95\x82\x20\xf4\x7a\xa8\x49\xd5\x1b\x4d\x41\x47\x51\xb5\x09\x7e\
+\x5f\x95\x0a\x95\x95\x65\x92\xdb\x47\x8f\x19\x8d\x13\xa7\x4e\xc2\
+\x1a\x1b\xab\x19\x9e\xd9\x6c\x56\x80\x0b\x76\xe4\x98\x52\x0a\x97\
+\xcb\x05\xb3\xd9\xcc\x84\xa8\x2d\x06\x52\xa8\x8f\xfc\x12\x04\x36\
+\x2b\xe3\xc3\x8b\x23\x22\x23\x71\xbc\xa8\x48\xb3\xd6\xa3\x94\x82\
+\xe3\x38\x4f\x4c\x13\x3d\x4e\x10\x84\xde\x97\x43\x51\x6a\xab\x44\
+\x49\x67\x19\x6e\xd8\xaa\xf0\xd0\xd9\x8c\x0e\xbe\x13\x00\x60\x35\
+\xc6\x60\x32\x37\x1e\x73\xa3\xa6\x61\x71\xcc\x3c\xa4\x44\x4d\x07\
+\x61\xc8\x09\x35\x88\xc1\x97\x72\x81\x78\x26\x0b\x9e\xcc\x43\x0d\
+\xc4\x80\x23\x05\xf9\x98\x35\x7b\xb6\x66\x78\xac\xed\x2a\x08\x02\
+\x04\x08\x28\x6e\x2f\x41\x41\xeb\x79\x7c\xe5\x68\x64\x3e\xbe\xc5\
+\xfd\x04\x2d\xee\x27\x28\xb3\x57\xe1\x8d\xd6\x62\x4c\xb6\x4c\x40\
+\xd6\xa8\x34\xac\x8d\x5d\x0c\x83\x4c\x26\xf3\x3c\xef\xc9\xe4\xda\
+\x85\x34\x51\x11\xd2\x24\x80\x06\x80\x1f\xc0\x04\x04\xd9\x39\x39\
+\x78\xe1\xc5\x17\x75\xc1\x13\xc1\xb9\x5c\x2e\xb8\xdd\x6e\x08\x82\
+\x80\x87\x8e\x26\x2c\xa8\xdd\x80\xf4\xe6\x3c\x55\x78\x2c\x7b\xe8\
+\x68\xc2\x86\xe6\x3c\x2c\xa8\xdd\x80\x06\xe7\x37\x8a\xf5\x78\x9e\
+\xf7\x5a\xd7\xa0\xdb\xeb\x68\x80\xf1\xd0\x5f\xa5\xd2\x9b\xf5\x5c\
+\x2e\x97\xae\xf8\x24\xf5\x3a\xd1\xae\x76\xdd\xc2\x9c\x87\xeb\x70\
+\xdb\x5e\xad\x3b\xf6\xdd\xb6\x57\x63\x7e\xed\x7a\x94\xda\x2a\xbd\
+\xd6\x93\xaf\xa5\xcf\x03\x7d\x7d\xf4\x21\xd0\xa6\x83\xe7\xb1\x3d\
+\x3f\x1c\x2d\x28\xc0\xf5\xd2\xeb\x9a\xe0\x19\x0c\x06\xf0\x3c\xef\
+\x95\x61\xaf\xd9\x2a\xb0\xbc\x7e\x27\x3a\x05\x7b\xd0\xf9\xb1\xd5\
+\xdd\x81\xc5\x75\xdb\xf0\xcf\xae\x3b\x5e\x32\x47\xba\x9e\xf6\x86\
+\x2a\x08\xbb\xec\xf2\x37\xe6\x41\x7d\x7b\x23\x2f\xf0\xd8\xb9\x63\
+\x3b\xea\xea\xea\x34\x41\x94\xea\xbc\x06\xe7\x37\x58\xd5\x98\x0d\
+\x27\x75\x21\x54\xe6\xa4\x2e\xac\x6c\xd8\xe5\x09\x03\x62\xa2\xd2\
+\x0e\x90\x48\x3c\x86\x68\x6c\x79\x51\x1f\x90\x25\xde\xdc\xd2\xd2\
+\x82\xd7\x32\xb7\xc3\xed\x76\x07\x9c\x38\x3c\xdb\x18\x02\x56\x37\
+\xed\x45\xab\xbb\x23\xe4\xf5\x6e\xa7\x60\xc7\xba\xe6\x83\x10\x20\
+\x28\x74\xa5\xbe\x76\x16\xf5\x11\xeb\xa8\x06\xa0\x8c\xea\xa6\xb2\
+\xa2\x02\x47\x0b\x0a\x02\x16\xba\xa2\x9d\x6f\x2f\x09\x2a\xe6\x05\
+\x12\x13\xcf\xb5\x5f\xf4\x5a\x5b\x10\x04\x8d\x3a\xd0\x4f\x6c\xb3\
+\x8e\xb0\x62\x63\x46\x06\x4c\x66\xe5\xd3\x52\x41\x40\x71\x71\x31\
+\x1a\x1b\x1a\x94\x7a\x52\x16\x0a\x4e\xbd\x73\x12\x33\x66\xce\xc4\
+\xf3\x8b\x17\xab\x56\x07\x62\x40\x17\x75\x5e\x7e\xcb\xd9\x3e\xef\
+\xbc\x14\xb4\x9c\xc3\x2f\x63\x5f\xf0\xe8\x44\x4a\x69\x10\xed\x2c\
+\xc6\x98\x47\x74\x4c\x34\xd2\x37\x6e\x50\x7d\xe8\xd4\xe4\x64\xa4\
+\xad\x5e\xd3\x53\x1d\x50\xdf\xf1\x31\x27\x3b\x1b\x89\x89\x89\xf8\
+\xfe\x14\xef\x1e\xa0\x9b\xe7\xc1\x99\xcd\x5e\x52\xe2\x9a\xad\x02\
+\xb5\xce\xe6\x3e\x07\x58\xeb\x6c\xc6\x35\x5b\x05\x16\x44\xcd\xf4\
+\x24\x14\x7d\xa3\x1d\x60\x57\x1d\x02\x2f\xe0\x5b\xbb\x7a\xf6\x7b\
+\x76\xce\x1c\x64\xed\xde\xa5\xae\x25\x25\xcf\xdd\xd1\xde\x81\x1d\
+\x99\xdb\x15\xcf\xc7\xaa\x12\x3e\xee\xbc\xd1\x6f\xfd\x3f\xf9\x5a\
+\xda\xcf\x44\x08\x82\xfa\x94\xd2\xfa\xf4\x74\xfc\x6c\xe9\x52\x76\
+\x0c\x95\xd5\xd3\xd5\x77\xef\xe2\xe0\xfe\x03\x72\xd9\xa8\x10\xd1\
+\x37\xed\xff\xee\x37\x80\xb7\xec\x5f\xea\x04\x48\x08\xbb\x26\x86\
+\x76\x98\xb9\x87\xf2\x30\x35\x39\x99\x1d\x5f\x65\x75\xf5\x07\x1f\
+\xbc\x8f\x0b\x1f\x7e\xf8\xf4\x05\xf7\xb6\xa8\xa4\x56\xe3\x68\xea\
+\x37\x80\x0f\x64\x55\x8d\x06\x1d\x48\x75\xf5\x08\x59\x16\x1d\x1d\
+\x8d\xe3\x45\x45\xb0\x4a\x4f\xd7\xd4\x26\xfd\x29\xb0\x37\x7b\x8f\
+\x67\x56\x86\x75\x8d\xfe\xdb\xdb\x18\xe8\x0f\xeb\x90\xad\xa5\x4d\
+\x07\xfa\xcb\xc4\x1a\xb6\x73\xc2\xa4\x04\xe4\xe7\x17\xf4\x14\xe6\
+\xc4\x77\xc6\x77\x3a\x9d\xd8\x91\x99\x89\x8e\x8e\x0e\x0c\x24\xd3\
+\xae\x03\x59\x1a\x30\x88\x83\xf5\x9f\x2e\x5a\x88\x4d\x5b\x36\x07\
+\x54\x5f\xd7\xd7\xd5\x23\x27\x3b\x9b\xd9\x14\x1d\x61\x8c\xe9\x37\
+\x68\x56\xd9\x5a\xfa\xb3\xb0\xce\x24\x22\xb7\xad\xdb\xb6\x61\xf6\
+\xec\x67\xfd\x8b\x76\x00\x1f\x5f\x2c\x41\x57\x57\x97\x22\x89\x4c\
+\xb1\x4c\xe8\x37\x80\xf2\xb5\x42\x37\x60\xa9\x13\x26\xc7\x71\x38\
+\x56\x58\x88\xf8\xf8\x78\xdf\x99\xbf\xd7\xaa\x2a\xab\x94\xf2\x28\
+\xf2\x47\xfd\x06\x50\xbe\x96\xbe\xe9\x2c\xb5\x3e\xa0\xce\x01\xa3\
+\xb1\xdf\x1d\x8b\xfc\xa3\xaf\xc3\x64\x32\xf9\xed\x29\x96\x97\x97\
+\x2b\x1e\xff\x7c\xcc\xdc\x7e\x03\x28\x5f\x2b\x34\x1e\x18\x82\x19\
+\xbd\xb9\xf3\xe6\x61\xef\xfe\x7d\x7e\x6b\xea\x7f\x30\xbe\x62\x2a\
+\x35\x6a\x06\x12\xb9\xf1\x7d\x0e\x2f\x91\x1b\x8f\xd4\xa8\x19\x41\
+\x66\x61\x56\xe9\x15\xa2\xa1\xa3\xd5\x6b\xd6\xe0\x17\x2f\xaf\x52\
+\xae\x23\x49\x56\xd5\xd5\x5f\x7a\xca\x28\x69\x75\x92\x35\x3a\xad\
+\xcf\x01\x66\x8d\x4e\x53\x54\x42\xa1\xfb\xb0\x21\x42\x03\x32\x7b\
+\x4f\x8e\x77\xfd\x2b\x0b\x17\xbc\x9b\xc7\xb5\x4f\x3f\x53\x1c\xee\
+\xa4\xc5\x2e\xc1\xac\xc8\x1f\xf6\x19\xbc\x1f\x47\x24\x61\xad\x75\
+\x89\xac\xb6\x20\x3a\x5b\xfa\x7d\x38\x6c\x1e\x19\x15\x89\x63\xc7\
+\x0b\x11\x19\x15\xa9\x3c\xfa\xec\x5d\xf7\x0f\xa7\x4e\xf5\xbc\x78\
+\xc9\xa1\xb7\x01\x06\x14\x4f\x38\x88\x51\x26\x6b\xc8\xe1\xc5\x18\
+\x22\x71\x7a\xc2\x3e\x18\x89\x37\x2e\x83\xc1\x10\xe4\x37\x58\xb2\
+\x8e\x36\x43\x60\x49\x49\x49\xc8\xcd\xcb\x53\x7a\x61\xef\x7a\xd7\
+\x4b\x4b\x51\xf7\xf5\xd7\x0a\x2f\x4c\xe0\xe2\xf1\xde\xc4\x43\xe0\
+\x88\x39\x64\xf0\x38\x62\xc6\xfb\xdf\x3b\x8c\x29\x96\x89\x0a\xef\
+\xd3\xe4\x81\xbc\xc0\xfb\x15\xcc\xfe\x3a\xc9\x5a\x6c\xd9\x4b\x2f\
+\x61\xfd\xaf\xd3\xd9\x13\x0e\x04\xc8\xd9\xb3\x87\x79\xd0\x9d\x1a\
+\x35\x03\x7f\x4d\x78\x1d\xd1\x86\xe0\xbf\x55\x33\xce\x64\x45\xc9\
+\xa4\xb7\xf0\x5c\xf4\x2c\xc5\x7d\xa2\x62\x08\xb8\x1f\x18\x13\x13\
+\x03\xab\xd5\xca\xee\x40\xf7\xbe\x8f\x91\x23\x47\xfa\x9d\xce\xd2\
+\x14\xb4\x77\xef\x42\x7d\x7d\x1d\xee\xdc\xfe\x42\x21\x6d\x6a\xee\
+\x3f\xc0\x5f\x2e\x5c\xc0\xd2\x65\xcb\x14\x8f\x5b\x18\x3d\x1b\x9f\
+\x4f\x3e\x8d\x5f\x35\x1d\xc0\x17\xdf\xde\xd3\x1d\xf3\xde\x9d\x98\
+\x8b\x04\x2e\x9e\x09\x4f\xbc\x78\x64\x30\x7f\x11\xb7\xbf\xe3\x50\
+\x01\x02\xce\xb7\x97\xa0\xa0\xf5\x1c\x1e\x06\xd8\xb1\x99\x6c\x99\
+\x80\x9d\xa3\xd6\x22\x2d\x76\x09\x58\x11\x4e\x84\x37\x24\x00\x06\
+\x02\xb1\xc7\x61\x7b\x46\x3b\x2e\x76\x5e\xc7\x0d\x5b\x15\x6a\x9d\
+\xff\xf1\x1a\xed\x48\xe4\xc6\x61\x6e\xd4\x34\x2c\x89\x99\xaf\x3a\
+\xda\x21\x4e\x6a\xc9\xe7\x0b\xc9\x50\xf8\x2a\x78\x41\x10\x42\x1a\
+\x7f\xe5\xc9\x42\x3a\xce\x21\x8f\xbb\x64\xa8\x7c\x97\xbe\xde\xe9\
+\x06\x35\x68\x22\x28\x51\x2a\xc9\x3d\x6f\xc8\x01\x14\x21\xba\xdd\
+\x6e\x04\xf3\x96\xa4\x9e\xc6\xda\xb2\x43\x1a\x60\x30\x5b\x5a\x9c\
+\xaa\x15\x0f\xeb\x8d\x46\xa3\xdf\xe9\xd4\x21\x0b\x50\x0a\x22\x50\
+\x90\x22\xac\x40\xc6\x7a\xa5\xf6\x3f\x58\x35\xa1\x3c\x7b\xdf\x22\
+\x13\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x01\x3a\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
+\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
+\x79\x71\xc9\x65\x3c\x00\x00\x00\xdc\x49\x44\x41\x54\x78\xda\x62\
+\xfc\xff\xff\x3f\x03\x25\x80\x11\x08\x40\x74\x23\x10\x6b\xa3\xc9\
+\x85\x00\xf1\x1a\x2c\x6c\x18\xb8\x0a\xc4\xf5\x20\x13\x40\x78\x1d\
+\x94\x46\xe7\xaf\xc3\x22\x86\xc2\x67\x82\x9a\x06\xa3\x37\xa0\xf1\
+\x99\xb0\x88\xa1\xf0\xd1\x0d\x08\xc0\xc2\xc7\xab\x86\x05\xca\x61\
+\x06\xe2\xad\x58\xc2\x08\x24\xe6\x8d\x4f\x0d\x2c\x10\x71\x81\x1d\
+\x50\xda\x03\x97\x02\x98\xb3\x76\x43\x31\x32\x7b\x37\x54\x23\x33\
+\x92\x38\x26\x0d\x0d\xd1\x7d\x48\x34\x3e\xf6\x3e\x34\xf5\x0c\x0c\
+\x68\x51\x83\x8e\x0f\x42\x31\x36\x3e\x98\x86\x05\xe2\x51\x2c\xde\
+\xb3\x06\x62\x7b\x24\xb9\xa3\x50\x31\x18\x9f\x05\xd9\x0b\x27\xd1\
+\x6c\x3e\x49\x24\x0d\x0f\x44\x56\x28\x7d\x0e\x8d\xcf\x0a\x15\x83\
+\xd1\xe6\x68\x34\x1c\xdc\x46\x73\xfe\x6d\x3c\x6c\x14\xb5\x20\x7f\
+\xc8\x01\xf1\x1d\x20\x7e\x8e\x25\x1c\x40\x62\x92\x48\x72\xc8\xec\
+\xed\x40\x9c\x0e\x4a\x45\xfc\x40\xcc\x81\x14\xdf\xc4\x82\xbf\x40\
+\xfc\x03\x20\xc0\x00\x06\xa1\x3b\xa3\x22\x84\xac\x0c\x00\x00\x00\
+\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x03\x76\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x03\x3d\x49\x44\x41\x54\x78\x5e\xed\x5a\x4d\x68\x13\x41\
+\x14\x7e\x93\x98\x44\x7b\x30\xa9\x98\x46\x8a\x84\xa2\x17\x0f\xd2\
+\xe6\xd2\x5e\xc4\xb4\x5a\xb0\xa7\xe2\xa1\x17\xeb\xc1\x83\x17\xbd\
+\x8a\xe0\x55\x0f\x9e\x04\x6d\x8f\xf6\x26\xe2\xdf\xa5\xf8\x13\x11\
+\x14\x2a\xb5\x9e\x6a\x85\x26\x05\x41\x08\x15\x09\x12\x4c\x5b\x35\
+\x11\x1b\xb3\xdd\x24\x23\x33\xdb\x84\xd4\x6c\xe4\x55\xc6\x99\x35\
+\x3b\x73\xc9\x12\x5e\xe6\xcd\xfb\xe6\xfb\xde\x7b\x3b\x19\x02\x2e\
+\x1f\xc4\xe5\xf1\x83\x06\x40\x33\xc0\xe5\x08\x68\x09\xb8\x9c\x00\
+\x3a\x09\x6a\x09\xdc\x98\x2f\xcd\x4a\x94\x41\x99\x10\xcf\x9d\x0b\
+\x03\xfe\x5b\x76\x3e\x8f\xdd\x5d\x3f\x49\x00\x62\x94\xd2\x21\x59\
+\x6b\x22\x13\xf3\x06\x95\xe5\xac\xc1\x4f\xb2\xea\x21\x67\x2f\xf6\
+\xfb\x17\xd9\x77\x43\xf7\x7f\xf6\x90\x4a\xe5\x21\x0b\x5e\xf6\x5a\
+\x54\x01\xc0\xe2\xfc\x62\x12\xff\xe1\x67\xef\xa0\x44\x7c\xeb\x8b\
+\x04\xa0\x47\x76\xf0\xcc\x9f\x4a\x00\x80\x52\x72\xfd\x69\xda\xf8\
+\x01\x84\x5c\x56\x11\xbc\x72\x00\x00\xe8\xf2\x93\x74\xd9\xab\x6a\
+\xf7\x1d\x00\x00\x18\x89\xb4\x19\x50\xb5\xfb\x4e\x00\x00\x12\x69\
+\x53\x65\xfc\x6a\x73\x00\x8b\x5c\x03\xa0\x19\xa0\x25\xe0\xbc\x1c\
+\x70\xef\x55\x06\x3e\xad\x15\x51\x0b\xbb\x34\x76\x88\xdb\x65\x56\
+\x8a\xf0\xe0\x75\x86\x3f\x9f\x3a\x1a\x85\x68\x57\x07\x7f\xbe\x36\
+\xfd\x9e\x7f\xee\xdf\xdb\x01\xa7\x07\xa3\x4d\x73\xda\xe5\x80\x73\
+\xcf\xe7\xe0\x60\x6e\x15\xe7\xff\xcc\x18\xb7\x3b\xf0\x79\x15\xce\
+\xbf\x98\xe3\xcf\x37\x4f\xc4\xe1\xc3\xbe\xb0\xe5\xff\xf6\x34\xff\
+\x5c\x8e\x84\x61\x6a\x24\xde\x34\xa7\x6d\x23\xe4\x7a\x00\x66\x92\
+\x39\xc8\x15\x0c\xd4\x0e\xd4\x76\x35\x97\x37\x60\x26\x95\xe3\xbf\
+\x19\xee\x8b\x40\x24\x64\x95\x77\x06\x26\x1b\x91\x60\x00\x86\x63\
+\x11\x14\x03\x46\xdf\xa4\xa0\xfb\x5b\x01\xe5\xbf\xb6\xab\xdd\x5f\
+\xf3\x30\xba\xb0\xc4\x7f\x93\xe8\xef\x85\xec\x9e\x10\x7f\x66\x6c\
+\x62\x23\xdb\x19\x84\xc4\x40\x1f\x8e\x01\x28\xcf\x82\x8c\x74\x19\
+\x74\x62\x19\x64\x12\x58\xf9\x8e\x93\xc0\x78\xdc\x4a\x6c\x4c\x02\
+\x2f\x97\x2c\x09\x1c\xef\xdd\x2a\x01\x42\x00\xba\x76\x6f\x57\x02\
+\x79\x14\xc7\xa6\x46\x06\xb9\x9d\x25\x81\xd4\xa6\x04\xfa\x7e\x93\
+\x00\x85\x6c\x67\x08\x2f\x01\xd7\x27\x41\xd7\x03\x80\xe2\x9e\x20\
+\x23\x9d\x04\x9d\x98\x04\x59\x42\x33\xcc\x0a\x6a\x8f\xa3\x61\xab\
+\xe3\x6b\x35\x58\x87\xc8\x0e\xdf\x03\x3e\x6f\xbd\x37\x68\xb4\xb5\
+\x63\x00\x4b\x68\x3b\x37\x70\xef\x08\xb5\x8e\xaf\x95\x7f\xd6\x21\
+\xb2\x51\xf2\xfb\xea\x89\xb1\xd1\x56\x58\x27\xd8\x6a\x01\xb2\x5a\
+\xe1\x96\xfe\x65\xb5\xc2\x6d\x05\x00\x97\xc0\x06\x52\x02\x9b\x2f\
+\x3d\x7f\x94\x00\x00\x04\xfc\xff\x91\x04\x50\xe2\x17\x64\xa4\xab\
+\x80\x13\xab\x80\xea\x46\x48\x10\xb9\x50\xd3\x08\xab\x02\x22\x0f\
+\x44\x50\x2b\x17\x64\x64\x0b\x80\xea\xf3\x00\x41\xb1\xa1\xa6\x51\
+\xfa\xd7\x18\x5b\xa1\x4e\x82\x4e\x4c\x82\x7f\x73\x1e\xd0\x8a\x6f\
+\x2c\xa1\x6e\xf7\x3c\x00\xc5\x5d\x41\x46\xc2\x92\xa0\xc8\x4e\x50\
+\x50\x6c\xa8\x69\x34\x00\x8a\x6e\x88\xd4\x77\x47\x27\x41\x27\x26\
+\x41\x94\x78\x04\x19\x69\x06\x68\x06\xe0\x4e\x7e\x04\x11\xae\x69\
+\x1a\xdd\x09\xea\x2a\xa0\xe6\xa2\xa4\x2e\x83\x35\x04\xd4\x57\x81\
+\x05\x53\xda\xbd\x5c\x16\xf4\xdb\xac\x79\xd5\xac\xc0\x91\x1a\x00\
+\x6b\xc5\x6a\x9d\x0d\x14\xe8\x63\x4a\x61\xf2\x5f\x25\x3c\xbb\x79\
+\xa5\xdf\x16\x67\xf7\x82\x3d\x95\x72\x12\x80\x04\xb7\x2e\x88\x16\
+\xaa\xde\x1d\xb1\xd9\xf1\x5d\x1f\xdb\x1a\x00\x16\x9c\x75\x39\xba\
+\x3c\x49\x80\xc4\x00\x68\x88\x02\x79\x44\xbd\xde\x2b\xb2\x83\x67\
+\x6b\x91\xce\x00\x99\xbb\x8b\xf1\xa5\x01\xc0\xa0\xd4\xce\x36\x9a\
+\x01\xed\xbc\xbb\x98\xd8\x34\x03\x30\x28\xb5\xb3\x8d\xeb\x19\xf0\
+\x0b\x80\x64\x82\x80\x49\x5e\x86\xab\x00\x00\x00\x00\x49\x45\x4e\
+\x44\xae\x42\x60\x82\
+\x00\x00\x00\xc7\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x04\x00\x00\x00\xb5\xfa\x37\xea\
+\x00\x00\x00\x8e\x49\x44\x41\x54\x78\x01\x9d\xd0\xb1\x69\x03\x41\
+\x10\x05\xd0\x87\xf1\x95\x20\x21\x35\xb2\x81\x12\x17\x20\x04\x92\
+\x02\xe7\xdb\x80\xfb\xb0\x43\x63\xbb\x0d\xa7\x07\x4e\x16\x2e\xf3\
+\xc6\xd7\x83\x40\x45\xac\xe2\x3d\x26\xba\x17\x0d\xc3\x4f\xfe\x07\
+\x80\x41\x56\x55\xd9\x20\x94\x4d\x92\x64\x92\x85\xaa\x04\x92\x2a\
+\xd4\x82\xcb\x93\xd0\x8a\x40\xb1\x54\xfa\xc0\x0b\x00\xf1\xe7\x6e\
+\xd7\xb5\xd8\xbb\xe9\x14\xa7\x6e\x87\x8b\x11\x78\x06\x7c\x7a\x77\
+\xc0\x37\x7e\xfc\xfa\xf7\x66\xe1\xcb\xec\x6a\x6b\xe3\x6c\xf6\x21\
+\xf0\xea\x4f\xd3\x8c\x8e\x10\x6b\x1a\xac\x5c\xb2\x68\xa0\x29\x00\
+\x3c\x00\xaf\xcb\x25\x7f\xd6\x8e\x5f\x26\x00\x00\x00\x00\x49\x45\
+\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x08\x3f\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\
+\x00\x00\x08\x06\x49\x44\x41\x54\x68\x81\xcd\x5a\xdd\x8f\x5d\x55\
+\x15\xff\xad\x75\xce\xbd\x9d\x99\xce\xb4\xd3\x8f\x69\xad\xd5\x04\
+\x4a\x62\x4c\x1c\xde\x50\x83\x31\x08\x8d\x82\xd1\x80\x88\x1f\x41\
+\xa5\x7e\x46\xe2\x7f\xa0\x08\x62\xa8\x80\x92\x88\x6f\x9a\xf0\x62\
+\x4c\x20\x12\x08\x86\x2f\x63\x50\x48\xc1\x07\x7d\x20\x31\x26\x52\
+\x3f\xa2\x40\x1f\xb0\x4d\x3b\xed\x74\xda\x32\xed\x30\xf7\x9e\xf5\
+\xfb\xf9\x70\xce\x99\x39\xf7\xde\x73\x6e\xe7\x4e\x47\x74\x25\x93\
+\xf3\x9b\x75\xd6\x5e\x7b\xed\xb3\x3e\xf6\xda\x7b\xc6\x50\x43\x77\
+\x3d\x7e\x3a\x1d\x4f\xfc\x36\x73\xec\xef\x64\xb8\x9c\xc2\x78\x9d\
+\xdc\x7f\x9b\xdc\xb0\xd4\x4e\x70\x24\x80\x43\xcb\x1d\x3e\x72\xef\
+\xad\xdb\xb3\x7e\x19\xeb\x67\xdc\xff\xe4\x99\x83\xe7\x97\xf5\xbd\
+\xb9\x73\xc4\x5b\x59\x8d\xc0\xdb\x4c\x02\x30\x96\x02\xbb\xb6\x3a\
+\x26\x5a\xf6\x83\x3b\x6f\x99\xbe\xbb\xfa\xbe\xc7\xbe\x1f\x3e\xb9\
+\xf0\xca\x6b\x27\x39\xfb\x56\x57\x6a\x25\x6e\x12\x09\x00\xee\xee\
+\x24\x59\x3e\x4b\xf9\xea\xef\xeb\xc5\x6b\x91\x35\x73\xef\x06\x35\
+\xd6\x32\xbb\x62\xc6\x0f\xdf\xf1\xe9\x6d\x57\x0e\x2c\xe0\xfe\x27\
+\x17\x5e\xf9\xc7\xd1\x98\xb5\xc4\xe0\xff\xeb\xcf\xde\x40\x14\xa0\
+\x10\xde\xbb\x37\x39\xfc\xdd\x62\x11\x0e\x00\x07\x9f\x58\x38\xf8\
+\xea\x89\x98\x85\x0b\x06\x51\x12\x29\x42\xea\xc5\x55\x5e\x93\xcc\
+\xa8\x78\x14\x59\x83\x08\x17\x5e\x3d\x11\xb3\xf7\x3c\xb1\x70\x10\
+\x00\xec\x8e\x47\x4f\xb7\x82\xe8\x1c\x3d\x9d\xa9\xe5\x6e\x44\xe1\
+\x42\xb8\xf7\xe3\x2a\xaf\x49\x66\x54\xbc\x9e\x71\x5d\x52\x7b\x77\
+\xa4\x06\xb2\x9d\x26\x09\x0e\x9c\x58\xc8\x90\xb8\x99\x4c\x30\x98\
+\x03\x80\x30\x88\xab\xbc\x26\x99\x51\xf1\x7a\xc6\x25\x6e\x76\xea\
+\x4c\x86\x3d\xdb\xd3\x03\x29\x89\xfd\x4b\x99\xd0\x4a\x40\x09\x0e\
+\xa0\xfc\xc2\x3d\x98\x14\xfb\x78\x03\x32\x03\xd8\x00\x37\x1b\x2e\
+\x73\x31\x1d\x0d\x78\x29\x93\x13\xda\x9f\x76\x33\x5d\x66\x12\x24\
+\x34\xd2\xb9\x0b\x81\xf1\xb6\x37\x0b\xd4\x91\x01\x59\x26\x74\x42\
+\xd8\xbc\xc9\x91\x26\x1b\x5b\x19\x4c\x42\xd6\xc1\xbe\x34\xa8\x31\
+\xe5\x0b\xa8\x75\xe7\xe2\x52\xe0\xae\xcf\x6c\xc3\x55\xfb\xc6\xcb\
+\x15\xf4\xaf\xc4\x87\xe1\x7f\x1e\xeb\xe0\xc1\x5f\x9f\xf1\xe3\x67\
+\x33\x4c\x8d\x39\xcc\x2e\x3d\x84\x0c\xe6\x92\x10\x50\xdb\x49\x40\
+\x12\x28\xd2\x64\xa0\xc8\x2a\x5e\xea\x8a\x57\xed\x1b\x87\x8a\x9a\
+\xcc\x0a\x55\x7f\x6f\xc2\xef\x79\x67\x1b\x0f\xdd\xbe\x8b\xb7\x7d\
+\x78\x0b\xe7\xdf\x64\xce\xaf\xe8\xaf\x9b\x73\x2d\x58\x12\x48\xc0\
+\x25\x01\x02\x4c\x06\x49\x30\x59\x0f\x2e\x23\xaf\x29\xc2\xdc\x1d\
+\xee\x3e\x14\x03\xc0\xe7\xae\x9e\xc4\x4f\xbf\xb1\x13\x8b\xcb\x84\
+\x71\x55\x7f\xdd\x9c\x6b\xc1\x10\x10\x12\xd2\x50\xee\x81\x22\x81\
+\x07\xc2\x40\x62\x69\x90\x57\x9f\x00\x70\xfd\xc1\x37\x00\x83\xb7\
+\x12\x43\x9a\x18\x54\x8e\x15\x90\x38\xbc\x95\x5a\xb9\x72\x07\x80\
+\xb1\xb6\x61\x73\xdb\x9c\x52\xb9\x83\xd6\xce\xb9\x16\x2c\x08\x0c\
+\x20\x65\x08\x82\x20\xd5\x57\x04\xe5\xd9\x5d\xb7\xed\x63\xcb\x84\
+\x7b\xea\xa0\x99\x01\x90\x53\x95\xb1\x02\x8b\xca\xb0\xa2\x73\xb9\
+\x23\x87\x81\x86\x55\xbe\x19\xdc\xcc\x46\xae\x42\x92\x9c\x10\x52\
+\x42\xa5\x07\x6a\x49\x43\xca\x53\x27\x02\x89\x3b\x0c\xc0\x99\xf3\
+\x81\x89\x31\x87\x0d\xa9\x66\x75\xb4\x9c\x09\xa9\x03\x63\x23\x56\
+\x39\x49\x50\xa8\xf0\xc0\xd0\x10\x2a\x62\xa0\x26\x84\x22\x13\x90\
+\xca\x4f\x9f\x0f\x3c\x74\xfb\x1e\x5c\xb6\xbb\x3d\x72\x28\x00\xc0\
+\x03\x4f\x9d\xf4\x43\x7f\xb9\x80\x6d\x93\xc9\x9a\xc7\x15\x55\x08\
+\x9e\x11\x80\x54\xf6\x1d\xd5\x5e\xa7\xe8\x55\xb4\x52\x55\xca\x67\
+\x81\x49\x09\x11\xe2\xd4\x98\xf1\xb2\xdd\xed\x8b\x56\xa4\x3a\x0c\
+\x00\xdf\xbe\x79\x86\x5f\xb9\x76\x2b\xe7\xce\x76\x07\xe6\x6f\xc2\
+\x90\xc0\x10\x52\x6a\xfd\x1e\x50\x9e\x3f\x6e\x7d\x32\xeb\xc0\xfe\
+\xc5\x6b\xa6\x31\x35\xee\x78\xf0\x99\x93\xbe\x6b\x6b\x6b\x6d\x1e\
+\x20\x90\x92\x02\x19\x90\xac\x36\x89\xc9\x00\x1a\x92\x38\x23\x9d\
+\x01\x16\xdd\xd1\x25\x9f\x07\x6e\x7c\xff\x16\x97\xc4\x9f\x3c\x7b\
+\x0a\x3b\x26\xfd\xe2\x49\x2c\xc2\x49\x0d\x4d\xd4\x8b\xbd\x2b\x7f\
+\x36\x8a\x6e\xfa\xc0\x56\x7c\xfd\xba\x6d\x58\x58\x1c\x38\x3d\x0e\
+\xcc\xcd\xd2\x03\x85\x11\x23\x87\x90\x83\x90\xcc\xcb\x6f\xb3\xde\
+\x10\xaa\xf2\x00\xf8\x97\xf7\x6f\xc7\x91\xb9\x65\xbc\xfc\xaf\xf3\
+\xbe\xa9\x55\x99\xae\xc7\x2e\x20\x48\x78\x90\x50\xbe\xa2\xc6\xc4\
+\x01\xea\x93\xb8\x2e\xd1\xd7\x93\xc4\x03\x3c\x89\xdf\xbf\x75\x0f\
+\xce\x5d\xc8\x9a\x13\x1a\x02\xa9\xdc\x03\x08\x41\x0d\xcd\xa2\xa2\
+\x39\x3c\x48\x41\x54\xef\xb7\xd9\x00\xa2\x04\x37\x03\x43\xcd\xf3\
+\xab\xb2\x00\xad\xee\x8c\xd5\x27\x70\xb1\x2a\x84\x22\xf4\x2e\xb1\
+\x0a\x35\xbd\x97\xe4\x5a\xed\xc2\x06\x5b\x89\x7c\x01\x2c\x93\x70\
+\xe4\x56\xc2\x24\x97\x44\xc4\xaa\x4c\x69\xc4\xb1\xf9\x65\x1e\x78\
+\xf0\x35\xbc\xf9\x96\x3c\xf1\xe6\x6a\x12\x84\xef\x9c\x4a\xf9\xd8\
+\x77\xae\xc0\xd4\x78\xda\xa3\xa3\x0c\x99\x06\xbb\x9c\x24\xd2\x88\
+\xbc\x8c\xc2\xea\x63\x28\x82\xb5\x7c\xe4\x5a\x10\x0c\x78\xcd\xd8\
+\x87\x5f\x9a\x47\x37\x88\x99\xad\x49\xe3\xf8\x92\x16\x16\x3b\xf8\
+\xd5\x1f\x4e\xe3\xab\x1f\xdd\xd5\xab\x9f\x44\xe4\x65\x7c\x90\xf2\
+\x4d\xb4\xf0\x00\x01\x6b\xea\x0c\x39\xa4\x0a\x49\x10\xcd\xe5\x83\
+\x21\x74\xdb\x47\x76\xf8\x6f\x5e\x3e\x8d\xe3\xf3\x9d\xf2\x58\xb9\
+\xaa\xb3\x82\x83\xc2\xcc\x74\xea\xb7\x7c\x68\xdb\x80\x0e\x85\x1c\
+\x51\x3f\x4e\xc5\x02\xd3\x08\x82\xf9\x0a\x6a\x43\x28\xd4\xbc\x91\
+\x09\x72\x2a\x28\xda\x8a\x4c\x69\xc4\xde\x9d\x6d\xfe\xfe\x81\x59\
+\xd4\xe9\xec\xc3\xc5\x73\x30\x0c\x43\xc1\xc8\xed\x1e\x1c\x27\x38\
+\x82\x48\x19\xcc\x6f\x8c\x1a\x42\x48\x1c\xb2\x49\x51\x50\xde\x4a\
+\x0f\xbe\xa2\x00\x08\xee\x8e\xc2\xa6\x5a\xdc\xcb\xeb\xb5\x41\x54\
+\xef\x15\x42\xcf\x4b\x81\x20\xd2\x8c\x84\x93\xd0\xaa\x9b\x7b\x5d\
+\x1c\xcd\x07\x1a\x80\x08\xc2\xcb\x1c\xd8\xe8\x2a\x14\x41\x8f\xea\
+\x35\x54\xc5\x2e\x2b\x17\x10\xc1\x3c\x7a\x1a\x7a\xa1\xf2\x3e\xa9\
+\x36\x84\x28\x37\x13\x73\x2f\xf4\xba\xff\xe8\xa9\x25\x7e\xf6\xde\
+\xbf\x61\xf1\x42\xe6\x3e\xe4\xc0\x12\x94\xcf\x4c\xb7\xf8\xf4\x3d\
+\x57\x62\xcb\x44\xab\x47\x07\xc8\xd5\xfb\xac\xfe\x2a\x04\x79\x44\
+\xb1\x00\xb7\x21\x95\xa6\xa8\x02\xb5\x11\x26\x21\x44\x78\x4f\x8e\
+\xe6\xf4\xf3\xe7\x8e\xa3\xdb\xcd\xb0\x6b\x3a\x6d\xd4\x5d\xd2\xfc\
+\xb9\x2e\x1e\x7b\x71\x0e\xdf\xfc\xe4\xde\x1e\x7e\x30\x56\xe6\xaf\
+\xa3\x10\x91\x46\x97\x48\x52\x01\xa8\x0f\xa1\xb1\x96\xe3\xf1\x97\
+\x8e\xe3\xf3\xd7\xbe\x63\xc0\xdd\x66\x04\x43\x6e\x96\x4f\x52\x7d\
+\xf7\xb5\x1b\xf6\xf8\xb3\x7f\x9c\xc3\xbf\x4f\x2e\x23\x71\x14\x57\
+\x22\xab\xd5\xae\xc4\x41\x60\xd7\x74\xdb\x6f\xbd\x76\xf7\x80\x0e\
+\x52\xbe\x1a\x41\xfd\xad\xb5\x10\x59\x59\x85\x8c\x8d\xed\xf4\x78\
+\xdb\xf1\xa3\x47\x8f\xf8\xdd\xbf\x78\x75\xe0\x66\x6e\xe7\x96\x76\
+\xde\x72\xfb\x60\x05\x79\xd7\xcc\x26\xbe\xfc\xb3\x0f\xa2\x4e\x67\
+\x1f\x6e\xae\x42\x41\x46\x56\x1f\x42\x66\xf2\x88\x40\x9a\x45\xa0\
+\xed\xc4\xb0\x3f\x65\x4c\x8e\x39\x26\xc7\x7a\xc2\xa4\x28\x3d\xf9\
+\x66\x52\x47\x97\x52\x85\x56\xc2\x95\x04\x54\x1f\xde\x92\x90\x85\
+\xe5\x65\x94\x49\x0c\x5d\x40\xdd\xf8\xaa\xa1\xb6\x81\xe7\x81\xdc\
+\xb8\x7c\x11\x11\xc3\x72\x40\x60\x18\xd2\x8a\x50\xfd\x4e\x3c\x88\
+\x7b\x5c\x41\x6a\x65\xa3\xdd\xe8\x32\x4a\x66\x4e\xae\xb4\x22\x03\
+\xb6\x44\x18\xd2\xc8\x02\x6c\x11\x5a\xdb\x4d\x71\x4f\x0e\x00\x70\
+\x0a\x14\x33\x00\xf0\xfc\x68\x80\x4b\xfe\x13\x93\x99\xc1\xcc\x9c\
+\x24\xa3\xa1\x8c\x1a\xe0\x91\x45\xb1\x51\x11\x00\xe1\x95\x67\x13\
+\xae\xf2\x1c\x04\x1c\xf0\x73\x8b\x5d\x3f\xf4\xa7\x39\x98\xb9\x57\
+\xbf\xea\x5a\x70\x1d\xcf\xcc\xfc\x97\xbf\x7b\x03\x13\x6d\x1f\x6a\
+\x4b\x44\x86\x34\xb2\xac\xc3\xfc\xae\x8c\x02\xbc\xbc\x0d\xad\xc3\
+\x55\x5e\x95\xbf\x75\x73\x8a\x6f\xfd\xf8\xcf\xbe\xb4\x1c\xc3\xbc\
+\xb7\x16\xaf\xe6\xd8\x80\xcd\x63\x89\x4f\x4d\xb4\xf2\x03\x5a\x8d\
+\x2d\x06\x78\x04\x3a\x69\xa7\xab\xd7\x25\x5d\x1d\xc5\xc1\x2a\x2a\
+\x9a\xfa\x71\x95\xd7\x2f\xb3\x65\x22\xc1\xe4\x44\xb2\x01\xd6\x57\
+\x74\x97\xd5\xa9\xc6\x96\xc4\x80\x4e\x07\xaf\xa7\xc1\x38\xd4\xe9\
+\xe0\x4b\x49\x02\xe7\xaa\x0c\xea\x70\x95\xd7\x24\x33\x2a\x5e\xef\
+\xb8\x4e\x00\x41\x1c\xb2\x77\xdf\xf2\x5c\x2b\x01\x3a\xd3\x53\x10\
+\xf3\x4b\xe3\x91\x92\xf8\x22\xf2\x6b\xc1\x23\x8f\x73\x83\x16\xde\
+\x84\x41\x68\x27\xe7\xfe\xfe\x08\xdb\xfb\xbe\xd0\x1a\x6b\xe9\x1a\
+\x87\x15\x27\x75\x01\x32\x83\xfa\x71\x95\xd7\x24\x33\x2a\x1e\x71\
+\x1c\xcc\xb2\xa0\x9d\x3d\xaf\xfb\x8e\x3d\xf3\x89\x17\x56\x76\xaf\
+\x1d\x1f\x7f\xf6\xf0\xde\x1d\xfe\xbe\xd5\x2d\xa9\xe8\x56\x7a\x70\
+\x95\xd7\x24\x33\x2a\x1e\x6d\x9c\x01\x38\x36\xcf\xbf\x9e\x7a\xee\
+\xc6\x59\xa0\x6f\xfb\xdd\x7e\xfd\x33\xaf\xec\xde\xee\xb3\x2d\x2f\
+\xc2\x49\xff\x27\x21\x64\x79\xd8\x74\x09\x9b\x9b\xe7\xe1\xf9\xe7\
+\x6f\x1a\xfc\x57\x83\x92\xb6\x7d\xec\xa9\x7b\x37\xa5\x7e\xe7\xf4\
+\xa4\x90\xb6\x7a\xef\xfb\x37\xb6\x61\x68\xa6\x1e\x1f\x1b\x90\x75\
+\x89\x33\x8b\x86\xe5\x8c\xf7\x2d\x3c\x7f\xf3\x5d\x4d\xb2\x2b\x34\
+\x73\xc3\xd3\x69\x96\xf1\x80\x80\xeb\xd2\x14\x97\x5b\xf1\xef\x36\
+\x6f\xd7\xbf\x50\x94\x1f\x4a\x86\xa5\x2c\xc3\x11\x03\x5e\x6c\xa7\
+\xfe\xf0\x89\xdf\x7e\x6a\xe0\xc2\xf4\x3f\xa0\x8e\x43\x09\xb6\x60\
+\x7b\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x0a\x2e\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x50\x00\x00\x00\x50\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\
+\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x2e\x23\x00\x00\
+\x2e\x23\x01\x78\xa5\x3f\x76\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xe1\x0c\x02\x0f\x17\x38\x58\xe8\x3c\x38\x00\x00\x09\xbb\x49\x44\
+\x41\x54\x78\x5e\xed\x9c\x6b\x6c\x14\xd7\x15\xc7\x7f\xb3\xf6\xe2\
+\xb5\xd7\x4e\xfc\x62\x81\x44\x6d\x4d\x11\x60\x53\x20\x12\x4b\x23\
+\xf5\x05\x9b\xa6\x6d\x10\x75\x2a\x0b\x54\x04\x05\x09\xb5\x6a\x81\
+\x0f\x2d\x4a\x63\xa9\x4a\x1f\xa1\x41\x4a\xa4\x7e\x48\x4b\xa5\x2a\
+\x4d\x82\x1a\x55\xa9\x78\x54\x29\x60\xb5\x41\xe9\x0b\xea\x98\x56\
+\x49\x9b\xd8\x04\x68\x28\xb4\xe5\x15\x5b\xb1\xc1\xc6\x6c\xb1\x77\
+\xbd\x6b\x7b\x77\x6f\x3f\x1c\xaf\xe3\x5d\xcf\x78\x66\xbc\x33\xb6\
+\x17\xfc\x93\xee\x17\xef\x9d\x7b\x3d\xff\x39\xf7\x75\xee\xb9\x17\
+\x66\xc9\x09\xcd\x2c\x83\x8b\xf8\x80\x8f\x00\x0f\x00\x2b\x80\x25\
+\xc0\x02\xe0\x1e\xa0\x08\x48\x00\x11\xa0\x07\x78\x0f\x38\x0f\x9c\
+\x01\x2e\x00\xb7\x75\xca\x9b\x16\xa6\x43\xc0\x0a\x60\x2d\x50\x0f\
+\x7c\x0a\x11\xb1\x08\xf0\x4c\xf0\x8c\x02\x86\x81\x5e\xe0\x2d\xe0\
+\x04\xf0\x7b\xe0\xca\xc8\x6f\x77\x05\xc5\xc0\x46\xe0\x28\x70\x0b\
+\x48\x21\x2f\x6f\x37\xa5\x80\x41\xe0\xef\x40\x23\x70\x3f\x77\x01\
+\x4b\x81\x9f\x02\xdd\x4c\x5e\x38\xbd\x14\x03\x7e\x0b\xac\x07\x0a\
+\xb8\x43\x79\x04\xf8\x03\x90\xc4\x5c\x90\xc9\xa4\x14\xf0\x5f\xe0\
+\x5b\x48\x57\x70\xc7\xa0\x21\x4d\xf6\x1d\x9c\xb5\x3a\xa3\x74\x0b\
+\xd8\x03\xf8\xb9\x43\x68\x40\x46\xcd\xa9\x10\x2f\x9d\xfa\x81\xef\
+\x02\x73\xc8\x73\x1e\x04\xfe\xc6\xd4\x8a\x97\x4e\x3d\xc0\x37\x98\
+\x9e\x19\x86\x23\x54\x01\xbf\x62\x7a\xc4\x4b\xa7\x33\xc0\xa7\x99\
+\x02\x9c\x1e\xb9\x34\xe0\x6b\xc0\x6e\xec\x37\xa3\x21\xe0\x7f\x40\
+\xd7\x48\x0a\x23\x93\x69\x0f\x50\x88\x3d\x8b\x9a\x87\x3c\xd7\x8c\
+\x4c\x79\x5c\xa3\xd0\x2c\x83\x4d\x16\x01\x5f\x41\xe6\x7c\x56\x50\
+\x40\x1f\xf0\x57\xe0\xcf\xc0\x39\x44\xbc\x41\xe4\xe3\x56\x21\x2b\
+\x94\x87\x80\xcf\x22\x93\x6e\x2b\x42\x6a\x48\x1f\xfc\x47\xe0\xd7\
+\x26\x79\x67\x14\xbb\x91\x97\x37\x6b\x62\x0a\x69\xe2\x7f\x01\xb6\
+\x02\x95\x7a\x85\x8d\xa1\x08\xf8\x0c\xf0\x12\x30\x80\x79\xd9\xe9\
+\xf2\x5f\x41\x96\x86\x79\xc1\x5c\xe0\x4f\x58\xeb\xfb\x92\xc0\x41\
+\x64\x0d\x6c\x87\x0a\xe0\xfb\x48\x53\x37\xab\x43\x01\x9d\xc0\x1a\
+\xdd\x92\x66\x20\x6b\x90\x95\x86\xd9\x4b\xa5\x80\xdf\x01\x75\xfa\
+\xc5\x98\x52\x0c\x3c\x83\xf4\x99\x66\x75\x25\x80\x1f\x30\xf1\x3a\
+\x7b\x46\xa0\x01\xdf\x46\x16\xfc\x66\x2f\xf5\x3e\xf0\x45\xfd\x62\
+\x2c\xf3\x21\x64\x75\x63\x66\xed\x29\xa0\x09\x28\xd5\x2f\x26\x77\
+\x9c\xfa\x32\x85\xc0\x72\xac\x95\x77\x02\x19\x1d\x73\xa1\x03\x71\
+\x4a\x98\x8d\xb0\x1a\xb0\x18\xe9\x5e\x5c\xc1\xca\x0b\x5b\xa1\x18\
+\xb1\x0a\xb3\x11\x32\x86\x88\x37\x60\x92\xcf\x0a\xa7\x80\x76\xb3\
+\x4c\x40\x00\xa8\x36\xcb\x34\x59\x9c\x12\xd0\x87\x8c\x76\x66\x02\
+\xf6\x23\x0e\x51\x27\xb8\x81\x74\x07\x66\xf8\x80\x32\xb3\x4c\x93\
+\xc5\x29\x01\xe7\x60\xcd\x13\x32\x8c\xcc\xfb\x9c\x60\x08\xf1\x58\
+\x9b\xe1\x41\x44\x74\x05\xa7\x04\xd4\x30\xb7\x3e\xf8\xa0\x63\x77\
+\x0a\xab\x65\x39\xbd\xe2\x1a\xc5\x29\x01\xef\x5a\x66\x05\xcc\x91\
+\x59\x01\x73\xc4\x8e\x33\x61\xa2\x7e\xc4\x4e\x1f\x53\x88\xbd\xfc\
+\x46\x14\x60\xad\xdf\x75\x15\x3b\x02\x36\x62\xbc\xe8\x2f\x43\xf6\
+\x74\xcd\xb8\x17\x71\x38\x84\xcd\x32\x5a\xa0\x10\x6b\xcb\xc1\x02\
+\x64\x5f\x26\x60\xf0\x7b\x0a\x99\x53\x5e\x36\xf8\xdd\x31\xae\x20\
+\x95\x19\x25\xb3\x25\x5c\x7a\x04\x76\x3a\x99\xd5\xa9\x90\x29\xcf\
+\xa0\x41\xba\x8d\x78\x84\x26\x85\x1d\x0b\xb4\x3a\x55\x99\x88\x5c\
+\x9f\x9f\x2c\xde\x09\x7e\x4b\x91\xc3\x58\x30\xe9\x07\x67\x11\x66\
+\x05\xcc\x91\x59\x01\x73\x64\x56\xc0\x1c\x99\x15\x30\x47\x66\x05\
+\xcc\x11\x3b\xd3\x18\x3b\x58\xf5\x92\xb8\x89\x13\xd3\x2e\x53\xdc\
+\x10\x70\x00\x38\x86\xec\x88\x4d\x17\x3e\x64\x5f\xf8\xc3\x66\x19\
+\x73\xc5\x0d\x01\x23\xc0\x8b\xc0\x9b\x66\x19\x5d\xa4\x12\xd9\xa3\
+\xc9\x4b\x01\x41\xf6\x7d\x93\x66\x99\x5c\x64\xca\xea\x9e\x1d\x44\
+\x72\x64\x56\xc0\x1c\x71\xab\x09\x4f\x3b\xa1\x50\xa8\x2c\x18\x0c\
+\x12\x0a\x85\x58\xbb\x76\x2d\x65\x65\x99\x1b\x73\xad\xad\xad\xb4\
+\xb4\xb4\xd0\xd2\xd2\xe2\x39\x7b\xf6\xac\xaf\xbd\xdd\xca\x0e\x69\
+\x6e\x5c\xc5\xdc\x6d\xa4\x90\xed\xc6\x4f\x18\x94\xe1\x2a\x4a\xa9\
+\xa0\x52\xea\x50\x32\x99\xec\x57\xf6\x79\x5b\x29\xb5\x45\x29\xe5\
+\x5a\x14\xc3\x8c\x15\x50\x8d\x08\xa7\x94\xea\x9b\x50\x22\x6b\xb8\
+\x26\xe4\x8c\x14\x50\x29\xb5\x53\x29\xd5\x39\xb1\x26\x93\xe2\x45\
+\xa5\x94\xa9\x97\x3d\x6f\xfb\x40\xa5\x54\x10\xd9\x66\xa8\xc7\x42\
+\xe4\xc1\xc5\xf8\x35\xa2\xc9\x18\x00\xb5\xbe\x1a\xfc\x05\xa6\x31\
+\xa0\x3b\x80\x55\x4a\xa9\x9f\x00\xaf\x6a\x9a\xa6\xbb\x89\x9f\x97\
+\x02\x8e\x88\x77\x18\x09\x1c\xd2\xa5\x29\xdc\xcc\xa9\xc8\x69\xda\
+\x06\x2e\x10\x4d\xc5\x89\x26\x63\xa4\x46\x56\x98\x7e\x4f\x31\x1e\
+\xcd\x43\xad\xaf\x86\x60\x49\x2d\xdb\x2a\xd7\x13\xf0\xea\x6e\xf7\
+\xac\x06\x0e\x01\x3b\x94\x52\x87\xf5\x44\xb4\xb3\x56\xbc\x0a\xd4\
+\x98\x65\x42\x62\x04\x1b\x70\x69\x25\x62\x26\x5e\x53\xb8\x99\xfd\
+\x37\x8f\xf1\x6e\xec\x32\x7d\xc9\x28\x91\xd4\x00\x29\x94\x5e\x56\
+\xfc\x1e\x1f\xa5\x9e\x12\xe6\x7b\xab\xd8\x58\xf1\x30\x3b\xab\x37\
+\x18\x09\x09\x62\x91\xe3\x44\xcc\x47\x0b\xdc\x09\xcc\xcf\xfe\xe3\
+\xc5\xf8\x35\x0e\xf4\xbe\xc6\x91\xf0\x49\x2e\x0d\x76\x90\xb4\xe0\
+\xcf\x88\xa6\xe2\x44\x53\x71\x6e\x24\x6e\xd1\x35\x7c\x93\xf3\xb1\
+\xcb\x6c\xaf\xaa\x67\x4d\xe9\x2a\xbd\x26\xbe\x03\x78\x1d\x39\x15\
+\x35\x4a\x5e\x59\xa0\x52\x6a\x27\xf0\x43\xb2\xb6\x50\x9b\xc2\xcd\
+\xec\xeb\x3e\xc8\xb9\xd8\x25\xfa\x93\x51\x43\x8b\x33\xc3\xef\xf1\
+\x31\xaf\xb0\x8a\xed\x55\xf5\xec\x9a\xbb\x51\xcf\x1a\xf7\x03\x4f\
+\x69\x9a\xd6\x95\xfe\x43\xde\xac\x44\xc6\x0c\x1a\xe3\xc4\x7b\xfa\
+\xfa\x4b\xbc\x11\x39\xc7\xed\x64\x64\xd2\xe2\x81\x58\xe4\x95\xa1\
+\xf7\x79\xae\xe7\x15\x7e\xde\xf3\x1b\xba\x87\x6f\x65\x67\xd9\x01\
+\xd4\xab\x31\x53\x9c\xbc\x11\x10\x9d\xa6\x7b\x31\x7e\x8d\x7d\xdd\
+\x07\x39\x3b\xf0\x1f\x4b\x4d\xd6\x2a\xdd\x89\x30\xcf\xf7\x1c\xa1\
+\xb9\xbf\x75\x74\xe4\x1e\xc3\x0e\xc6\x7c\xc4\xbc\x10\x70\xc4\xfa\
+\x42\x64\x4d\x57\x0e\xf4\xbe\xc6\xb9\xd8\x25\x47\xc5\x4b\xd3\x9d\
+\x08\xf3\x72\xef\x71\x6e\x24\x7a\xb3\x7f\x5a\x0d\x84\xd2\x56\x98\
+\x17\x02\xa2\x63\x7d\x4d\xe1\x66\x8e\x84\x4f\xd2\x9f\x8c\x1a\x3c\
+\x92\x3b\xa7\x22\xa7\x79\xb9\xf7\xb8\x51\x53\x5e\x00\xf9\x23\x60\
+\x90\x2c\xeb\xdb\x7f\xf3\x18\x97\x06\x3b\x72\xea\xf3\xcc\x88\xa6\
+\xe2\xbc\xd0\x73\x94\xae\xe1\x9b\xd9\x3f\xad\x66\xe4\xff\x99\xf1\
+\x02\x8e\x34\xdf\x0c\xf1\x9a\xc2\xcd\xbc\x1b\xbb\xec\x4a\xd3\xcd\
+\xa6\x3b\x11\xe6\x48\xf8\xa4\x9e\x15\x06\x95\x52\xa5\x33\x5e\x40\
+\xe4\x6b\x67\x2c\xec\x4f\x45\x4e\xd3\xe7\x62\xd3\xcd\xc6\xa0\xbe\
+\xd5\x40\x59\x3e\x08\x18\x24\x4b\xc0\xb6\x81\x0b\x44\x52\x4e\x9c\
+\x94\xb0\x86\x41\x7d\xab\x80\xbc\xb0\xc0\x71\x4d\x38\x9a\x8a\xbb\
+\xda\xf7\x65\x63\x50\x5f\xde\x58\x60\x06\x63\xbd\x2a\x53\x89\x51\
+\xbd\x79\x27\xe0\x58\xaf\xca\x54\x12\x4d\xc6\x50\x3a\x56\xef\x96\
+\x80\xae\x1d\x6c\x99\x69\xb8\x21\x60\x25\xb0\x0b\xb9\xf2\xc4\xec\
+\x20\xb5\x6d\x6a\x7d\x35\xf8\x3d\xa6\xce\x50\xc7\x31\xaa\xd7\x0d\
+\x01\x0b\x81\x2f\x03\xbf\x00\x7e\x09\x7c\x95\xdc\xae\x67\x6a\x43\
+\xce\xd8\x01\xe0\x2f\x10\x67\xe8\x54\xe3\x2f\x28\x46\xd3\x32\x9c\
+\x57\xad\x40\xbf\x5b\xff\x89\x06\x94\x03\x8f\x02\xcf\x01\x07\x80\
+\x6f\x22\x77\x2a\xd8\xad\xb3\x8d\xac\x33\x71\x62\x0d\x53\xd7\x4b\
+\xd4\xe9\xd7\x77\x1a\x88\xd8\x7d\x19\xbb\x68\xc8\x51\xd8\x10\xf0\
+\x2c\xe2\x1e\xff\x1e\xb0\x12\xeb\x67\x45\x5a\xc9\x12\x30\x58\x52\
+\x4b\xa9\xa7\xc4\x20\xbb\xf3\xac\x2a\xa9\xa5\xcc\x33\xee\x42\x24\
+\x57\x2d\x50\x8f\x22\xe0\xe3\x88\x43\xf4\x10\xf0\x34\xf0\x49\x4c\
+\x4e\x79\x6a\x9a\x96\xd1\x84\x01\xb6\x55\xae\x67\xbe\xb7\xca\xe0\
+\x09\xe7\xd9\x56\xb9\x9e\x79\xe3\x9d\xab\x6d\x9a\xa6\xb9\x6e\x81\
+\xd9\x68\x48\x1f\xf9\x31\xe0\x3b\xc8\xc5\x13\xfb\x80\xcf\x01\x46\
+\x26\x55\xd0\xd9\xd9\x79\x65\x70\x70\x70\xf4\x74\x7a\xc0\x5b\xc9\
+\xc6\x8a\x87\x09\x14\x56\x18\x3c\xe2\x1c\x0d\xe5\x21\x96\x17\x2f\
+\xa2\x40\xcb\x68\x30\xad\x8c\x7c\x54\x3b\x02\xa6\x6f\xca\x70\x0a\
+\x0f\xb2\x45\xb0\x0b\xb9\xe9\xe8\x79\x60\x03\x72\x33\x07\xc8\x19\
+\xe4\x07\x81\xbd\x9b\x36\x6d\x7a\xa0\xbd\xbd\x3d\xc3\x52\x77\x56\
+\x6f\xe0\xa1\xb2\xd5\xae\xf6\x85\x75\xbe\x1a\x1e\x0f\x6c\x65\x81\
+\x77\xdc\x81\xf7\xfd\xc8\xfd\x36\x96\xfb\x21\x80\xeb\xc8\x81\xe9\
+\x00\x72\x43\x9a\x9d\xfd\x94\x89\xd0\x90\xa5\xda\x4a\xe0\x0b\xc8\
+\x95\xa0\xf7\x03\x9b\x81\x27\x80\xfa\x8e\x8e\x8e\xea\x15\x2b\x56\
+\xb0\x64\xc9\x12\x8a\x8a\x44\x47\x7f\x41\x31\x7e\x4f\x31\xa7\xfa\
+\xdf\x21\x9c\xcc\x68\xe1\x8e\xf1\x58\x60\x0b\x8f\x96\xaf\xa1\x38\
+\xf3\x23\xb5\x02\x3f\xd6\x34\xed\x3a\xd8\x13\xf0\xdf\xc8\xed\x42\
+\x6f\x21\xe6\x5b\x8d\x8c\xb4\x4e\x0a\x59\x8c\x9c\x7f\x5b\x83\x44\
+\x37\x54\x32\xd2\x4a\x3a\x3b\x3b\x59\xb7\x6e\x1d\x55\x55\x1f\xf4\
+\x7d\xf7\x79\xe7\x32\x90\x8a\xf3\xaf\xf8\x15\xa2\xa9\xb8\x4e\x91\
+\x93\xa7\xa1\x3c\xc4\xee\xc0\x66\xee\xf3\xce\xcd\x9e\xbe\x3c\x05\
+\xbc\xb9\x77\xef\xde\x21\xb0\x27\x20\x88\x05\x5e\x05\x4e\x02\x6f\
+\x20\x77\x9a\xde\x8b\xbc\xa8\xdd\xb2\x26\xc2\x4b\xd6\x87\xe9\xea\
+\xea\x62\x68\x68\x88\x60\x30\x38\x1a\x69\x35\xc7\xe3\xa5\xce\xb7\
+\x10\x80\x0b\xf1\xab\x8e\x89\xd8\x50\x1e\xe2\xc9\x05\x5f\x67\x99\
+\x6f\x61\xf6\x9c\x73\x3f\xb0\x5f\xd3\xb4\x51\xe7\xe0\x64\x5f\x3a\
+\x89\x5c\xf8\xf0\x3a\xd0\x82\xc4\x43\x97\x22\x56\xe9\xda\x31\xd4\
+\xb6\xb6\x36\x96\x2e\x5d\x3a\xae\x29\x2f\xf3\x7d\x94\x3a\xdf\x42\
+\xa2\xa9\x18\x5d\xc3\x3d\x0c\xab\x84\x49\x49\xfa\xd4\xf9\x6a\x78\
+\x2c\xb0\x85\xdd\x81\xcd\x2c\xf3\x2d\xd4\x1b\x38\x1a\x35\x4d\x7b\
+\x6f\xec\x1f\x9d\x7a\x51\x0f\x72\x31\xd8\x23\xc0\x97\x90\x7b\xae\
+\x1c\x8f\x6e\x02\x08\x06\x83\x1c\x3e\x7c\x98\xc5\x8b\x33\x03\x13\
+\xa2\xc9\x18\x37\x12\xbd\xbc\xdc\x7b\x9c\x17\x7a\x8e\xd2\x9d\xb0\
+\x77\xa2\xb6\xa1\x3c\xc4\xe3\x81\xad\xac\x2c\x59\xcc\x3d\x1e\x7f\
+\x76\xb3\x05\x83\xc8\x04\xa7\x04\x1c\x4b\x00\x11\xb2\x01\x99\x40\
+\x57\xe0\x70\x3d\x46\x22\x02\x74\x0f\x4b\x94\xc1\x91\xf0\xc9\x8c\
+\xd8\x18\x3d\xea\x7c\x35\xac\x1a\x89\x8d\x59\x5e\xbc\x88\x05\xde\
+\xea\x6c\xab\x4b\xa3\x2b\x1e\x38\xfc\x62\x59\x94\x23\x83\x41\x03\
+\xb0\x0e\xd9\x55\x73\xac\xbe\x60\x30\x48\x63\x63\x23\xf5\xf5\xf5\
+\xe3\xa2\x4f\x41\x84\x1c\x1b\x1b\xa3\x17\x9d\xe5\xf7\xf8\x28\xf3\
+\xf8\x99\xe7\xad\x34\x12\xae\x15\x98\x30\x3a\x6b\x2a\x28\x41\x26\
+\xca\x3f\x43\x4e\x85\x27\x30\x8f\x31\xb4\x92\x52\x40\x78\xcf\x9e\
+\x3d\x67\xa2\xd1\x68\x58\x37\xc2\x6f\x0c\x91\xc4\x80\xea\x4b\x44\
+\x54\x5f\x22\xa2\x92\xa9\xa4\x59\x76\xa5\x2c\xc6\x07\x4e\x25\x73\
+\x90\xa5\xdc\x33\xc0\x3f\xb1\x76\x51\x99\x91\x70\x5d\xc8\xe4\x7b\
+\x03\x50\xa9\xf2\x24\x42\xd5\x29\xd2\x17\x95\x3d\x81\x04\x20\xc5\
+\x31\x17\x4d\x21\x96\x7b\x15\x59\xb1\x7c\x1e\x9d\xeb\x8e\x55\x6e\
+\x42\xce\x78\xe1\xb2\xd1\x80\x85\x88\x9b\xeb\x04\xe2\x71\xc9\xbe\
+\x03\x21\x85\x08\x77\x1e\xf8\x11\x32\xb9\xb6\x72\xc5\x54\x5a\xcc\
+\x46\xa5\xd4\xab\x4a\x5f\xd0\xb7\x95\x52\xcf\x2a\xa5\x32\x82\x85\
+\xf2\x11\x0d\x19\x60\xb6\x23\x67\xec\xc2\x88\x70\x83\xc0\x3f\x80\
+\x27\x91\xe5\xdd\x8c\x8c\x65\xfc\x3f\xb4\xf6\x1e\x56\xc8\xef\xf6\
+\xaa\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x04\xf4\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\
+\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x25\x00\x00\x80\x83\
+\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\x30\x00\x00\xea\x60\
+\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\x46\x00\x00\x01\x98\
+\x50\x4c\x54\x45\xff\xff\xff\x00\x00\x00\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\
+\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\
+\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\x39\x39\x35\
+\x39\x39\x35\x39\x39\x35\x39\x39\x35\xff\xff\xff\x47\xac\xbb\xfb\
+\x00\x00\x00\x86\x74\x52\x4e\x53\x00\x00\x2c\xb0\xf5\xfa\xd6\x87\
+\x23\x2e\xf1\xed\x92\x86\xb5\xfb\x8b\x0b\xc5\xe8\x1c\x1b\xa1\xdc\
+\x25\x75\x72\xfe\x5a\x3c\xe4\x0c\x28\xee\x7b\x04\x11\xe0\xef\x5b\
+\x45\x68\x33\x67\xa2\xd2\xfd\x99\xea\xd1\xae\x53\x40\x3b\x02\x6e\
+\xec\xad\x29\x17\xc0\x01\x0f\x3a\xeb\x9f\xa5\xf9\x07\xcd\xab\xe6\
+\x36\x7c\x54\xc8\xdd\xb1\x0d\xf7\x7a\x7e\xda\x20\x0e\xf8\x4f\x5d\
+\x10\x08\xcc\x62\xac\x34\x15\xc2\xbd\x9e\xa6\x26\x74\xf0\xaa\x1a\
+\xf2\x9b\xd3\x51\x46\x6d\x32\x60\xa0\xcf\x7f\xdf\x44\x73\x2a\x2d\
+\xf6\xe1\x70\xc6\x19\x9a\xde\x82\xb3\xfc\x91\xdb\x90\x24\xe7\xc8\
+\x3e\x7d\x00\x00\x00\x01\x62\x4b\x47\x44\x00\x88\x05\x1d\x48\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
+\x00\x9a\x9c\x18\x00\x00\x02\x37\x49\x44\x41\x54\x38\xcb\xa5\x53\
+\x57\x5b\xea\x40\x10\xcd\x22\x11\x1b\x22\x0a\x06\xc1\x80\x14\x41\
+\xc5\x0a\x82\x88\x11\x51\xec\x8a\x62\xc7\x5e\xb1\xf7\xde\xbb\xe7\
+\x77\xbb\x21\x51\xc2\xe7\xc3\x7d\xb8\xe7\x61\xe7\xec\xcc\xf9\x26\
+\x9b\x29\x0c\xf3\x2f\x10\x09\xaa\x1c\x35\x9b\xab\xc9\x13\x69\x9e\
+\x26\x97\x55\xe7\xa8\xe4\x80\x2c\xc8\x2f\x28\x2c\xd2\x16\xeb\xd8\
+\x12\xbd\xbe\x84\xd5\x15\x6b\x8b\x0a\x0b\xf2\xb3\x04\xa5\x65\x06\
+\x7a\x1a\xcb\xc1\x71\x28\x37\x52\x6a\x28\x2b\x55\x0a\x4c\xa8\x90\
+\xae\x66\x8b\xc5\x2c\xb1\x0a\x98\x14\x82\x4a\x48\x09\x79\xb5\xd5\
+\xaa\xe6\xa5\x8f\xa2\x52\x21\x30\xc3\x26\x9a\x2a\x3b\x67\x30\x70\
+\xf6\x2a\x91\xdb\x60\x56\x08\x1c\x70\xd2\xd3\x55\xed\xf6\xd4\xd4\
+\x78\xdc\xd5\x2e\x7a\x71\xc2\xa1\x10\xd4\xa2\xce\x5b\xdf\xd0\x08\
+\x19\x8d\x0d\xf5\xde\x3a\xd4\x66\x04\xb6\x26\xea\x6d\x6e\x81\xcf\
+\xdf\x1a\x08\xb4\xfa\x7d\x68\x69\xa6\x8e\x26\x9b\x2c\x08\xb6\x85\
+\x80\xf6\x70\x87\x5b\xe8\x94\x7e\xa0\x53\x70\x77\x84\xdb\x81\x50\
+\x5b\x50\x14\x44\x04\x08\x5d\xd1\x6e\xd2\x13\x0b\xcb\xc5\x23\xe1\
+\x58\x0f\xe9\x8e\x76\xd1\x40\x84\x30\xbd\x7d\xac\x97\x90\xfe\x81\
+\x41\x68\xc8\x2f\xb4\x18\x1c\xe8\x27\xc4\xcb\xf6\xf5\x32\x43\x18\
+\xa6\x1e\x0f\x46\x42\x91\x8c\x20\x12\x1a\x81\x87\xda\x61\x0c\x31\
+\xa3\x71\xd1\x33\x36\x8e\x04\x51\x20\x81\xf1\x31\xd1\xc6\x47\x19\
+\x4c\xa4\x3d\x93\x98\x52\x0a\xa6\x30\x99\xb6\x13\x90\x33\x4c\xcf\
+\xc8\x75\x91\xe1\xc0\xcc\xb4\x9c\x21\x81\x59\x4a\xe6\xe8\x1b\x92\
+\x99\x78\x92\xbe\x61\x8e\xda\x59\x24\x98\xf9\x05\x76\x91\x90\x25\
+\xeb\x32\xb4\xca\xbf\x58\xb6\x2e\x11\xb2\xc8\x2e\xcc\x33\x64\x65\
+\x15\x6b\xbc\x7d\x9d\x6c\xc4\x36\x7f\xe2\x9b\xb1\x0d\xb2\x6e\xe7\
+\xd7\xb0\xba\x22\x56\xd2\xb9\x95\x02\xb6\x55\x3b\xbb\x42\x50\x8a\
+\x07\x85\xdd\x1d\xd5\x36\x90\xda\x72\xca\xbd\x48\xee\xd1\xd2\x47\
+\xf7\xe1\x3b\x38\xe4\xf9\xc3\x03\x1f\xf6\xa3\xd4\xb1\x97\xcc\x74\
+\xf3\x08\xc7\x27\xa7\x67\xba\x9f\x6e\xea\xce\x4e\x4f\x8e\x71\xa4\
+\x68\xf7\x39\xc4\xd3\x75\x11\xf2\x5f\x5e\xfa\x43\x17\xe2\x3c\x10\
+\x9c\x2b\x04\x57\xd2\x44\x5d\xa7\x38\xa3\x91\x4b\x5d\x4b\x13\x75\
+\x95\x35\x93\x37\x69\x1b\xb8\xbd\xbb\xbb\x0d\xa4\xe9\x4d\xd6\x4c\
+\x9a\x64\x39\xb9\xb7\x58\xee\x89\x9c\x54\x39\xd5\xe4\x21\xbd\x17\
+\x8f\x4f\x96\xe7\x67\xcb\xd3\x63\x7a\x2f\x1e\xfe\x6c\xd6\xcb\x2b\
+\xfb\xf6\xae\xd7\xbf\xbf\xb1\xaf\x2f\x7f\x36\x8b\xa8\xe2\x6a\x7c\
+\x7c\x7e\x89\xf4\xeb\xf3\x03\xea\xf8\xef\x6e\xfe\x37\xbe\x01\xff\
+\x79\x87\x6d\x8b\x61\x54\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x15\x34\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xdd\x09\x18\x09\x3b\x1c\xdd\xab\x30\xa8\x00\x00\x14\xc1\x49\x44\
+\x41\x54\x78\xda\xdd\x9a\x79\x7c\x14\xe5\xfd\xc7\xdf\xcf\x33\x33\
+\x3b\xbb\x9b\x84\x9c\x10\x0e\x39\x14\x5a\x14\x05\x29\x49\x90\x42\
+\x2b\x9e\x94\x6a\xad\x3f\xfd\x59\x6b\x6b\x6b\xab\x62\x10\xb5\xaf\
+\x5e\xda\x7a\xf5\xa7\xb6\x56\xdb\x6a\x6d\x6b\xbd\x08\x1e\xd5\x22\
+\x5a\x0b\x5a\xc1\x2a\x1e\x88\xa8\x08\x84\x84\x9b\x08\x84\x3b\x40\
+\x8e\x4d\xb2\xd9\xcd\x66\xcf\x99\xe7\xf9\xfd\xb1\x93\x10\xd4\x5a\
+\x6d\x1b\xd0\x3e\xaf\xd7\xbe\x66\x32\x9b\x64\xe6\x73\x7c\x8f\xe7\
+\x99\x47\xf0\x09\x1b\x95\x65\x4c\x10\x82\xe9\x4a\x31\x36\x93\xe1\
+\xb8\x74\x9a\x62\x34\x01\xc0\x35\x4d\x3a\x2d\x1f\x7b\x0c\x83\x0d\
+\xc0\x1a\xe0\xc9\xaa\x5a\x54\x65\x19\x54\xd5\xfe\x6b\xf7\x13\x47\
+\x18\x2c\x55\xb5\x50\x39\x81\x81\x42\x72\x73\x57\x8c\xab\x0b\xfa\
+\xc1\xf8\x93\xa7\x30\xfc\x84\xcf\x73\xd4\x88\x61\x94\x0c\xee\x8f\
+\xcf\x2f\x51\xe9\x2e\x22\x6d\x4d\x34\xee\xd9\x49\x43\x5d\x0d\x75\
+\xab\xd7\xb1\x7d\x07\xf8\x03\xac\x96\x92\xdf\x57\xd5\x32\xef\x5f\
+\x21\xe2\x88\x12\x70\xc5\x04\x0a\x84\xe4\x89\x64\x27\xe7\x4c\xbb\
+\xf8\x3c\xa6\x7d\x63\x26\xfd\x47\x7d\xa9\xe7\x7b\x27\xd1\x40\x2a\
+\xba\x17\x95\x8e\x81\xd0\x58\xfe\x7c\x7c\x79\xa5\x48\x5f\x2e\xb8\
+\xed\xa4\x9b\x96\xf0\xc6\x73\x4f\xf3\xea\x82\xb7\x69\x0b\x93\xb0\
+\x2c\x2a\xab\x6a\x99\xfb\x71\x88\x10\x47\x4a\xf5\x99\xe5\x5c\x1b\
+\xef\xe4\xae\xb3\xbe\xf3\x0d\x2e\xba\xa1\x0a\xc8\x45\x65\x9a\x69\
+\xde\xf0\x34\xe1\x5d\xcb\xe8\xd8\xbb\x11\x69\x80\x9d\xe3\xc7\xb0\
+\x4c\xb4\x52\x64\x12\x19\xd2\xc9\x0c\xfe\x7e\x03\x28\x18\x36\x9e\
+\x82\xe1\x65\xf4\x1b\x3c\x01\x99\xde\x49\xf5\x0b\x0f\x31\x6f\xf6\
+\xab\x24\x53\x6c\x12\x82\xa9\x40\xfb\x47\x21\x41\x1c\x11\xcb\x97\
+\x89\x9a\x82\xa2\xfc\xb2\x9b\xe6\x2e\xa3\xdf\x80\x71\xa4\x3a\xd6\
+\xb1\xf7\xed\x5f\xd3\xb2\x65\x35\xa5\xa3\xfb\x93\x3f\x50\x92\x57\
+\x22\x31\x4c\x8d\xeb\x28\x54\x46\xa3\x5c\x85\x56\xd9\xf3\x78\x44\
+\xd3\x15\x16\x44\x5b\x32\x68\x6c\x06\x8e\x9d\x4e\xf1\xc8\x93\x51\
+\x9d\x1b\x79\xe6\x0f\xbf\xe6\x8d\xa5\xcd\xd8\x36\xa7\x55\xd5\xb2\
+\xf4\x13\x43\x40\x65\x19\x68\x4d\xd0\x71\xd8\x59\x71\xca\x49\x03\
+\x66\xfd\x61\xa5\x80\x0c\xbb\x96\x5c\x43\xd3\x86\x65\x0c\x2f\x2f\
+\x61\xe0\x68\x1f\xca\x51\xa8\x8c\x42\x29\x17\xed\x7a\xc0\x1d\x85\
+\x72\x34\x5a\x65\x09\x71\x1d\x8d\x76\x34\x5a\x6b\x12\x51\x88\x84\
+\x0c\x5c\xd7\xc7\xd0\x8a\x0b\xb1\x73\x06\x50\xb7\xf4\x3e\xe6\xdc\
+\xbf\x0a\xcb\xe2\x8a\xd9\x35\x3c\xfc\x61\xcf\x65\x1c\x46\xf0\x86\
+\x93\x61\xff\x99\x5f\x3b\xbb\xff\xa5\x77\xbe\x2e\x32\x5d\x3b\x58\
+\xf7\xa7\x33\x30\xad\x30\xe3\xce\x29\x26\xa7\x50\xa0\x5d\x85\x72\
+\x15\xa8\xac\xda\x68\x8d\x76\x15\xda\xd5\x68\xa5\xb2\xe7\x0a\xb4\
+\xab\x30\x04\xa0\x01\xa5\xf0\xe7\xb8\x48\x1c\x9a\xb6\xac\x45\xa3\
+\x18\x7a\xe2\xb9\x8c\x1b\x1d\x67\xc5\x5b\x3b\xbf\x5a\x31\x84\xdd\
+\xb5\x07\x58\x7f\xc4\x1d\x70\xc5\x04\xf1\xee\xc4\x53\x27\x8d\x9e\
+\x71\xf7\x3b\x22\xd9\xb1\x96\x75\x8f\x5f\xc4\x31\x53\x06\x32\x60\
+\x94\x0f\x37\xe5\x80\x3a\x08\x5e\x39\xdd\x60\xb3\xca\x2b\xf7\xa0\
+\x03\x92\x5d\x2e\x4b\x17\xb7\xb0\x7b\x67\x0c\x27\xad\x18\x32\x34\
+\x87\xb2\xf2\x42\x0a\x0b\x7d\xa4\x13\x9a\x48\x87\x4d\xb0\x70\x38\
+\x03\x8e\x3d\x8d\xa6\xcd\xf3\xb9\xe7\x57\x6f\x61\x9a\x4c\x02\xaa\
+\xab\x6a\xd1\x47\x84\x80\x99\x15\xdc\x5b\x3a\xa8\xe4\x7b\xb7\x3d\
+\x1f\x22\xdd\x59\xcf\x9a\x47\xbe\xc2\x71\xd3\x07\xd3\xaf\xd4\x04\
+\xc7\xc9\x5a\x5d\xb9\xe0\x39\x20\xeb\x04\x8d\x72\xbd\x30\x70\x14\
+\x96\x29\x78\x65\x61\x33\x0b\x9e\x6c\x24\xaf\x20\x70\x6f\xc9\xa0\
+\x51\xcb\x35\x22\x15\x6b\x3f\x50\x11\x6e\x0b\xdf\x70\xe2\xb8\xa0\
+\x38\xef\xbc\xc1\x22\x93\x56\x44\xa3\xb9\x04\x8b\x86\x52\x32\x72\
+\x22\x75\x6f\xce\xe5\xd1\x87\xb7\xe0\xf3\x11\x10\x82\xe4\x7b\x13\
+\xa3\xe8\x6b\xeb\x03\x63\xd2\x09\x36\x57\x55\xd7\x63\xe5\x0c\xa3\
+\xe6\xc1\x0a\x46\x4c\x2e\xa4\x64\x98\x85\x76\x5d\xb4\x72\xd1\xae\
+\x3a\x08\x5e\x79\x31\xef\x59\x3f\xab\xbe\x62\xc5\x1b\xed\x2c\x98\
+\xd7\xd6\x3a\xed\x5b\xd7\x8c\xfc\xda\x0f\xef\x8e\xbe\xf7\x5e\x57\
+\x7d\xde\xbf\xfe\x98\x11\xc6\xd8\x0b\x2e\x18\x22\x32\x19\x45\xa4\
+\x23\x97\xc2\x61\xc7\xd3\xaf\x64\x00\x7f\xb9\x7f\x8e\xde\xb0\x29\
+\xb6\xa4\xaa\x96\x33\xdf\xfb\x77\xb2\x2f\x09\xa8\xaa\x05\xa5\x78\
+\xee\x92\xeb\xae\xc0\xca\x19\xc5\xf6\xbf\x57\x52\x38\x22\x8f\x92\
+\x63\x7c\xa0\x5c\xd0\x59\xcb\x67\x03\x3b\xfb\xd1\xca\x45\x6b\xe5\
+\x7d\x5c\xb4\x52\x18\x86\x66\xee\x9c\xfd\x1c\x5b\x36\x65\xcc\xfe\
+\xfa\x0d\xef\x03\xff\xe4\x9d\xdf\xe3\x81\x15\xc9\x13\x37\x6f\x4e\
+\x3a\x4d\x4d\x09\x04\x9a\xfc\x82\x18\x6d\xbb\x36\x93\x49\x26\xb8\
+\xf0\xd2\xd3\x84\xd6\x9c\x51\x59\xc6\x14\x4f\x94\xbe\x4f\x82\x95\
+\x65\x50\x36\x98\xe9\xb6\xc9\xf7\xaf\xba\x7f\x15\xb1\xc6\x95\xec\
+\x59\xfe\x10\x63\xbf\x5a\x02\x19\xc7\x03\xec\x66\x09\x50\x59\xc0\
+\xb8\xae\x77\xd4\x5e\x22\xcc\x3a\x60\xe5\xb2\x30\x0d\x0d\xbe\x05\
+\xbf\x78\x76\xf3\x9f\x16\xaf\xde\xf1\xbe\x7b\x3d\xbb\xa4\x1a\x80\
+\xd3\xc7\x96\x1a\x99\x64\xd7\x29\x9f\xf9\x6c\x2e\xd2\xb2\xb0\xec\
+\x00\xd1\xb6\x76\xf2\x06\x8c\xc4\x2f\xf6\x53\xbf\x2d\x56\x31\x67\
+\x0d\x0f\x1e\x16\x07\x54\xd5\x82\xd6\xdc\x72\xfe\x55\x33\x00\x93\
+\x5d\x4b\x6f\x67\xe4\x17\xfb\xa3\xd3\xce\x41\xc5\x95\xf2\x9c\xe0\
+\x7a\xe0\x35\x28\x9d\x75\x82\xf7\xbd\xd0\x8a\xc6\x7d\x49\xb4\x1b\
+\xaf\xae\x5e\xfc\xe7\x0f\xbd\x67\x6e\x61\xf1\xaa\x03\x07\x52\xd8\
+\x41\x3f\x86\x69\x93\x93\xe7\xa0\x15\x24\xa3\x2d\x4c\x9d\x76\x02\
+\x5a\x31\xb6\xb2\x8c\xe3\x0f\x0b\x01\x57\x4c\x20\x2f\x19\x63\xd2\
+\xa9\xdf\xf9\x1e\xf1\xd0\x46\x12\xed\xbb\x28\x19\x65\x67\x6b\x57\
+\xb7\xfa\xbd\x88\xd0\x28\x84\x77\x4d\x7b\xe1\xd1\x1d\x02\x42\x80\
+\x52\xae\x51\xfb\xda\x5f\x3f\xf4\x9e\xb6\x6d\x19\xd2\x34\x90\xa6\
+\x8d\xb4\x6c\x90\x01\xf2\x8a\x03\x74\xb6\x36\xe3\x90\xcf\xe4\x29\
+\x79\x08\xc1\x35\x7d\x4e\x40\x65\x19\x48\xc9\x8c\xb2\x2f\xf8\x80\
+\x71\xb4\x6c\x9e\xcf\xe0\x71\xfd\x51\x49\x07\xa1\xdc\x83\xaa\xf7\
+\x8a\x7f\xd1\xa3\xba\x8b\xe8\x56\x5f\x65\xeb\xff\x90\xa3\x4c\x0c\
+\x2b\xef\xf4\x59\x77\x2f\xfc\xd0\xfb\xb6\x35\xed\x9f\x34\x62\x64\
+\x01\x4a\xf8\x90\xa6\x0f\xd3\xb6\xb1\x6c\x87\x8c\xeb\x47\xbb\x09\
+\x26\x4e\x1a\x42\x22\xc9\x8c\x3e\x27\xc0\x4b\x7e\xd3\xc6\x4d\xfd\
+\x1f\x00\x42\x75\x2f\x52\x3c\xcc\x3c\x18\xeb\x3d\xc9\x2f\x4b\x84\
+\x56\x87\xe6\x02\xa5\xbd\x1e\xc0\x73\xc4\x49\x93\xf3\x88\x86\xdb\
+\x4e\xbf\xe3\x92\xc9\xc7\xfe\xe4\x4b\x83\xde\x57\xb9\x9e\xfa\xd5\
+\x65\x68\xad\x7d\xe1\x50\xdb\x4d\x15\x9f\x1f\x8c\x34\x6d\x0c\x9f\
+\x1f\xc3\xf2\x63\x98\x3e\x72\x8a\x0b\x88\x85\xc3\x8c\x18\x99\x87\
+\x65\x62\x56\x96\x31\xa4\xcf\x43\x20\x91\xe0\xb4\x13\x2a\x2a\x70\
+\xd3\x51\x9c\x64\x1b\x81\x42\x99\x55\xb6\x1b\xb4\x3e\xd4\x05\xba\
+\xdb\x15\xda\x45\x28\x75\x48\x85\x48\x25\x1c\x2e\x9f\x59\x4a\xfd\
+\xba\x55\xeb\x07\x1f\xf3\x99\x51\x00\xd5\xaf\x3c\x96\xcd\x19\xc0\
+\xd9\x57\x5e\x15\x9c\x75\x92\x5d\x7f\xda\xf4\x11\x7a\xe0\x90\x42\
+\x0c\xcb\x46\x9a\x7e\x84\x69\x63\x58\x7e\x4c\x7f\x0e\xae\x93\x21\
+\x93\x91\x8c\x1c\x69\x21\x04\x67\xf4\x79\x1f\x70\xf1\x67\xd0\x73\
+\xdf\x7d\x9e\xc8\xbe\x7c\xf6\xbd\xf3\x43\xc6\x9c\x99\x8f\xce\x64\
+\x10\x38\x08\x32\x68\x57\x83\xd7\x03\x74\x37\x3f\xdd\x0d\x90\x76\
+\x7b\x77\x80\xa0\x95\x81\x90\x06\xeb\xd7\xc4\xf9\xd3\xc3\xfb\x28\
+\x28\x29\x59\x64\x59\x2c\xf1\x05\x82\xc9\x64\x57\xb2\xa2\xad\xb9\
+\xf5\xf2\x73\x2e\x18\xad\xcf\x38\xeb\x68\x91\xce\x48\x10\x12\x29\
+\x7d\x20\x25\xae\x12\x74\x46\x25\x4e\x34\x44\x71\x51\x07\x8b\x16\
+\xd4\xb1\x62\x45\xfc\xce\xd9\x35\xdc\x08\x60\xf6\x51\x0e\x18\xa4\
+\x32\x80\x51\x4a\x32\x52\x8f\x3f\xcf\x87\x76\xb2\xf6\x16\xda\x05\
+\x0e\xad\xfb\x42\x29\xb4\xd6\xd9\xda\xef\xcd\x03\xb2\xe2\x1a\x20\
+\x0d\x34\x06\xca\x35\x38\x7e\x9c\x9f\xdf\x3f\x34\x90\xe5\x6f\x85\
+\xcf\x09\x35\xa7\xcf\x51\x08\x4a\x07\xf6\xa7\x7c\xca\x04\x0c\xc3\
+\x10\x19\xd7\x44\x9a\x16\x42\x18\x60\x18\x08\x69\x21\x10\x20\x53\
+\x68\x99\x8b\x10\x2d\x14\x16\x59\x28\xc5\xa0\xee\x67\x35\xfb\xc8\
+\x00\xf9\xc1\x20\x80\x1f\xe5\x24\x30\x2c\x91\x05\xaa\xd5\x21\xe0\
+\xbb\x2b\x81\x46\xf5\xe4\x00\x81\x00\x61\x22\x84\x04\x69\x00\x06\
+\x42\x1b\x60\x48\x50\x26\x19\xd7\xe4\xa4\x29\x83\x41\x5a\x08\x69\
+\xa1\x31\x10\xd2\x44\x48\x33\x7b\x4d\x98\x20\x3d\xf0\xd2\x00\x61\
+\xa2\x45\x06\x61\x06\x11\x3a\x89\xdf\x06\xad\xc9\xef\x6b\x02\xbc\
+\xd0\x8a\x65\x0f\x5a\xf7\x24\x38\x81\x0b\xb8\x87\x26\x42\x95\x8d\
+\x65\x21\x2c\x10\x12\x2d\x24\x42\x1a\x68\xd7\x40\x0a\x03\x25\x0d\
+\xd0\xb2\x17\x40\x13\x81\x95\x3d\x0a\xcb\x03\x6f\x1c\x42\x82\xe8\
+\xfe\x59\x98\x68\xe2\x48\x53\x83\x4e\x22\x84\x3a\xe4\x41\xfb\x8a\
+\x80\x8e\x78\x1c\xa0\x19\xc3\x17\xc4\x49\xb9\x1e\x11\x2e\x90\x9d\
+\xc7\x0b\xed\x7a\x21\x81\xa7\x92\xec\x01\x0f\x06\x78\x36\x46\x19\
+\x08\x3c\x27\x08\x1f\x88\x2c\x28\xa4\x09\xa2\x1b\xac\xd9\x43\x06\
+\xd2\x04\x29\x91\x86\x95\xfd\xbf\x4a\xa0\xf1\x61\x9a\x49\x24\x69\
+\xd2\x69\x8d\x10\x74\xf6\x75\x19\x6c\x4c\x24\x81\x58\x3d\xfe\xfc\
+\x41\x24\xa2\x19\xa4\xe8\xce\xec\xdd\x0e\xf0\xf8\x17\x3e\xc0\x07\
+\xc2\x02\xe1\x43\x08\x1b\xa4\xdd\x73\x2e\xa4\x0f\x61\xd8\x08\x23\
+\x90\x3d\x9a\x7e\xa4\xe9\x47\x1a\xde\xd1\xf4\x7b\xd7\x02\x08\xcb\
+\x46\x58\x36\x86\xe9\x47\x18\xd9\x4a\xe0\x68\x13\x64\x00\x9f\x11\
+\x43\xe8\x34\x91\x88\x8b\x94\x1c\xe8\xf3\x32\x28\x0c\x32\xcd\x5b\
+\xdf\x21\x77\xd0\x78\x3a\x5b\x62\x08\xe1\x95\x3c\xc8\xda\x57\xf8\
+\x3c\x90\x1e\x78\x6c\xef\xbc\x9b\x04\x7f\x16\xbc\xf4\x23\x3c\xb0\
+\xc2\x0a\x20\x3d\x12\x84\x19\xf0\xae\xf9\x91\xa6\x8d\x30\x3d\x72\
+\x8c\x5e\x84\x98\x36\x19\x47\x62\x05\x4b\x90\x99\x06\xa4\xe1\x72\
+\xe0\x80\x0b\xb0\xbd\xcf\x09\xf0\x07\x78\xfd\xdd\xea\xb7\x90\x66\
+\x3f\xfc\xf9\x83\xe8\x6c\x15\x80\x99\x05\x89\xd9\xa3\xb8\xee\x06\
+\x2d\x7d\x68\x61\x23\x84\x8d\x96\x9e\xf2\xd2\xee\x51\x5d\x98\xb6\
+\x07\x3e\x70\x88\xf2\xc2\xf0\xc8\x30\xec\x1e\x57\x08\xef\x63\xfa\
+\x72\x48\x24\x1c\xec\xdc\x22\x54\xd7\x36\x82\x41\xd8\xba\xd5\x41\
+\x6b\x96\xf4\x79\x2b\x6c\x48\x5e\xd9\x50\x1d\x87\xf8\xdf\x28\x19\
+\x7d\x32\x1d\xcd\x16\x42\x58\x68\xb2\x80\xd1\x36\xf4\x38\xc1\x0f\
+\xbd\xc1\x0b\x0b\x21\x6d\xf0\x00\x0b\x33\x90\x0d\x01\x33\x80\x30\
+\xfd\xd0\xa3\xf2\xc1\x70\xe8\x71\x84\x19\x40\x7a\xbf\xeb\x28\x83\
+\x78\x97\x03\xc9\x36\x72\xf3\x32\x34\x34\x28\x52\x29\xa5\xab\x6a\
+\xd9\x7d\x38\x5a\xe1\x47\x6a\x56\xa5\xa0\xf3\x19\x4a\xc7\x9e\x47\
+\x53\x7d\x1c\x69\x5a\xd9\x78\xc7\x07\xd2\x02\x61\xf7\x8a\x7f\x1b\
+\xdd\xa3\xba\x1f\xa4\x8d\x90\x81\xac\xfd\xbb\x5d\x60\xf8\x11\x86\
+\xe7\x00\xc3\xce\x12\x61\x04\xc0\x03\x9e\xfd\xd9\x06\x8f\x94\x48\
+\x7b\x02\x3b\xa7\x14\x33\xfe\x0e\x86\x25\x58\xb9\x22\x43\x30\xc8\
+\x23\x87\x65\x36\x38\x67\x0d\x11\xdb\x4f\xcd\x1b\x7f\x7d\x19\x5f\
+\x20\x45\x5e\xe9\x31\xb4\x1e\xc8\xf5\x14\xb7\x3d\xc5\x7d\xd9\x84\
+\x27\xfd\xd0\x1d\xef\xc2\xf6\xe2\xde\x03\x66\x04\x3c\xd0\xdd\xd6\
+\xb7\xbd\xef\x02\x9e\xf2\xbd\x42\xa4\xc7\x01\x7e\x5c\x6d\xd1\xd1\
+\xd6\x49\x7e\xff\x81\xd0\xb1\x94\x9c\x1c\xc1\x6b\x4b\xd2\x68\xcd\
+\x03\xbd\x17\x45\xfa\x8c\x80\xca\x32\x84\x94\xdc\xf7\xca\xf3\x51\
+\x44\xf8\x57\x8c\x38\xf9\x6a\x1a\x36\x86\x7b\x92\x1f\xc2\x97\x05\
+\x2e\xec\x9e\x30\xe8\xb6\x7d\x56\x49\x7f\x8f\x95\xe9\xb6\x7d\xaf\
+\x30\x90\xdd\xa1\xd1\x6d\x79\x23\x90\x6d\x76\x0c\x3f\x86\x2f\x97\
+\xa6\x86\x10\x76\xfe\x10\x74\xd3\x02\x82\x79\x06\x6f\x2c\x75\xd1\
+\x5a\x6d\xab\xaa\x65\x6d\xef\x75\xc1\x3e\xcb\x01\x55\xb5\xe8\x74\
+\x9a\x3f\x9e\x75\xc9\xc5\xe8\x92\x2a\x6c\x73\x1f\x05\xc3\x4e\x60\
+\xdf\x8e\x7e\x60\x58\x20\xfc\xd9\x04\x28\xfd\x59\xc5\x3d\xf5\xbb\
+\xc1\x67\xc1\x74\x87\x40\x10\xf9\x1e\x47\x88\xee\x3c\x60\x04\xbc\
+\xf0\xc8\x5e\x33\xac\x1c\x42\x8d\x61\x92\x09\x45\x71\x91\x8d\x19\
+\x59\x48\x20\x20\x78\xea\xe9\x14\x52\x72\xe5\x7b\x97\xc4\xfa\x6c\
+\x32\x54\x59\x2e\x56\x9d\x58\x3e\xa2\xe2\xaa\x07\x77\x0a\x15\x7b\
+\x83\xcc\xae\x8b\xb0\x47\x3d\xc1\xc6\xf9\xb7\x33\xf4\x73\xa3\x29\
+\x28\x8e\xa1\x95\x89\xd6\x06\x68\x13\xc8\xf6\xfc\xda\xcb\x09\x5a\
+\x74\x57\x0a\xef\x28\x2d\xaf\x45\xee\x6e\x7a\x7a\x35\x43\xd2\x02\
+\x69\x20\x0d\x1f\xed\x2d\x61\x42\xfb\x9b\x18\x36\xa6\x9c\xf8\xaa\
+\x0b\xc9\xcb\x8d\xf3\xf4\xd3\x5a\x2f\x5f\x9e\x7c\xb3\xaa\x96\x53\
+\x0e\xcb\xb2\xf8\xcc\x72\x6e\x09\x06\xb8\xf5\x9e\x37\x3b\xd0\x4e\
+\x86\x64\xdd\x04\x8c\xc0\xf1\xf8\xe4\x2e\x18\x78\x3b\x1b\x16\xdc\
+\xcb\x31\x93\x3f\x47\x6e\x6e\x0c\xad\xac\x2c\x09\xc2\x44\x6b\xd3\
+\x2b\x8d\xbd\xc0\x7b\xad\xad\xee\x45\x46\x77\x8b\xdb\x0d\x1c\x61\
+\x22\x4d\x9b\x96\x7d\xcd\x84\x43\xed\x0c\x3f\xbe\x8c\xd8\xaa\xab\
+\xc9\xf1\xed\x60\xcf\x1e\xc1\x1d\x77\xc4\x09\x04\xc8\x05\xba\xfa\
+\x74\x59\xbc\xb2\x0c\x01\x94\xa7\x13\x54\xff\xf1\xf5\xe5\xe4\x96\
+\x4e\x26\xbe\xf9\x24\x24\x8a\x44\xc9\x0b\xdc\x7b\xf9\x14\xfa\x0f\
+\x35\x98\x75\xe7\x4f\xd8\xfc\xe2\x93\x0c\x9f\x38\x99\xbc\xbc\x68\
+\x96\x04\x4c\xaf\x27\xf0\xba\x43\x61\x78\xc7\xde\x4a\x9b\x68\x71\
+\xb0\xe5\xcd\x4e\x82\x6c\xd2\xa9\x0c\x4d\x7b\x0f\xe0\xa4\x1d\x86\
+\x8e\x99\x40\x64\xe5\x2c\x72\xcd\xad\x24\x12\x82\x1f\xff\x38\x81\
+\x65\xe9\xa9\xc0\x9b\x1f\xf4\xb2\xd4\xfc\x4f\xc6\x3d\x90\x93\x4c\
+\x50\x7d\xdd\xef\x6e\x22\xb7\x74\x32\xc9\x9d\x33\x20\xd3\x8c\x6f\
+\xcc\x6a\xe6\x54\x9e\x4a\x57\xa2\x95\xce\xba\x24\x22\xf3\x2a\x63\
+\xce\xfc\x0a\xf5\xcb\xfe\x4e\xc9\x71\x27\x53\x50\xec\x22\xa4\x44\
+\xf4\x56\x5f\x5a\x08\x4c\x74\xf7\x04\x48\xf4\x9e\xe8\x58\x08\xd3\
+\x26\xd9\x95\x24\xd2\xde\x42\x67\x38\x4a\x5e\xf1\x00\x06\xf7\x2f\
+\x26\xfa\xf6\x05\xe4\xd8\xed\x24\x93\x82\x1b\x6e\x48\xe2\xf3\xe9\
+\x6b\x66\xd7\xf0\xe6\x3f\x7a\x6e\xf9\x9f\xac\xfd\x5a\x8b\x35\xd3\
+\xce\x2b\xd7\x27\x4c\xbb\x9d\x74\x68\x2e\x99\xf6\x45\x04\xc7\xbe\
+\xc0\xd3\xb7\x5e\x4e\x28\xd4\x4a\x67\x24\xca\x0f\xee\x7b\x16\x3a\
+\x5d\xe4\x81\x9b\x19\x3d\x65\x2c\xa9\x96\x0d\xec\xdd\xd8\x40\x4a\
+\x1f\x85\x96\x01\x34\x3e\x32\x19\x99\xfd\x38\x06\x8e\x23\x71\x5c\
+\x13\xc7\x35\x48\xa7\x20\x11\xcb\x10\x0e\x45\xd9\x57\xbf\x97\xa6\
+\xbd\x8d\x48\x23\xc0\xd1\xe3\xa7\x10\x4c\xac\xa4\x6b\xf9\x99\x04\
+\xfd\xed\x84\x42\x92\x6b\xaf\x4b\x02\xea\xda\xd9\x35\xdc\x7f\x58\
+\xde\x0e\xcf\x2c\xe7\xd1\x92\x92\x9c\x4b\x6f\x7f\x29\x8a\x9b\xd8\
+\x49\x6c\xe3\xa9\x04\x87\x7d\x9f\x15\xaf\x39\xbc\x39\xff\x7e\x9a\
+\x9a\x43\x9c\x77\xd9\xa5\x9c\x7e\xfe\x78\xf4\x9e\xab\x10\x52\x42\
+\xda\x41\x05\xc6\xa0\x73\x4f\xa7\xa9\x7e\x0b\xae\x3d\x8a\x01\x63\
+\x26\x21\xad\x20\xca\x89\xa3\x95\x8b\xe3\x68\x32\x0e\xb8\x0a\xd0\
+\x12\x61\xd8\x98\xbe\x20\x76\xde\x00\xfc\x01\xe8\xaa\xff\x1b\x99\
+\xbd\x0f\xe1\x0f\xa6\x09\x04\x24\x8b\x16\xc1\xfc\xbf\xc6\x09\xf8\
+\x39\x77\x76\x2d\x0b\xfb\xfc\xf5\x78\x65\x19\x08\x38\xc7\xcd\xb0\
+\xf0\x81\xb7\xb7\x61\xe5\x1e\x4d\x64\x4d\x39\xa6\xff\x68\xc2\xe2\
+\x16\x1e\xbf\xe1\x2c\x5a\xda\x3a\x38\x76\xec\x67\x99\xf5\xe0\xb3\
+\x38\xd5\x23\x31\x4c\x0b\x81\x93\x5d\x0f\x29\xfa\x06\x9d\xa1\xfd\
+\x08\x3a\xc9\x2d\xf9\x2c\xad\x0d\xad\x60\x06\xd0\xfe\x11\x98\xb9\
+\x83\xf1\x17\x94\xe2\xcb\x2d\x40\x4a\x93\x4c\x32\x86\xd3\xd5\x8a\
+\xea\xda\x8e\xea\x58\x09\xd1\x0d\xf8\x73\x25\xb6\x0d\xdb\xb6\x09\
+\x1e\x79\x38\x45\xb8\xc3\xd9\x23\x04\x93\xe7\xac\x39\x38\xe3\xfb\
+\xb0\x61\xfe\x07\xe2\x7e\x50\x3c\xce\xc2\x5f\x3c\x31\x07\x2b\xef\
+\x33\x44\x37\x9d\x07\x5a\x22\x87\xce\xe1\xe9\x19\x15\x84\xa3\x31\
+\x72\x73\x4c\xae\xbc\xf7\x55\xd2\xd5\xc7\x20\x65\x00\xa1\xd3\xd9\
+\xf7\x03\xe6\x60\x3a\xfb\xdd\xcd\x03\xd7\x9e\xc4\xf6\x5d\x61\xee\
+\xba\x63\x0d\xf9\x85\x85\x08\xdf\x40\xa4\xdd\x82\x4a\xfb\x48\xed\
+\x4d\xd1\x95\x4a\x20\x74\x12\xd3\x54\x98\x66\x06\x54\x0a\x43\x26\
+\x30\x8a\x2c\x56\xae\x70\x58\xfc\xb2\xc3\x9e\x3d\x19\x15\x08\x70\
+\xf5\xc3\x6b\x79\xe8\xbd\xb5\xbe\x4f\x08\xe8\xde\xed\x71\xd9\x78\
+\xd6\x5f\x78\xc5\x57\x19\x5e\x3e\x83\xae\xdd\x77\x92\x89\xae\xa7\
+\x78\xd2\x0a\xaa\xae\x39\x87\x8e\xce\x2e\x12\x9d\x9d\xdc\xb8\x70\
+\x3b\xa9\xcd\x67\x20\x90\x08\x95\x41\x0b\x85\x00\xc4\x89\xb5\xcc\
+\x9b\x39\x8d\x48\x2c\x46\x7e\x6e\x86\xe0\xc9\xed\x88\x75\x45\x6c\
+\xd9\x10\xa7\x23\xac\x18\x32\x18\x8a\x8a\x24\x96\x6d\xe0\xba\x82\
+\x70\x87\xa2\xa5\x59\xb3\xb7\x41\xb3\x6d\x9b\x66\xd3\xa6\x34\x3e\
+\x1f\xef\x9a\x26\xbf\x7f\x62\x33\x55\xbd\xf3\x51\x9f\x13\x50\x55\
+\x0b\x33\x2b\xc4\x4b\x23\x8e\x29\xed\xff\x95\x1f\x3e\x4f\x3a\xb2\
+\x82\x78\xc3\x43\x14\x9d\xf8\x30\x0b\xef\xb9\x89\xa6\x03\xfb\x68\
+\x6b\x6d\x63\xc6\x6d\xf7\x91\x9b\xf8\x3d\x6e\x62\x2f\x52\x38\x20\
+\xbc\x25\xb0\x09\x2b\x98\xff\xcb\x59\x34\x87\xda\x88\xb4\x87\xf9\
+\xc9\x63\xab\x90\x3b\xbe\x4c\x63\x8b\xcd\x5d\x77\xa5\xf0\xfb\x59\
+\x94\x4e\x33\xd1\x71\x28\xed\xc9\xd8\x92\xa4\xcf\x47\xbd\x69\xb2\
+\x4a\x08\x6a\x02\x01\x9e\xaa\xaa\x25\xfa\x71\x14\xff\x8f\x11\x30\
+\xb3\x9c\x2b\x0d\xf4\xf4\xeb\xe7\xae\x47\x65\xda\xe9\xd8\x54\x49\
+\xa0\xf4\x42\xea\xaa\x5b\xd9\xf4\xce\xcb\xb4\x86\x5a\x99\xf2\xa5\
+\xb3\x28\x9b\x7a\x1c\xb1\xb5\xd7\x62\x1a\x16\x08\x6f\x19\xec\xa8\
+\x1f\x50\xfd\xc2\x3b\x6c\x5d\xbf\x86\x50\x4b\x0b\xe7\xce\xf8\x29\
+\x47\xf7\x7f\x8d\xd4\xae\xd5\xdc\x7e\xbb\x22\x18\xe4\xcb\x55\xb5\
+\x2c\xfe\x28\x0e\xfc\xb8\x8a\xff\xdb\x49\xd0\x6b\x76\x46\x27\xbb\
+\x78\xf7\xae\x45\x2f\x31\x60\xd4\x74\x42\xd5\x67\x20\x84\xc4\x38\
+\xea\x51\x66\x5f\x3d\x89\x50\x7b\x84\xc2\xa2\x42\x6e\x5e\x50\x4b\
+\x64\xd9\x08\x4c\xcb\x87\x24\x8d\x14\x2e\x46\x70\x14\xed\x05\x0b\
+\x78\xf4\x47\xa7\xd3\x14\x6a\x67\xe4\xb1\x63\xf9\xde\xec\xc7\x60\
+\xfd\xf1\xdc\x7c\xa3\xa0\xb5\x55\x3f\x38\xbb\x86\xab\x0e\xd7\xce\
+\x15\xf3\xe3\xc6\xbd\xd6\xc8\x4c\x86\x35\x33\x6e\xbc\x8a\x01\xa3\
+\xa6\xd3\x5e\xf7\x03\xdc\x54\x1b\x25\x15\xaf\xf1\xe0\xcc\x93\xe9\
+\xe8\x8c\xe3\xa4\xe2\xfc\xe8\xb1\x5d\x44\x96\x4f\x44\xc8\x1c\xb4\
+\x4a\xa1\x45\x76\xe5\x57\x8f\x7e\x9d\xa7\xae\xf8\x02\xed\x91\x18\
+\x01\xdb\xc7\xac\x3f\x2e\x82\xb5\x83\x78\x72\x9e\xd4\x2d\x2d\x6a\
+\xeb\x9c\x35\x87\x0f\xfc\xc7\x6e\x84\xaa\x6a\x41\x48\xb1\xba\x7c\
+\xf2\x71\xfe\x2f\x7e\xfb\x7e\xba\x1a\xe7\x93\x68\x7a\x91\x81\x93\
+\x16\xf0\x97\x5b\xbf\x45\x5b\xb8\x83\x8e\xf6\x30\xdf\x7f\x70\x19\
+\xce\x8e\x4b\xd1\x6e\x2a\xfb\xaa\x4b\x2b\x70\x12\xd8\x93\x36\xf2\
+\xf4\xff\x7d\x9d\xb6\x70\x94\x58\x24\xc2\x0f\x1f\x5d\x87\xdc\x32\
+\x99\x4d\x75\x82\x57\x5e\x56\x0a\x98\xf0\xef\xc4\x73\x9f\x13\x30\
+\xb3\x9c\x3b\x73\x02\xc6\xe7\xae\x7e\x68\x8d\x70\xe2\xbb\x68\xaf\
+\xbb\x85\x82\xcf\xde\xc0\xb2\x79\x8f\xb3\xa7\xfe\x5d\xda\x5a\x5b\
+\xf9\xea\x65\x3f\x66\x48\xf1\x3a\x32\xe1\xd5\xa0\x32\xde\x0b\x10\
+\x8d\x3d\xf8\x02\x6a\x17\xdc\xcf\xf6\x2d\xbb\x09\x85\x42\x7c\xeb\
+\x86\x07\x19\xa0\x6f\xa7\x2b\xb6\x93\xdf\xde\xe5\xe2\xf7\x53\xf1\
+\x41\x7b\x78\x3e\x11\x21\xe0\xc5\xfd\xe4\x54\x9c\xeb\xef\x7a\xa1\
+\x06\xad\x4d\x9a\x6a\xbe\x8b\x5d\x38\x89\xf6\x8e\xd1\x2c\xff\xdb\
+\xcf\x08\xb5\x75\x30\xf2\xb8\xb1\x9c\x55\x79\x25\x2d\x4b\x2a\x30\
+\x2c\x7f\xcf\x36\x97\xdd\xfb\x5c\x6e\xfd\xee\x7c\x8a\x4b\xa0\x74\
+\x00\xf4\x2f\xb6\xf1\xfb\xd6\x91\x6c\x78\x84\x5b\x6e\x06\xdb\xe6\
+\xfa\xd9\x35\xac\xe5\x08\x0c\xf1\x51\xe2\x1e\xc8\x4f\x26\xe9\xf8\
+\xd1\x5d\xbf\xe1\xc4\x2f\x5f\x47\x63\xf5\x25\x38\x89\x7d\x94\x8c\
+\x7f\x86\x07\x2a\x2b\x68\x6e\x0d\x83\xd6\xdc\xf1\xe2\x4e\x5a\x97\
+\x8d\x47\x4a\x30\x44\x1a\x83\x34\x4a\x3b\x5c\x7b\x6d\x0c\xcb\xe2\
+\x24\x60\xa7\x10\x9c\xa6\x35\xe5\xae\xcb\xe4\x44\x82\x29\xfd\xfa\
+\xf1\xda\x43\xab\xdf\xbf\x79\xe9\x13\x35\xae\x98\xc0\xae\x27\x6e\
+\x3c\x45\x6b\xad\x75\xdb\x96\xdf\xe9\x9d\x8b\x3f\xa7\xb5\xd3\xaa\
+\xe7\xcc\x9a\xa8\x7f\x76\x56\xa9\xbe\x72\x02\xba\x75\xff\x1e\xdd\
+\xf4\xfa\x24\xdd\xf4\xf2\xd1\xba\xf9\xe5\x41\xba\xed\x95\x62\x1d\
+\x7f\xbb\x40\x5f\x7b\xaa\xd4\x33\xcb\xf9\xc5\x47\x20\xf9\x88\x0c\
+\xf3\x23\xc4\xfd\x93\xc5\x25\x85\x23\xbe\xfd\xcb\xa5\x24\xda\x6b\
+\x68\xdf\xf1\x30\xc3\x26\x3f\xc2\xc2\x7b\x7e\x44\x73\x53\x13\xa1\
+\x96\x66\x2e\xfb\xf9\xe3\x88\x96\xdf\xe2\x26\x43\x48\xe1\x20\x85\
+\xc2\xf2\x69\x1e\x7b\x34\xae\xa3\x51\x55\x5b\x55\xcb\xcf\xfe\x59\
+\x72\x3d\x52\x43\xfe\x13\x65\x2e\x70\xd3\x7c\xf3\x96\x67\xd6\xe1\
+\xa6\x3b\x38\x50\xfd\x7d\xf2\x87\x5f\xcc\x96\xea\xcd\xd4\xad\x7a\
+\x93\x96\x50\x88\x49\xd3\x2e\xe0\xc4\x89\xa5\xc4\xf6\x3e\xdb\xb3\
+\xf5\x45\x2b\xc5\x9a\xda\x0c\xab\x57\xa7\xe3\x8e\xc3\xc4\x23\xa9\
+\xf0\xbf\xe4\x00\xef\x81\x8f\x4a\xc6\xf9\xeb\xad\x4f\xcc\xc3\x97\
+\x37\x8c\x3d\x6f\x9c\x8f\x19\x18\x8a\x55\x74\x11\x8b\x6f\x99\x4a\
+\x5b\x7b\x84\xa2\xe2\xfe\x7c\xe7\xb6\xfb\xd9\xfd\x42\x19\x81\x80\
+\x1f\xbf\x2f\xbb\x2b\xab\xb9\xd9\xe5\xd1\x3f\x75\xe2\xb7\x99\x70\
+\xa4\x15\xfe\xd8\x04\xf4\x9e\xe4\x7c\xad\xf2\x9b\x1c\x5d\xfe\x0d\
+\x9a\xd6\xde\x40\x26\x19\x66\xf8\xd4\xf9\xcc\x9e\xf5\x45\x22\xb1\
+\x04\xc9\xae\x4e\x6e\x7b\x6e\x27\x0d\xaf\x9e\x41\xf5\xca\x14\x9b\
+\x37\xb6\x10\x6a\xcd\xd0\xd9\xa9\x91\x12\x02\x01\x2e\x9e\x5d\xc3\
+\x36\x3e\x8d\x63\x66\x85\x78\xfd\xce\x8b\x86\x6b\xad\xb5\x8e\x34\
+\x3c\xaf\xb7\x3e\x3f\x5e\xab\xf4\x7e\xfd\xe4\xf5\x67\xea\xdb\xce\
+\x3d\x4a\x57\x8e\x47\xef\xda\x58\xa3\xf7\x2e\xf9\x5f\xfd\xda\x6f\
+\x86\xe9\xef\x9c\x40\xa2\xb2\x8c\xb3\x2a\xcb\x18\xf9\x69\xc3\x6a\
+\x7e\x40\xd2\xfb\x81\x81\x3e\xf5\xfa\xb9\x6b\xc9\x74\xed\xa5\x79\
+\xfd\x6d\x0c\x1c\x7f\x33\x6f\xcd\xfb\x23\x7b\xb7\xef\x20\xd4\xdc\
+\xcc\xb9\x33\x6f\xa2\x9f\x5c\xce\x81\x03\x1b\x99\xfb\xe7\xbd\xd8\
+\x36\x93\x80\x0d\x1f\xb4\x1b\xfb\x53\x43\x80\xd7\xec\x9c\x90\x88\
+\xf1\xbb\xbb\x5f\x58\x8a\x96\xf9\xec\x5b\xf9\x75\x82\xfd\xa7\x12\
+\x89\x14\xb2\x62\xd1\x53\xb4\xb4\xb6\x31\xf2\xf8\x32\xa6\x7f\xfb\
+\x12\xb6\x2d\x3c\x9b\xda\xd5\x51\x7c\x3e\x66\x57\xd5\xfe\xe3\xfd\
+\xf8\x9f\xf4\x21\x7b\x25\x3d\x2b\x9d\xa6\xa6\xf2\x67\x3f\xa1\xff\
+\xc8\x53\xd8\xbf\xb2\x12\xb0\x29\x3a\xf6\x7a\xe6\xdf\x71\x39\x1d\
+\xd1\x4e\x0c\xc3\xe0\x9a\xfb\x5e\x64\xeb\xc2\xf3\xb1\x7c\x39\x34\
+\x34\x74\x21\x04\x35\x9f\xe4\x2c\xff\x91\x08\xf0\xb2\xf4\xa3\x27\
+\x4d\xad\xb0\xa6\x7c\xf3\xd7\xb4\x6d\xbb\x8f\x44\x78\x33\x47\x9f\
+\xf2\x04\xf3\x6e\x3c\x9b\x48\x2c\x45\x7b\xa8\x9d\x1b\xe7\xd5\xb1\
+\xe3\xc5\xaf\x81\xf4\xe3\xf3\xc1\x9e\xdd\x5d\x68\xcd\xe2\x4f\x72\
+\x96\xff\xc8\x21\x20\x04\x5b\xb4\x90\x22\xdd\xb9\x85\xb6\xad\x73\
+\x18\x71\xf2\x5c\x5e\xf8\xdd\xd5\xb4\x34\xb5\xd1\xd4\xd8\xc8\x95\
+\xbf\x79\x86\xe8\xd6\xdf\xe2\xa4\xa2\x48\x1c\xc2\x6d\x49\x52\x29\
+\xf8\x73\x1d\xfb\xe0\x53\xee\x00\x00\xad\x79\x69\xd3\xaa\xd5\x84\
+\x37\x5f\x47\xd1\xa8\xcb\xd8\x56\xfd\x16\x75\xd5\xd5\xb4\xb4\x34\
+\x33\xe5\xec\x8b\x19\x39\x3a\x87\xc8\xee\xa5\xd9\xcd\x8d\xca\x65\
+\xf7\xee\x18\xb6\xcd\x72\x3e\xe5\x43\xf6\x6a\x47\x6b\x3b\x3b\x15\
+\x5d\x5d\x45\x98\x85\xd3\x59\x3c\xe7\x37\xb4\xb6\xb7\x51\x50\x32\
+\x90\x6f\xdd\x74\x0f\x3b\x5f\xff\x69\xf6\x8d\x8d\x72\x31\x84\xa2\
+\xf1\x40\x12\xc3\xf8\xc7\x6f\x5c\x3e\x95\x65\xd0\x1f\xe0\x8d\xfa\
+\x7a\xf3\x94\x9d\xf3\xce\x27\x12\x4b\x90\x88\x45\xf9\xf9\x73\x3b\
+\xd8\x34\xff\x6c\xfc\xc1\x7c\x9c\x74\x8a\xed\xdb\x22\xec\xda\x15\
+\x61\xeb\xd6\x8e\x43\xf6\xda\xfc\x57\x10\x20\x25\xaf\xac\x7a\xe9\
+\xf9\x53\x20\x87\xe6\xc6\x16\xfe\xef\xf1\xe7\x59\x3b\xff\x52\x36\
+\xaf\x3e\x40\xfd\xb6\x10\x8d\x8d\x09\x6c\x9b\xb5\x86\xc1\x73\x5a\
+\xb3\xa0\xaa\x96\xba\x4f\x3b\x01\xe2\x3d\x6d\xf0\xb8\x60\xd0\x5c\
+\x1f\x08\x04\x30\x4d\x41\xc3\xee\x28\x18\xc4\x7c\x3e\x9e\x04\x5e\
+\xae\xaa\xe5\x39\xfe\xcb\x86\xf8\x80\xb9\xc0\x06\xa5\x68\x30\x0c\
+\x5e\xd5\x9a\xbf\x54\xd5\xd2\xf8\xde\x65\xe8\xff\xa6\xf1\xff\x5d\
+\x51\x20\xfa\x91\x10\xee\xb4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x03\xb8\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8d\x89\x1d\x0d\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x35\x49\x44\
+\x41\x54\x38\x8d\xad\x94\xcf\x6b\x1b\x47\x14\xc7\x3f\x33\xbb\xb3\
+\xb3\xb2\xa8\x6d\xc9\x32\x0e\x34\x31\xb6\x83\xdb\x9e\x52\x72\x30\
+\x14\x4a\x82\xc1\x85\x1e\x4c\x2f\xbd\xf6\xee\x53\xff\x81\x40\x28\
+\xa5\x04\x7a\xe8\x7f\xe0\x53\xef\x39\x95\xe2\x5b\x0d\xc6\xa5\x14\
+\x5c\x1a\x35\xb4\x87\x52\xb0\x2c\xb7\xb8\x8e\xe3\xac\xa4\x95\x25\
+\x4b\xde\x5f\xaf\x07\xed\x0a\x47\x49\x6e\x1d\xf8\xee\x8f\x79\x6f\
+\x3f\xbc\x99\xf7\x9d\x55\x22\xc2\xff\x39\xdc\x37\x05\x1e\x2a\x75\
+\x5f\xc3\x86\x03\xeb\x29\xdc\x05\x70\xa0\x9e\xc2\x5e\x06\xbb\x8f\
+\x44\xf6\x5f\xf7\x9d\x9a\xac\xf0\x81\x52\x15\x0b\xdb\x5e\xa9\xb4\
+\xb9\xb4\xbc\x6c\x66\xab\x55\xf7\xad\xb9\x39\x00\x2e\x82\x80\x4e\
+\xbb\x9d\x34\x1b\x8d\x38\x1a\x0c\x76\xae\x60\xeb\x6b\x91\xf6\x1b\
+\x81\x5f\x28\xf5\x21\xf0\xfd\xca\xca\xca\xd4\x7b\x77\xee\x58\x0d\
+\x78\x73\x73\x68\x63\x10\x11\xb2\x38\x26\x6e\xb5\xc8\x94\xe2\xcf\
+\xa7\x4f\xaf\x1a\x8d\xc6\x25\xf0\xc9\x57\x22\x3f\xbd\x02\x7c\xa0\
+\x54\xc5\xc0\xe1\xda\xda\x5a\xa5\x56\xa9\xa0\x4b\x25\xcc\xf4\xf4\
+\x28\x4b\x04\x11\x81\x5c\x71\x18\x22\x69\x4a\xd0\x6e\x73\x70\x70\
+\xd0\x8e\xe1\x76\x51\xa9\x2e\xc8\x16\xb6\x97\x6e\xdd\x9a\xaa\x96\
+\xcb\x0c\x4e\x4e\x90\x24\x21\x6a\xb5\x88\x82\xe0\x15\x65\x71\xcc\
+\xe5\xd1\x11\x95\x72\x99\xa5\xc5\xc5\x29\x0b\xdb\x05\x47\x17\x0d\
+\x30\xd6\x6e\xae\xae\xae\xda\xb0\x5e\xc7\xb1\x96\xb4\xd5\x22\x0d\
+\x02\x24\x08\xc8\x8a\x7b\xab\x45\xda\x6a\x91\x04\x01\xca\x75\x09\
+\x9f\x3c\xe1\x9d\xd5\x55\x6b\xac\xdd\x7c\xa8\xd4\xfd\x71\x97\x35\
+\x6c\xbc\xbd\xb0\x60\x92\xd3\x53\x08\x43\x74\x18\xa2\x1c\x07\x9d\
+\x2f\x51\x15\xfb\x2c\x42\x26\x82\x64\x19\x3a\x49\x90\x30\x24\x3a\
+\x3d\xe5\xe6\x8d\x1b\xe6\xf0\xf8\x78\x03\xd8\x77\x73\x3b\xac\xcf\
+\x96\x4a\xae\xea\x74\xb0\x80\x3a\x3b\x43\xfb\x3e\x3a\xcb\x50\xd7\
+\x80\x99\x08\x64\xd9\x88\x1d\x45\xa3\xdc\x4e\x87\x99\xe9\x69\xd7\
+\x81\xf5\x71\x85\x19\xdc\x9d\xf5\x7d\x9c\x93\x13\x5c\xc0\xed\xf5\
+\x70\xfa\x7d\x1c\x11\x14\xa0\xf2\xfd\x91\x51\x2e\x69\x3e\xa7\x80\
+\xac\xdb\x65\x66\x61\x81\xc2\xab\x63\x63\xeb\x24\xc1\x88\xa0\xf3\
+\x49\x57\x04\x27\xdf\xe4\xeb\xc0\x34\x57\x92\xc7\x32\x11\xa2\x38\
+\x1e\xe7\xe8\xfc\x52\xef\x5d\x5c\xe0\xf9\x3e\xde\xa8\xe3\x58\xc0\
+\xcf\x55\xba\xf6\xec\xe7\x31\x0f\x30\x80\xe7\xfb\xf4\xba\x5d\x34\
+\xd4\xc7\xc0\x14\xf6\xda\xbd\x5e\x6c\x3c\x0f\xb7\x48\x7c\x0d\xb4\
+\x00\x15\x30\x03\x18\x6b\xe9\xf4\xfb\x71\x0a\x7b\x63\x60\x06\xbb\
+\xcd\x6e\x37\xc1\x5a\x5c\x6b\x71\xf2\x65\x9b\x09\x68\x01\x74\x0b\
+\xf9\x3e\xe2\xfb\x34\xc3\x30\xc9\x60\x77\x0c\x7c\x24\xb2\x1f\x27\
+\xc9\xce\xaf\xe7\xe7\x43\x53\xad\xa2\x95\xc2\x19\x75\x1f\x77\x02\
+\xa4\x0b\x29\x85\x5b\xad\xf2\xdb\xf3\xe7\xc3\x38\x49\x76\x8a\x9f\
+\xc5\xf8\xa4\x5c\xc1\xd6\x51\xbf\x3f\x68\x0e\x06\xe8\x5a\x0d\x8c\
+\x41\x26\x1a\x91\xe5\xef\x18\x83\x9e\x9f\xe7\x78\x30\xa0\xd1\xeb\
+\x0d\xaf\x60\xab\xe0\x8c\xcf\xb2\x52\x6a\xea\x33\xf8\x78\x05\xbe\
+\xbd\x6d\xad\x7f\xaf\x5c\xf6\x4a\x51\x84\x9b\x24\x23\x13\x03\xe2\
+\xba\xc4\xc6\x30\xf0\x3c\x7e\xec\xf7\xe3\xc3\xe1\x70\xf8\x3b\x7c\
+\xfe\x1d\xfc\x00\xbc\x10\x91\xe8\x3a\x70\x06\xa8\x2d\xc2\xf2\xa7\
+\xf0\x65\x55\xa9\xb5\x77\x3d\xcf\x59\xd0\xda\xa9\xb9\x23\x77\x05\
+\x69\xca\x69\x9a\x66\x7f\x45\x51\x7a\x2e\x52\x7f\x0c\xdf\x3c\x83\
+\x26\xf0\x02\xf8\xf7\x25\x60\x0e\x9d\x06\x6a\x40\xe5\x23\xb8\x77\
+\x13\x3e\x98\x87\xf7\x7d\x58\x02\x18\xc2\xdf\xcf\xe0\x8f\x7f\xe0\
+\x97\x3d\xf8\x19\x08\x81\x73\xe0\x4c\x44\x92\x97\x96\x7c\x7d\x28\
+\xa5\x9c\xdc\x29\x85\x2c\x23\x7f\x27\x23\x2e\x43\xe0\x12\xb8\x92\
+\x09\xc0\x7f\x02\xdb\x94\xe6\x90\x75\x0b\x12\x00\x00\x00\x00\x49\
+\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x06\x9b\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x06\x18\x49\x44\
+\x41\x54\x48\x89\x95\x93\x6b\x50\x94\xd7\x19\xc7\xff\xe7\xbd\xb0\
+\xfb\xee\x05\x96\xcb\xc2\x2e\x2c\x2e\xa2\x55\x02\xad\xc1\x78\x69\
+\xc1\x3a\x20\x66\x12\x31\xd1\x68\x30\x1d\x33\x6d\x3f\x74\x3a\x63\
+\x32\x4e\xa7\x69\xad\x89\xa2\x8d\x88\x06\x04\x04\x4d\x26\x6d\xe2\
+\xe4\x43\x62\x4c\x67\x52\x63\x12\xab\xc8\x02\x51\x47\xcd\x70\x89\
+\x86\x28\xf1\x82\xc6\x5a\x59\x0b\xbb\xac\x5b\xd8\x5d\x97\x5d\xde\
+\x77\x79\x2f\xa7\x1f\xba\x38\xa2\x90\xb6\xcf\xcc\xff\xcb\x79\x9e\
+\xf3\xff\x9d\xe7\x3c\xe7\x80\x52\x8a\xa9\xb4\xfd\xf5\xad\xfc\x74\
+\xb9\xff\x47\x84\x52\x8a\x87\x63\x47\xf5\x76\x1b\x21\xe4\x2b\x4a\
+\x71\x35\x16\x93\x7e\x53\x5f\xdb\xe8\x7e\xa4\xe8\x7f\x8c\x47\x00\
+\x3b\x77\xef\xb0\x31\x0c\x39\xbf\xfa\xd9\xd5\x0e\x8b\x25\x85\x1c\
+\xfa\xe8\xa0\x24\x8a\x62\xfd\x9e\x9a\x86\xea\xe9\x4c\x1a\x1b\x6b\
+\xe7\x81\x30\x07\x65\x42\xd7\x57\x6e\xaa\xbc\xf9\x60\x8e\x99\xa2\
+\xde\x4a\x29\xb5\xf8\x87\xef\x12\x41\xd0\x93\x4d\xbf\xdf\x2c\x64\
+\x64\x64\xbc\xf6\xea\xd6\x3f\xfc\x6e\x2a\xf3\xfa\xa6\xda\x72\x93\
+\x39\xa9\x7b\xed\x9a\x8a\x42\x3d\xc3\x77\xd6\xd5\xd5\xe5\x4e\x09\
+\xa8\xa9\xdb\x65\xdb\x5d\xbb\x33\x7b\xe7\xeb\xbb\xae\xa8\xaa\x5a\
+\xd2\xd9\xd1\x25\xfd\xc3\x7d\x0b\x3e\xbf\x17\x15\x15\x2f\x18\x38\
+\x8e\x7b\xf9\x61\xf3\x86\xa6\x9a\x5f\x26\x27\x25\x7f\xba\x7e\xfd\
+\xcf\x0d\x26\xb3\x89\x94\x95\x95\xa5\xf0\x3a\xa6\xab\x66\x5f\x4d\
+\xf6\x24\xc0\x9e\x86\x37\x6c\x1c\xc7\x9d\xe7\x38\xee\xab\x9d\xbb\
+\x77\xd8\x01\x7c\x4b\x08\x89\x52\x4a\xa1\xaa\x0a\x0c\x82\x01\x00\
+\x1e\x19\x16\x01\x33\xbb\xb8\x78\x89\x30\x1a\x09\x22\x32\x16\x46\
+\x62\x92\x99\xc9\xc9\xc9\xb1\xea\xc0\x36\x4f\xd4\x70\xf5\x8d\xb5\
+\x3c\xcb\xb2\xdd\x2b\x9e\x2e\xcf\x92\x24\x09\xed\x5f\xb4\xf7\xb1\
+\x2c\x13\x30\x1a\x0c\x82\xd9\x64\x82\xd9\x94\x04\xf7\x1d\xb7\xa6\
+\x28\xca\xb1\x07\xcd\xab\xdf\xae\x4e\x34\xb1\xc2\xf3\x3c\x9f\x40\
+\x29\x40\x44\x49\x44\x30\x10\x42\xbf\xbb\x5f\x94\x35\xfa\xd2\x7d\
+\xc0\x96\xcd\xdb\xe4\xc6\xfd\xf5\x37\x63\x31\xc9\x69\x34\x0b\x64\
+\x59\x59\x89\x45\x96\x15\x4b\x72\xb2\x05\x0c\xcb\x20\xd1\x9c\x84\
+\x53\xa7\x4e\x4a\xaa\xaa\xba\x27\x99\x2b\x42\x47\x69\x49\x69\x5e\
+\x4a\x4a\x32\x73\xe6\xec\xe9\x71\x4d\x03\x19\xf4\x0c\xc8\x9a\xac\
+\xae\xad\x7c\x6d\xdb\xf9\xfb\x5d\x52\x4a\xb1\xa7\xe1\x8d\x02\x96\
+\x65\x2f\x3c\xbd\xe2\x29\x03\xcb\xb2\x93\xae\xc1\x91\xe9\x84\xaa\
+\xa8\xd8\xf7\xe6\xbe\x68\x34\x1a\x59\x97\x99\xe1\xe8\x64\x79\x74\
+\x94\x96\x94\xe6\x67\xcf\x98\xc1\xb5\xb8\x9a\xc5\x40\x20\xd0\xad\
+\x28\x4a\x8b\x46\x99\x1b\x95\xaf\x56\xba\x26\x5d\xe3\xc4\x33\x6d\
+\x68\xda\xf3\xd7\xf4\x74\xeb\xea\xc2\xf9\x85\x42\x24\x12\xc1\x90\
+\xd7\x47\xfd\x7e\x7f\x6c\xd1\xe2\x85\xfa\x5c\xe7\x6c\xb0\x2c\x87\
+\xa6\xfd\x8d\x63\x49\x89\x89\xbe\xb2\x65\x65\x33\x1c\xd9\xd9\x5c\
+\x4b\x6b\xb3\x18\x0c\x84\x3a\xcc\x06\xcb\x33\x1b\x36\x6c\x90\x1f\
+\x9e\xd1\x24\x40\x4d\xdd\x2e\x3d\xcb\xb2\xae\xf1\x71\xb9\x18\xa0\
+\x21\x4d\xd3\x8e\xc9\xb2\x12\x4e\x48\xe0\x37\x96\x97\xaf\x30\x64\
+\xa4\xdb\xd1\xde\xde\x86\x27\x9e\x58\x00\x87\xc3\x01\x57\xeb\x09\
+\x31\x18\x0c\x7d\x69\x36\x58\x56\x4d\x67\x3e\x09\x30\x5d\x6c\xdf\
+\xb1\x75\x33\xc7\x72\xd5\xf6\xcc\x4c\xc3\xe2\x45\x8b\x61\xb7\xdb\
+\x71\xc2\xd5\x8c\xc0\x48\xb0\x47\x8a\x8e\x17\x55\x55\x55\x29\xdf\
+\xb7\x7f\xaa\x8f\x36\x29\xbc\xca\xdc\x0f\x8d\x26\x53\x70\xe1\x82\
+\x45\xb0\xd9\x6c\x68\x6b\x6b\x45\x6a\x8a\x15\x23\x23\xc3\x05\x5e\
+\xdf\xe0\x93\xff\x6d\xff\xb4\x1d\x90\xea\x6a\x66\xd1\xf0\xd2\x3f\
+\x6a\x94\x56\xbd\xb2\x5c\x53\xcb\x7e\x92\xc7\xbb\x5c\x2d\x90\x15\
+\x05\x73\xf3\x1e\xc3\xbc\x82\x1f\x61\x6f\x63\x83\x18\x0c\x06\x7e\
+\x0b\x42\x9c\x2c\xc3\xae\x53\x55\xc5\x28\xcb\xca\x47\x07\xdf\x3f\
+\xb4\xfd\x7b\x01\x3f\x7c\xe9\xd4\x0c\xa3\x8e\x3d\xea\x48\x4b\x28\
+\x5c\x55\x64\x67\xfe\xfc\xe9\x25\x94\xcf\x19\x91\x8c\xea\xf0\xad\
+\xe1\x91\x91\x59\xcf\xad\x5d\x25\xd8\x32\x6c\xb0\x24\xa6\xe2\x93\
+\x23\x87\xc5\xe2\xa2\x62\x5d\x66\x56\x16\x43\x35\x0d\xef\x1c\x78\
+\x27\xda\xdf\xdf\xdf\x74\xe8\x83\xbf\x54\x4d\x09\x28\xdc\x78\xf6\
+\x17\x1c\x8b\x77\x2b\x96\xa6\x1b\x8b\x0b\xd2\xc8\x98\xa4\xe0\xce\
+\xd0\x3d\xec\xff\xe0\xa4\x9a\x40\xa5\x9c\x27\x67\x0e\xbe\xc8\xf3\
+\x7c\xf5\xda\x8a\x35\x42\x5a\x9a\x15\x69\xc9\x56\x84\xc2\x41\x88\
+\xd2\x18\x34\x4d\x03\xc7\x26\x60\x6f\xc3\x5e\x31\x12\x89\x14\x1c\
+\xfe\xf8\x48\xff\xfd\x19\xcc\xdb\xd8\x91\xfc\xe3\x57\xce\x1d\xb7\
+\x5a\x12\xde\xdd\xf6\xe2\x6c\x53\x51\x7e\x1a\x09\x8f\xc9\xf8\x6e\
+\x28\x82\x8b\x97\x6f\xab\x44\x19\x3b\x7a\xf5\xe8\x16\xff\x9b\xfb\
+\xde\x7e\x4b\x14\xc5\x03\x67\xcf\x9c\x13\x63\x31\x09\x1e\xdf\x00\
+\x46\x23\x61\x04\x83\x21\xdc\x0b\xdd\x83\xa2\x8e\xa3\x78\x49\x11\
+\xaf\xd3\xe9\xfe\x04\x00\x1c\x00\xe4\xfd\xba\xd3\x6c\x34\x28\xdf\
+\x95\x3e\x9e\x96\x58\xf2\x78\xaa\xce\xa8\x67\x11\x8a\xca\xb8\xe9\
+\x1d\x85\x41\x13\x71\xae\xbb\xf7\x5f\x03\x17\xff\xb6\x09\x80\x39\
+\x7e\x9e\x4c\x47\xb6\x43\x07\x00\x06\xc1\x88\x56\x57\xab\x74\xa9\
+\xb7\x57\xd2\x54\x55\xb7\xf2\x99\x95\x7a\x59\x96\xa9\xa6\x69\x05\
+\x3f\x5b\xbf\x8e\x67\x08\x21\x60\xb9\xb1\x7c\xa3\x0e\xfa\xe5\x0b\
+\xac\x3a\xb3\xc0\x61\x64\x74\x1c\x7d\x03\x61\xcc\xc9\x10\xf0\xd6\
+\xfb\xcd\x92\xff\x76\xd7\xcb\xa2\xb7\x47\x00\x60\xd5\xeb\x75\x36\
+\x86\x61\x9e\x9d\x35\x2b\x97\x21\x84\x20\xc5\x92\x86\xcb\x97\xaf\
+\x04\x3e\xf9\xf8\x48\x9e\xcf\x77\x77\xf9\x89\xe6\x96\xd1\xae\xce\
+\x6e\x5f\x38\x1c\x5e\x72\xe4\xf0\x67\x0a\x03\x80\xf4\x7d\x53\x79\
+\xf1\x5e\x30\x28\x47\xa3\x12\xfc\xa1\x18\xae\x0f\x86\x31\x3f\x37\
+\x19\x4d\x07\x3e\x17\xa3\xfe\xdb\xef\x85\xfa\x5c\x43\x00\xac\x00\
+\xd2\x19\x86\xb1\x6b\x9a\xa6\x37\x1a\x8d\x60\x18\x06\x84\x10\x50\
+\xaa\x99\x9d\x39\x4e\x67\x6b\x4b\x7b\xbf\xd7\xe3\x5d\x39\xf0\xcf\
+\x81\x92\xe6\x63\x2d\x7e\x00\x84\x05\x40\x30\x34\x44\x8c\xce\xa2\
+\xaf\xbf\xfc\xc6\xbd\x6e\xe1\x63\x36\x7e\xfe\x0f\xd2\xd1\xd2\xde\
+\x25\x5f\xe8\xb9\x74\x7d\xa0\xeb\xbd\x7a\x00\x42\x5c\x7a\x59\x56\
+\xb8\xc2\xf9\x85\x05\x81\x40\x20\x75\xe6\xcc\x1c\xd6\x60\x30\x62\
+\xee\x9c\x3c\xde\x3b\xe4\x59\x03\xa0\xb5\xb3\xa3\xfb\x96\xdb\x7d\
+\x27\x0c\x40\x05\xa0\xfd\x07\x00\x90\xe0\x8d\xb6\x41\x5d\x6a\x7e\
+\x4f\x57\xef\xdf\x57\x1e\x6f\xed\x60\xae\x5e\xbb\x21\xde\xbd\xd6\
+\xf6\x2b\x65\x2c\x20\xc6\x6b\x68\x5c\x6a\xdf\xb5\xeb\xa7\x33\xb3\
+\x32\x9d\x57\xaf\x5c\xb3\x8f\xcb\x12\x4b\xa9\x46\xfb\xfa\xfa\x46\
+\x3d\x1e\xcf\xa1\xbb\x3e\x7f\x08\x80\x1c\x07\x50\x12\x1f\x1a\x13\
+\x17\x0b\x20\xc1\x32\x67\x59\x6e\x6c\xa0\x37\x20\x8a\x41\x02\x80\
+\x8f\x8b\x8d\x0b\x13\xa0\x9f\x2e\x5d\x92\x9b\x5f\x90\xb7\x45\xd3\
+\x68\xce\xb7\xbd\x97\x57\x7c\x7d\xa1\xc7\x03\x60\x1c\x80\x32\xd1\
+\x01\xa1\x94\x82\x10\x82\xf8\x29\x27\x20\xcc\x03\x86\x13\x9a\xc8\
+\x4f\x00\x28\x00\x0d\x80\x9a\xe5\xc8\x62\x3c\x83\x1e\x29\x6e\xaa\
+\xc6\xd7\x29\x00\xfa\x6f\x93\x53\x01\xfa\x57\x90\x79\xbb\x00\x00\
+\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x0d\x64\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x78\x00\x00\x00\x78\x08\x06\x00\x00\x00\x39\x64\x36\xd2\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdb\x07\x07\x09\x0c\
+\x1b\xb2\xdb\x1f\x8a\x00\x00\x0d\x06\x49\x44\x41\x54\x78\x9c\xed\
+\x9d\xdb\x6f\x1c\xd7\x7d\xc7\x3f\x67\xf6\x42\x2e\x97\xf7\xab\x44\
+\x51\x22\x29\x8b\x92\x23\xb9\x71\xe5\x6b\x51\xd4\x0d\xea\x18\x45\
+\x6b\x20\x2d\x60\x67\xed\x02\x85\x81\x02\x7d\x2d\xd0\xa7\xa0\x7f\
+\x40\x81\x3c\xe4\xaf\xf0\x93\x53\x87\x4e\x8a\xb8\x68\x8b\x3e\x14\
+\x2d\x12\xa4\xa9\xa5\x00\xb2\x1a\x4b\xb1\x65\xd9\x92\x25\x51\x36\
+\x69\x4a\xe2\x45\xdc\xeb\xcc\xf9\xf5\xe1\xcc\xd9\x5d\x8a\x5c\x7a\
+\x77\xc9\x9d\x59\x49\xe7\x03\x2c\x76\x76\xf6\xec\xcc\x99\xf9\xee\
+\xef\x9c\xf3\xfb\x9d\xcb\x80\xc3\xe1\x70\x38\x1c\x0e\x87\xc3\xe1\
+\x70\x38\x1c\x0e\x87\xc3\xe1\x70\x38\x1c\x9d\x45\xb5\x92\xf8\xad\
+\xb7\xde\xca\x16\x2b\xf9\xbf\x56\x22\x4f\x0b\x2a\xd9\xa9\x4c\xb5\
+\x83\x52\xea\x4b\x25\xc1\x7b\xef\xbe\xfb\xcf\x1f\xc5\x9d\x97\x6e\
+\xa2\x69\x81\x73\xb9\xdc\x21\x94\x3e\x8f\x62\xa6\x93\x19\x3a\x00\
+\xde\xcd\x66\x06\xfe\xe6\xed\xb7\xdf\x2e\xc6\x9d\x91\x6e\xa0\x79\
+\x2b\xf4\xf4\xdf\x81\x11\xb7\xb7\xb7\x87\x84\xd7\x92\xf1\x77\x14\
+\x11\x28\x96\xca\x68\xad\x01\xde\xdc\x2a\x6c\xa6\x80\xef\x03\x12\
+\x6f\xce\xe2\xa7\x95\x62\xf6\x45\x80\xe1\xa1\x01\x5e\xfa\xe3\xb3\
+\x1d\xca\x4e\xfb\xf8\x7e\xc0\xb9\xf3\x97\xb8\xb3\xba\x06\xf0\xda\
+\xf7\xdf\x78\xed\x07\xef\xfd\xe4\x67\x3f\x8a\x3b\x5f\x71\xe3\x35\
+\x9d\x52\x49\x2f\x80\x97\xec\x1e\xcb\xad\x27\x99\x4c\xf0\xdc\xb3\
+\xa7\xe9\xcb\x66\x00\x50\x4a\xfd\xf0\x8d\x37\x5e\x7f\x35\xe6\x6c\
+\xc5\x4e\x5b\x0d\xa5\x74\xba\x87\xb1\xd1\x49\x7a\xd2\x3d\x28\xaf\
+\xf9\xff\x48\xa7\x08\x82\x80\x8d\x8d\x7b\xac\xad\xdf\xe5\xf9\xe7\
+\x4e\xf3\xab\x5f\x7d\x88\xef\x07\x09\x51\xbc\x93\xcb\xe5\x5e\x5c\
+\x5c\x5c\xfc\x24\xee\x3c\xc6\x45\xcb\x02\xf7\xf6\xf6\xf0\xc4\xfc\
+\x93\x8c\x8c\x8c\x31\x30\x30\xcc\xdd\xbb\x2b\x14\x8b\x85\x4e\xe4\
+\xad\x25\x86\x06\x87\x19\x1a\x1a\xe1\xe6\xad\x6b\x9c\x3d\x7b\x8a\
+\xdf\xfc\xe6\x77\x88\xc8\x10\x9e\x7e\x3f\x97\xcb\xbd\xb0\xb8\xb8\
+\xb8\x1e\x77\x1e\xe3\xa0\x65\xf3\x9b\x9a\x9c\xa4\xb7\x37\xc3\xf1\
+\xe3\xdf\x62\x72\x72\x9a\xd9\xd9\x85\x4e\xe4\xab\x2d\x06\x07\x86\
+\x99\x9f\x5d\x60\x7a\x7a\x8a\x85\x85\x63\x76\xf7\x49\x3c\xfd\xe3\
+\x5c\x2e\x97\x88\x33\x6f\x71\xd1\xb2\x05\xa7\x53\x69\x44\x04\x11\
+\x8d\x52\x09\x82\xc0\xe7\xf3\x6b\x57\x28\x16\x0b\x68\x1d\x74\x22\
+\x8f\x0d\x51\x4a\x91\x4a\xa5\x19\x1b\x9b\x62\x6c\x74\x1c\x80\xbe\
+\xbe\x7e\xe6\x8e\x3d\x41\xe0\xfb\x6c\xac\xdf\xe7\xab\xe5\x3b\x00\
+\x7f\xae\x3c\xf9\x21\xf0\x0f\x91\x66\xb0\x0b\x68\x59\x60\x11\x21\
+\x08\x7c\xae\x5e\xbd\x4c\x5f\x5f\x3f\xff\x7b\xee\x17\x6c\x6c\xdc\
+\xed\x44\xde\x9a\xa2\x54\x2e\x71\x7f\x6b\x93\xbb\x77\x57\x98\x9f\
+\x3b\x49\x32\x99\x24\x9b\x1d\x60\x6e\xee\x04\xe5\x4a\x85\xad\x5f\
+\x17\xd8\xdc\xcc\x23\xc8\x0f\x72\x7f\xf5\xda\xc5\xc5\x7f\xfa\xd9\
+\x3b\xb1\x65\x36\x06\x5a\x17\x38\x74\x2d\xf3\xf9\xfb\xdc\xbe\x7d\
+\x93\xb5\xb5\xd5\x03\xcf\x54\x3b\xac\x6f\xac\xf1\xf1\x95\xdf\x72\
+\xf2\xc4\x19\xd2\xe9\x34\x83\x03\xc3\x9c\x38\x7e\x8a\x52\xb1\xc8\
+\xff\xfc\xfa\x23\x2a\x15\x5f\x21\xea\xed\xdc\x9b\xaf\x1f\x51\xc2\
+\x85\xb8\xf3\xbb\x1f\x82\x04\xb7\x7e\xfa\xe3\x9f\x7e\xdc\x4c\xda\
+\x7d\x85\x1b\xfd\xa0\x62\x83\x0b\x5d\x41\x3e\xbf\xc5\xe5\x8f\x2f\
+\x72\x72\xe1\x34\x7d\x99\x2c\xc3\xc3\xa3\x7c\xfb\xf7\x9e\xa5\x58\
+\xf2\xf9\xe0\x83\x8f\x00\x52\xc0\x8f\xa4\x3b\x3d\xbd\xa6\xf1\x34\
+\xe4\xde\x7c\xfd\x3c\xda\xfb\xdb\xc5\xc5\xc5\xdf\xee\x95\x76\x5f\
+\x02\x07\x81\x8f\x0e\xba\x47\x60\x80\x62\xa1\xc0\xe5\xcb\x17\x39\
+\xb9\x70\x86\xc1\xc1\x21\x06\x06\x06\xf9\xce\x4b\x7f\xc2\xf4\xe1\
+\x69\x7e\xf1\xcb\x73\xdc\xb9\xb3\x16\x77\x16\x0f\x8a\xe7\xf1\xf4\
+\x3b\xc0\xb7\xd9\x23\x62\xd7\xba\xc0\x75\x87\x12\x91\xae\xb2\x60\
+\x4b\xb9\x5c\xe6\xd2\xe5\x8b\x2c\x9c\x78\x92\xf1\xf1\x49\x92\xc9\
+\x24\xdf\x7a\xf2\x0c\xa7\x4e\x9e\xa6\x54\x2a\x56\xdb\x11\xd2\x44\
+\x20\x53\xeb\x80\x60\xb7\x6b\x14\x41\x8b\x20\x5a\xd0\xa2\x11\xad\
+\x29\x97\xcb\xe1\xb6\x98\x7b\x13\x36\x46\x45\x63\xf6\x87\xdf\x05\
+\x3a\x40\x07\x41\xf8\xbd\x20\x5a\x23\x84\xc7\xd2\xba\xb6\x5f\x34\
+\x5a\x87\xc7\x43\xaa\xf7\x7b\x79\x79\x95\xeb\x5f\x7c\x05\xf0\x54\
+\x2e\x97\x7b\x7a\x71\x71\xf1\xc3\x46\xf9\x6f\xa3\x0e\xde\x76\x8d\
+\xbb\x5f\x7c\x57\xa0\xf9\xdd\x27\x1f\x71\x68\xed\x30\xc7\xe7\x17\
+\x48\x24\x92\x78\x9e\x22\x93\xc9\x1c\xd8\x19\x44\x34\x41\x10\x8a\
+\x10\x8a\x61\x04\xd2\x68\x6d\x84\xd3\x12\xd4\xb6\xb5\x11\x75\x47\
+\x7a\x5d\x7f\x0c\x21\xd0\x1a\xb1\xbf\xb3\xe9\x02\xf3\x7d\x10\x1e\
+\x33\x14\x18\x12\xc1\x11\xe0\xe0\x04\xae\xff\xdb\x77\xab\x05\xd7\
+\x73\xfb\xcb\x25\x56\xbe\x5e\xe6\xc8\xf4\x51\x26\x27\x0e\xd1\xd7\
+\x97\xed\xd8\xb9\x24\x7c\x99\x0d\x6b\x75\xe6\x3e\x49\xd8\x3c\x95\
+\xaa\x75\x36\x7a\x99\xd2\xa0\x6a\xd9\xa1\xd5\xdb\x12\xc0\x58\x74\
+\xf3\xf7\x7c\x9f\x16\xdc\xfd\x02\x83\x29\xb2\xaf\x5d\xff\x8c\x6b\
+\xd7\x3f\x43\x29\x45\x32\x71\x30\x5d\xd9\x02\x24\x12\x09\x14\xaa\
+\xea\x5d\x98\xbd\x2a\x14\x18\x63\x10\x4a\x41\x28\xb6\x79\x57\x28\
+\x25\x20\xd6\x2b\x51\x88\x08\x4a\x99\x7b\x6a\x3f\x63\xb6\x10\x94\
+\x11\x3a\x3c\x54\xb9\xd2\x7c\xbc\x61\x5f\x57\x6a\xff\x51\x71\x22\
+\x22\x14\x0a\x25\xfc\x16\x2e\xfa\xa1\x45\x29\x32\x99\x1e\x44\x3a\
+\x68\xc1\xf5\x36\xec\x57\x7c\x74\x10\xcf\x8d\xd5\x5a\x58\x5e\xbe\
+\xc7\xca\xd7\x6b\x94\xcb\x7e\x2c\x79\x88\x8b\xbe\x4c\x4f\xd3\x69\
+\x1f\xca\x56\xb4\x88\xf0\xe9\xd5\x25\x36\x37\x1f\xcf\x41\x1b\xf9\
+\x42\xa9\xe9\xb4\xfb\x2b\xa2\x89\x47\xe0\x5b\x4b\x77\xaa\xe2\x1e\
+\x39\x32\xc5\x33\xcf\x3c\x45\x3a\x95\x8a\x3c\x1f\x51\x23\x22\xac\
+\x7c\x7d\x87\x73\xe7\x2e\x52\x2e\x57\x00\xf0\xc4\xdb\x53\xc3\xb6\
+\x62\xd1\xd5\x6d\x2d\x04\x11\x07\x3a\x8a\xc5\x0a\xab\xab\x1b\x00\
+\x4c\x1f\x9e\xe4\xf5\xd7\xfe\x8c\x44\x22\xfe\x3e\xe9\xa8\x98\x9d\
+\x9d\x66\x68\xb0\x9f\x7f\xfd\xb7\xff\x06\x40\x23\x4f\x01\x3f\x6f\
+\x94\xfe\xa1\x6a\x45\x8b\xc0\xd2\xd2\x2a\x22\x82\xe7\x79\xbc\xf2\
+\xca\x1f\x92\x48\x78\x0c\xf4\x0f\x30\x3a\x3a\x41\x22\xd9\xe0\x72\
+\xea\x5a\xb4\x82\x02\xd1\xf8\x81\xbf\xcb\x7e\xdb\x16\xd6\x20\x0a\
+\x41\x87\x2d\x5d\xaa\x3e\xad\x88\x69\x11\x5b\x7f\xc8\xba\x45\xb5\
+\xdf\xd5\x5c\xa5\xa0\xda\xbb\x26\xb5\x73\x6c\xdb\xb6\xe7\x31\x69\
+\xcc\xbd\x94\xba\xe3\xd5\xae\xbb\xb6\x47\x78\xe2\x89\xd9\xea\x77\
+\x0a\x99\xdb\xeb\x9e\xed\x2f\x54\xa9\xa3\x6d\x45\xaf\xaf\xe7\xd9\
+\xca\x9b\xfa\xe7\xec\xd9\xd3\x8c\x8d\x8d\x30\x73\x64\x96\xa3\x47\
+\xe7\x1a\xfc\xc2\xfa\xa1\xba\xea\xbe\xdb\x6d\xd3\x12\xad\xf3\x53\
+\x85\x50\xd0\x7a\x9f\xd4\x1e\x43\x6a\xbe\xac\x84\x02\x6c\xfb\x2c\
+\x75\xc7\x90\xed\xc7\x60\x67\xfa\xda\x6f\xd8\x79\x0c\xeb\x4e\x3d\
+\x70\xce\x6a\x1a\xb6\x85\x22\xd0\x4a\xed\x59\x37\xed\x4b\xe0\x4a\
+\x25\xba\xce\x86\x20\xd0\x2c\xaf\x98\x38\x72\x7f\x7f\x1f\x2f\xbe\
+\xf0\xfb\x8c\x8d\x4d\x56\xc5\x15\x11\xca\xe5\x72\x5d\x15\xa2\x8d\
+\x9f\xea\x25\xaa\x22\x1a\xb1\xac\xc0\x50\x2f\x9e\x39\x86\x46\x29\
+\x0f\x3b\x9a\xb8\xa1\xb0\xbb\x0a\x19\xfe\x69\xec\x1f\x2a\xf4\x77\
+\x6b\xbf\xd3\x0f\x88\x69\x4a\x88\xed\x82\xd7\x82\x21\x3c\x20\x6e\
+\x55\x74\x6a\xf9\x6d\x86\x87\x26\x92\xb5\xba\xba\x89\xef\x9b\x73\
+\xbd\xf4\x47\xcf\xd3\xdf\x9f\xe5\xf8\xbc\x19\x4d\x52\x28\xe4\xb9\
+\x74\xf9\x22\xe5\x72\xf3\xad\xcb\x83\x42\x29\x15\xfe\x29\xec\x7d\
+\xa9\xef\xaa\x7a\x50\x88\x30\x78\x51\xfd\x13\x49\xdd\x3b\xb5\xed\
+\x6a\xf1\x6f\x3e\x2b\xa5\xb6\xa5\x6b\xe5\x9e\xb7\xd1\xc8\xaa\xdb\
+\x8e\xa8\x91\xb5\xb1\x51\x60\x6d\x3d\x0f\xc0\xcc\xcc\x61\x4e\x9d\
+\x9a\xe7\xf8\xfc\x02\xc9\x64\x12\x11\xe1\x93\x2b\x97\x62\x11\x17\
+\xac\x45\xb7\x1a\x0b\x88\x2e\x76\xb0\xaf\x40\x87\x16\xa1\x52\x09\
+\x28\x16\xcb\xf8\x81\x46\x07\x36\xe2\x7a\x30\x88\x40\xb9\x5c\xa1\
+\x58\x34\x81\x8c\xde\xde\x1e\x5e\x79\xf9\x0f\x18\x1a\x1a\x61\x34\
+\x1c\xa2\xb3\xb4\x74\x83\x7c\x7e\xeb\xc0\xce\xf9\xa8\xd1\x76\x1d\
+\x7c\xe3\xe6\x12\xe7\xce\x5f\x64\x75\xf5\xde\x41\xe6\xa7\x21\x83\
+\x03\xfd\xbc\xfa\xea\x77\x18\x1e\x19\x66\x6e\xf6\x38\x00\xe5\x72\
+\x89\xa5\xdb\x37\x22\x39\xff\xc3\x4a\x5b\x7e\xf0\xbf\xff\xc7\x7f\
+\x72\xe1\xc2\xff\x75\x22\x3f\xdb\xe8\xe9\x49\x33\x3e\x3e\xca\x99\
+\xd3\x27\x58\x58\x98\x27\x99\x4c\x30\x31\x3e\x45\x36\xdb\x8f\x88\
+\xe6\xe6\xad\x2f\xf0\xfd\xc7\x20\x06\xbd\x0f\x5a\x16\x78\xe9\xf6\
+\x32\xab\xab\x66\x90\x5d\xb6\x2f\xc3\xb3\xcf\x3d\xc5\xec\xb1\x23\
+\x0c\x0c\x64\x49\xa5\x3a\x3b\xe1\x30\x91\xf0\x38\x7a\x74\x0e\xad\
+\x85\x62\xb1\xc0\xf2\xf2\x6d\x76\x36\x64\x1c\xf5\xb4\xac\x88\x15\
+\x77\x72\x72\x8c\xbf\xfc\xde\x77\xab\x53\x45\xa2\xe0\xd8\xb1\x79\
+\xd2\xe9\x14\x22\xc2\x8d\x9b\xd7\x5a\x72\x17\x1e\x57\xda\x32\xb9\
+\x4c\xa6\x97\xbf\xf8\xde\xcb\x91\x8a\x3b\x39\x79\x88\xa9\xc9\x69\
+\xb4\x16\xd6\xd7\xef\x71\xe7\xce\xd7\x91\x9d\xfb\x61\xa6\x79\x81\
+\xa5\x96\xf6\xd9\x67\x4e\x93\xcd\x66\x18\x1a\x1c\x62\x62\xe2\x50\
+\xe3\x10\xe1\x7e\xa8\xfa\x82\x42\x3a\xd5\x43\x36\x3b\x80\x88\xc6\
+\xf7\x7d\xae\x5d\xbf\x82\x2b\x9a\x9b\xa3\x79\x65\x14\x43\xf6\x9e\
+\xce\xce\xcd\x30\x37\x77\x82\x23\xd3\xc7\xf6\xfe\x4d\x9b\xd4\xa2\
+\x4b\xf5\xa1\x44\xc1\xf7\xcb\x5c\xf9\xf4\x63\x8a\xc5\x78\x7c\xde\
+\x87\x91\xa6\x05\x56\xa8\x8c\xb5\x99\xb3\x4f\x9f\xad\x8a\xab\x75\
+\x40\x3e\x9f\xe7\xa0\x2c\xaa\x16\x13\xae\x85\x05\xb5\x0e\xb8\x7f\
+\x7f\x93\xaf\x96\x97\x28\x97\xcb\x07\x72\x9e\xc7\x85\xa6\x05\x96\
+\x30\xed\xc8\xc8\x10\x33\x33\xa6\x37\x23\x5f\xd8\xe2\xd2\xa5\x8b\
+\x94\x4a\x8f\x67\xc7\xfb\xc3\x40\xcb\x1d\xa9\x47\x8f\xce\xe0\x85\
+\x73\x82\xbf\xb8\xfe\x99\x13\xb7\xcb\x69\x6b\x7e\xb0\xa5\xe2\x57\
+\x70\x8d\x9d\xee\x66\x9f\xa3\x2a\xb7\x77\x3e\x38\xba\x8f\xc7\x67\
+\xac\xcb\x63\x8a\x13\xf8\x11\x67\x9f\x11\x8a\xea\x64\x0d\x47\x97\
+\xe2\x2c\xf8\x11\x67\x7f\x16\xec\x1a\x59\x5d\x8f\xb3\xe0\x47\x1c\
+\x57\x07\x3f\xe2\xec\xcb\x82\x9d\xb4\xdd\x4f\x9b\x16\x5c\xeb\xed\
+\x71\x74\x37\x6d\x8d\x8b\x36\xcb\x0d\x10\x0e\xec\xee\x40\xae\x1c\
+\x0d\x50\x66\x2e\x79\x0b\xb4\x2c\xb0\xae\xeb\xc6\x23\x9c\xfe\xe1\
+\x88\x06\x23\x6e\x6b\x0a\xb7\x3e\xaa\x52\x4b\xb8\x38\xc8\xc1\x8e\
+\x81\x76\x34\x87\x6a\xd1\x84\xdb\x98\x5d\x18\x2e\x0c\x22\x76\xda\
+\x85\x23\x3a\xcc\xfd\x6e\x45\xe4\xb6\xa6\xae\xd8\x3a\xd8\x55\xc0\
+\xd1\xa2\x54\x14\x16\xac\xeb\x67\xbc\x69\x37\x74\x35\x42\xaa\x0b\
+\xf6\xb4\x40\x5b\x13\xc0\xb5\x5d\x99\xcd\x15\xd1\x31\xd0\x61\x0b\
+\x66\x9b\xe5\x3a\xeb\x8d\x12\x33\x55\xb5\xd3\x45\xb4\x48\xe8\x2a\
+\xe1\xf4\x8d\x81\x56\xcb\xcc\xf6\xd6\xe8\x90\x70\xa9\x03\x9c\x1f\
+\x1c\x2d\xd2\xb2\xc2\x6d\xad\x93\xa5\x43\x81\x1d\xd1\xa2\x88\xa4\
+\x88\xb6\xcb\xe5\x82\xd6\x9e\xd3\x39\x42\x8c\x67\xda\xe9\x46\x96\
+\x52\x26\x92\x65\xda\xd3\x2d\xff\xdc\xb1\x3f\x5a\x0d\x57\xb6\xe1\
+\x07\xd7\xb7\xa0\x5d\x4b\x2b\x4a\x4c\xa0\x03\x5a\xb9\xe7\x6d\x58\
+\xb0\x0d\x57\x8a\x2b\x9e\x63\x21\x8a\x48\x56\x58\x07\x2b\xe5\x14\
+\x8e\x96\x88\xfc\xe0\xfa\xc5\xb9\x9c\x15\x47\x4c\x24\x8d\x2c\xab\
+\xac\x8b\x76\x44\x4f\xc7\x63\xd1\xe1\x03\x26\x70\xbd\xc1\xb1\x10\
+\x41\x7f\x70\xfd\x82\x98\xce\x4d\xea\x76\x5a\x16\x58\x61\xac\xd8\
+\x8c\x7a\x77\xbd\x49\x51\x12\x49\x67\x43\xf5\x61\x4f\xd8\x47\xbd\
+\xb8\x82\x3a\x2a\xda\xb9\xd5\x6d\x0d\x9b\xb5\xcb\xde\x3a\xa2\x65\
+\xb7\x28\x96\x88\xd4\x2f\x5d\xbb\x83\x66\x07\xbe\x7b\xb6\x77\x3f\
+\x5c\xfe\xd8\x14\xd3\xae\x88\x8e\x9c\x5a\x34\xcb\xa0\x83\x20\x05\
+\xf4\x01\xbb\x3e\x00\xbb\x19\x0b\xf6\x80\xac\x51\xd3\x3e\xe0\xc9\
+\x8e\xe8\x70\x76\x1c\x2d\x3b\xeb\x60\xad\x25\x09\xf4\x63\x74\xca\
+\xf3\xc0\x5a\xc5\xdf\x24\xb0\x02\xb2\x40\x56\x04\xcf\x1e\xbb\xda\
+\xc8\x72\xb1\xe8\x48\xd9\x2d\x72\xa8\x75\x90\xc2\x08\x6c\x95\xbf\
+\x4f\x9d\x28\xdf\x24\x70\x0f\xc6\xfc\xab\x6b\x16\x9a\xae\xe0\xed\
+\xcf\x3a\x70\x44\x83\xe9\x2a\x7c\xa0\x0e\xd6\x92\xc0\x68\x04\x46\
+\x58\x1f\x28\xd8\xef\xf7\x12\x38\x81\x11\x36\x6d\xde\xc3\x22\x5a\
+\xd5\x8a\x67\x27\x6e\xf4\xec\x28\xa2\x45\x27\x81\x5e\x4c\x50\xa2\
+\x82\xd1\xac\x4c\x58\x54\xef\xd5\xc8\x4a\x61\x2c\x38\x03\xf4\x4a\
+\x75\x08\xa5\xaa\xb5\xb4\x5c\xf1\x1c\x29\xb5\x67\x37\xd4\x44\x0e\
+\x2d\xb8\x17\xa3\x55\x2f\xc6\x20\xd3\xf6\xfb\x46\x16\xac\xc2\xc4\
+\xa9\x30\x71\x8f\x3d\xaa\xd6\x54\x63\xd1\xca\xd5\xc1\x11\x23\x3b\
+\xc6\x45\x8b\x48\x02\xa3\x8f\x8f\xb1\x5c\x6b\x98\x45\x40\x1a\x59\
+\xb0\x87\x29\xa2\x53\xf6\x25\xa1\x4f\xa4\x54\xad\x2f\xd8\x49\x1b\
+\x35\x3b\xdd\xd2\x70\x18\x5e\xaa\xee\xd5\x83\xd1\x2e\x01\x8d\x2d\
+\x38\x11\x7e\x97\xc4\x88\x5d\x4b\x17\x4e\x19\x75\xdd\x85\x31\xd0\
+\xb8\xc0\x4c\x61\x34\xb2\xc2\xa6\x08\xab\xdf\x46\x16\xac\xc2\xef\
+\xac\xb8\xd5\x40\x87\xb6\x5d\x84\x4e\xdd\x6e\xc2\x6a\x95\x60\xbb\
+\x76\x7b\xd6\xc1\x36\x04\xb6\xa3\x5c\xa8\x3d\xb2\xdc\xd5\xc1\xd1\
+\xd2\xf0\x5e\x5b\x9d\xbc\x07\xb6\xf7\x6c\x45\x37\x88\x64\xd4\x57\
+\xbe\x2e\x54\xd9\xc5\x68\x68\x6c\xc1\x76\x4c\x6c\x4d\xe0\xea\x43\
+\x32\x55\x68\xc1\x66\x9e\xb0\xa3\x2b\xb0\x5a\x3d\xf8\xde\x50\x60\
+\x5d\xf7\x0a\x80\xc0\x06\x9e\x4b\xe5\x0a\x81\xd6\x28\x60\x70\x70\
+\x8c\x20\x78\xbc\x1e\xaf\x1e\x27\xc9\x64\x1a\xc1\x3c\x14\xd4\x22\
+\x22\x3e\x56\x23\xe3\x2a\x59\xcd\xf6\x14\xd8\x26\xae\xd8\x77\x3f\
+\xf0\x57\x12\x89\xc4\xc4\xca\xca\x32\x37\xbe\xb8\xc5\xe1\xc3\x13\
+\x28\xa5\x48\x26\x1f\xfd\x27\x6f\x77\x0f\xa6\x10\xbd\x7a\xf5\xf3\
+\xea\x9e\x42\xbe\x70\x8b\x9a\x56\x01\x35\xcd\x02\xd8\xdb\x82\x6d\
+\xc2\x32\x50\x59\xbb\xb7\xf1\x5f\x53\x53\x13\x67\x7c\xdf\xe7\x27\
+\xef\xbd\xcf\x8b\x2f\x9c\x65\x7c\x7c\x14\xe5\xb9\x7a\x38\x2a\x44\
+\x6b\x6e\xdd\xfa\x92\x73\xe7\x3f\x0c\x77\xc0\x67\x9f\x7f\xfe\x4b\
+\x42\x8d\xea\xde\xab\x02\xef\xa5\x4e\x1a\x18\x06\x06\x80\xfe\xa1\
+\xa1\xa1\xb1\x57\xfe\xf4\xe5\xf7\x3c\xcf\x1b\xe9\xdc\x25\x38\x5a\
+\xa1\x54\x2c\x5f\x7e\xff\xe7\xff\xf2\xf7\x98\xce\x85\xfb\xc0\x16\
+\xb0\x09\xac\x01\x25\x68\xd0\x49\x1c\xa2\xa9\x39\xcf\x5e\xa9\x54\
+\xd2\x7e\x25\xf8\x60\x74\x7c\xe4\x4c\x22\x91\x18\xef\x6c\xd6\x1d\
+\xdf\x44\xa5\x52\xf9\xf4\xc3\x0b\x17\xfe\x71\x7d\x7d\x63\x15\x23\
+\x70\x11\xd3\x1f\x5c\x08\x5f\x02\xdf\xec\xe7\xa4\x80\x41\x4c\x9f\
+\xb0\xed\x36\xec\x99\x99\x9d\x39\x3c\x35\x39\x71\xbc\x37\xdd\x3b\
+\x86\x72\xbe\x52\x64\x88\xe8\x52\xa9\xbc\xbe\xba\x7a\xf7\xe6\xf5\
+\xeb\xd7\x6f\x62\x8a\xe2\x12\x46\xdc\x2d\x8c\xc0\x1b\x98\xa2\x1a\
+\x68\xce\x91\xcd\x60\x3a\x94\x33\x98\x0e\x88\x5e\x42\xab\xc6\xad\
+\x56\x1b\x17\xf5\x1e\x4e\x31\x7c\xd9\x62\xba\x50\x9f\xb0\x99\x21\
+\x3b\xf6\xb9\x39\xf6\x80\x15\x6a\xb1\x4e\x67\xbd\xf1\x60\x7d\x5d\
+\xdb\x08\xb6\x16\xbc\xe3\x19\x47\xad\x08\x54\xdf\xdf\x58\x0d\x66\
+\x3b\x62\xc3\x7a\x3a\x56\xe0\x32\xbb\xc4\x32\x5b\xb5\x40\x8f\x5a\
+\xb7\x94\x0d\x6c\x3b\xa2\x47\xa8\x95\xa6\x15\xf6\x98\x62\xb2\x1f\
+\x81\x76\xed\x88\x70\x44\x82\xeb\xe5\x71\x38\x1c\x0e\x87\xc3\xe1\
+\x70\x38\x1c\x0e\x87\xc3\xe1\x70\x38\x1c\x0e\x47\xd7\xf0\xff\xe6\
+\x06\x8d\x65\xbd\x66\x9c\xa4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x05\xdd\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\
+\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
+\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
+\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
+\x42\x28\x9b\x78\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x03\x12\
+\x0b\x21\x05\xe4\x38\x9d\xbd\x00\x00\x05\x5d\x49\x44\x41\x54\x48\
+\xc7\xb5\x56\x5b\x6c\x54\x55\x14\x5d\xe7\x71\x5f\x33\xd3\xce\x14\
+\x06\x2a\x7d\xd1\x4e\x29\x8f\xb6\xb4\x3c\x44\xc2\x23\x41\x23\x24\
+\x1a\x42\x44\x79\x05\xd0\xf8\x48\xf8\x50\x09\x7e\x80\xa2\xfc\x38\
+\x5f\x06\xa3\x46\xa0\x04\x4d\x30\xa8\x44\x24\x16\x10\x89\x06\x23\
+\xc1\xd8\x88\x4a\x54\x0c\x41\x0a\x05\xb1\x15\x68\x29\x4c\x9f\x43\
+\xe7\x7d\xef\x3d\xe7\xf8\xd1\x69\x69\x29\x25\xfe\x78\x92\x95\xfb\
+\x71\xf7\xd9\x2b\x7b\xed\xbd\xce\x39\xc0\xff\xbc\xc8\x7d\xff\x86\
+\xc3\xb4\xfc\xb4\x11\x72\x09\x1f\x47\xb9\x14\x44\x8a\x68\x49\xd2\
+\x6e\x69\x68\x08\xbb\xf7\xdb\x36\x79\xd9\xbb\xc1\xbf\xbe\xde\xd2\
+\x05\x00\xfc\x5e\x01\x95\x2b\x76\x3c\xcd\x0d\x63\x2d\x6f\xa6\x53\
+\xac\x32\x5f\xbe\x6e\x70\x9f\x46\x19\x00\x11\x13\xae\xd3\xbd\xa0\
+\x7c\x5f\xdb\xed\x58\xfc\x42\x3a\x13\xdf\xf5\xf7\xb1\x6d\x17\x87\
+\xee\x2d\x5d\x1e\x0e\x08\x21\xda\x01\xe8\x23\x2a\xa8\x7a\x38\xec\
+\x63\x13\x82\x87\x82\xc1\x71\x8b\x1c\x09\x4b\x11\x02\x4a\x29\x18\
+\x63\x60\x8c\x82\x51\x0a\xce\x39\x72\x3c\x1a\xfc\x1e\x0d\x6e\x26\
+\xd9\xdb\xda\xde\xd9\xd4\x1e\xe9\xdd\xd9\x58\xff\x4a\x7d\x68\xf1\
+\x76\x7f\x4d\x6d\xf1\x0f\x95\xe5\xe3\x4b\xde\x7a\x69\x49\xf0\xae\
+\x0a\x14\x61\x13\x76\x1f\x2d\x2d\x2b\x59\x1c\x4b\xda\x20\x50\x30\
+\x34\x02\x4d\xd3\x60\x68\x1c\x4a\x11\xb8\x0a\x20\x04\x48\xdb\x12\
+\x86\xae\xe0\xf7\xfb\xf3\x1e\x9d\x54\x30\x5f\xd9\x99\x19\x3f\x17\
+\xee\xdf\x6c\xdb\xca\x7c\xe6\xa9\x79\x35\xbf\x9f\x6d\x8e\x0c\x64\
+\x1d\x24\xa8\x5a\x55\xf7\x6c\x51\x51\xc1\xc2\x64\xda\x51\xb7\x7b\
+\xba\xdb\xa5\x42\x37\x94\x8a\xb8\x42\x46\x94\x90\x69\xaf\xc7\x2c\
+\xca\xf3\x5b\x65\xc1\xf1\xc1\x90\x65\x99\x1a\x67\x14\xa6\xc1\xe1\
+\x4a\x85\x40\xc0\xe7\x59\xbb\x7c\xc1\x43\x94\x00\xb1\xb4\x80\x54\
+\xca\x19\x41\x60\x99\xfa\x3a\xa5\x08\x3a\x6e\xdc\x3a\x57\x5c\x3e\
+\xf1\x3b\x8f\xae\xb9\x91\x68\xac\xe5\xc7\x1d\x2b\xf7\x0d\x16\xb9\
+\xaa\x9e\x4d\x6d\x6b\x7a\x61\x62\xe1\xd8\x0d\x33\xab\x4a\xab\xfc\
+\x3e\xc3\x63\x1a\x0c\x94\x12\x24\xd2\x2e\x18\xa5\x70\x84\x82\x1c\
+\x22\xfd\x20\x01\xa5\x34\x74\xb3\x3d\xd2\x5c\x53\x5b\x75\x88\x6b\
+\x2a\xa9\x04\xc9\xc0\xee\x6b\x1b\xd6\xfd\x43\xab\xc5\x25\x60\xef\
+\x25\x60\x6f\xc7\xba\xdd\x2f\xce\x99\x5e\xf2\xda\xc2\x39\x53\x4b\
+\x53\xae\x04\x21\x04\x20\x40\xd2\x16\xa0\x8c\xcb\xc1\xbc\x03\x9d\
+\xef\x89\xf6\x8d\xcf\xcf\xcf\xff\xd8\x9f\x6b\x35\xe5\x7a\xbc\x97\
+\xa4\x94\xd7\x5c\xa8\x8e\xd1\x46\xf1\xec\xe7\x1b\x3f\x68\x38\xd3\
+\xbc\xe1\xdc\x85\x7f\xfa\x38\xa3\xa0\x94\x20\x65\x4b\xa4\x6c\x01\
+\x4a\xa8\x1c\x56\x81\x37\x0a\x37\xe9\x71\x8c\xea\xca\xd2\x06\x06\
+\x3d\x49\xb9\x9b\xb2\xa9\xe9\xb0\x9b\xb0\x47\x23\xa8\x7e\xe2\xbd\
+\xe2\xf9\x0f\x86\xf6\xcc\x9a\x51\x91\x9b\xb6\x5d\x08\xa9\x90\xb2\
+\x05\x52\xb6\x04\xc8\x5d\x12\x5d\x68\x08\xc7\x43\xcb\xde\x79\x3f\
+\xc7\x6f\x9e\x7f\x73\x55\xa5\x43\x08\x51\xf7\x33\x52\x68\xf1\x76\
+\xff\x9c\x59\x65\xc7\x57\x3e\x36\xbb\x22\x91\x11\xc8\xb5\x38\x52\
+\xb6\x82\xc6\x19\xbc\xa6\x86\x48\x8a\x91\x11\x4e\xae\x5e\xb3\xeb\
+\x43\xc3\xd0\x18\x25\x14\x1a\x07\x18\xa1\xfd\x02\x4a\x20\x63\xdb\
+\x81\xde\x58\xfc\xe0\x95\xa3\x5b\xbf\x04\x80\xca\x27\xdf\x5e\x9f\
+\x93\x9b\xbb\x54\xd7\x78\x02\x14\x90\x12\x10\x4a\x42\xb8\x80\x54\
+\x12\x99\xb4\x83\xc6\xfa\x4d\x1b\x86\x35\x79\xee\x8c\xd0\x92\xd0\
+\xa4\x89\x21\x9f\xc9\xe1\x33\x39\xbc\x3a\x85\x2b\x15\x00\x81\xcf\
+\xbe\x3a\x7d\xbe\xab\x3d\xf6\xfd\x40\xec\xc5\xa3\x5b\x0f\x00\x38\
+\x30\xf2\x64\x09\x53\x54\xae\xe2\xfb\x3f\xf9\x26\x3c\x62\x8a\x5c\
+\x21\x91\x76\x14\x0c\x4d\x41\x2a\x85\x84\x2d\x40\x08\x70\xfc\xe4\
+\x1f\x57\xda\xae\x76\x2f\x6d\x39\xf9\xfa\xed\x7b\xc9\xb5\x60\xe3\
+\xc1\x82\x45\x73\x6b\x15\x55\x8e\x99\x71\xb9\x95\xec\x4c\x78\x25\
+\xe4\x1b\x00\xb6\x0d\x23\x20\xa0\xca\xd2\x29\x2c\x9d\x81\x51\x02\
+\x29\x25\x4e\x9d\x6e\x8c\x36\x36\xdd\x7c\xee\xd2\xb1\xcd\xad\xa3\
+\xf5\x43\x0a\x55\xd1\xd9\x75\x5b\x33\x38\x34\xa9\x60\xfd\xd9\xd4\
+\x3c\x93\x6b\x7a\x74\xd8\x98\x66\x43\xa9\xa5\x33\x58\x3a\x85\x94\
+\x0a\x42\x2a\xcc\x9d\x3d\x25\x30\xbd\xb2\xe8\xd3\xda\xb5\x75\xcf\
+\x8f\x46\xc0\x34\x3e\x46\x4a\x51\x1e\x4f\x3b\x53\xba\xfb\x12\xd5\
+\xbd\x3d\xbd\x6b\xf2\x02\x39\x2d\x23\x24\x22\x4a\x50\x8f\xce\x00\
+\x05\x48\xd5\x2f\x53\xca\x01\x6a\x6a\x26\x4f\xca\x1b\x13\xa8\xf3\
+\x79\x3e\xda\xd0\xd5\xd3\xb7\xf7\x72\x6d\xdf\xa7\x08\x87\xe5\xe0\
+\xf9\x45\x8f\x3c\x10\x8d\x25\x0a\xe3\x89\xa4\x79\xfd\x6a\xdb\xe3\
+\x25\x25\x45\xa1\xce\xc8\xad\x23\x23\x9d\x4c\x00\x9d\x13\x28\xe9\
+\x82\x33\x0a\xdb\x51\x48\x65\x5c\xc4\x92\x0e\x88\xee\xf5\x96\x55\
+\x94\xcf\xcb\x8b\xf6\xcd\x1e\xdb\xd6\xfd\x6a\x6c\x4d\x5d\xab\xe3\
+\xba\xed\x4c\xdb\x93\xdb\xdb\xc9\xfc\x3d\x91\xce\x31\x20\xc8\x2f\
+\x2e\x2e\x2c\x90\x6e\x26\x9a\x4e\x3b\xbb\x47\x10\x68\x8c\x6a\x4a\
+\xba\xd8\x7f\xf8\xa7\x46\x9d\x93\xc4\xec\xda\x8a\x1a\xcb\x97\x63\
+\xa5\x32\xee\x20\x11\x98\xae\x07\xc6\xe5\x4f\xcb\x1d\x2b\xa7\x49\
+\x21\x60\x3b\x2e\x5c\x21\x21\xa5\x82\xa1\x33\xe8\x8c\x8a\xd6\xb6\
+\x8e\x13\x17\x0f\x6f\x3a\x35\x28\x61\xd6\x0b\x9e\x99\x8f\xac\xd9\
+\xf2\xcb\x99\xcb\x57\x7e\x3d\x5a\xb7\xbe\xa9\x61\xef\xe1\x1b\x89\
+\x9c\x9b\x9d\xbd\xa9\x7c\x25\xe1\x61\x4c\x37\x5c\x29\x21\xc4\x1d\
+\xb8\xb2\x3f\xb1\x94\xaa\xdf\x4c\xae\x1d\x6f\x6f\xbd\x7e\xa2\xe5\
+\xdb\x9d\x2f\x3b\x4e\x14\xfd\x0e\x82\x24\x00\x34\x00\x66\x68\xe9\
+\xf6\x9e\x8e\xf3\x5f\xd4\xc6\xaf\x9f\xb5\x01\x98\x59\x18\x81\xd0\
+\xfc\xc9\xc1\x8a\x45\xab\x3d\x81\x60\xa9\x69\x78\xc6\xe8\xa6\xc7\
+\xab\xb8\xee\x91\x42\x91\x8c\x9d\x8a\x67\x92\x89\x2e\xe9\xa4\xae\
+\x25\x3a\xaf\x1d\xb8\x7e\x6a\xcf\x09\x00\xe9\x21\xc8\x0c\x38\xd9\
+\xc8\x9f\xbe\xa2\x30\x72\xfe\x48\x0c\x80\x91\xbd\xee\x86\x82\x03\
+\xa0\x9c\x9b\x9a\x59\x50\x3d\x8e\x5b\x63\x73\x18\xd5\x84\x13\xef\
+\x88\xf4\xb5\xfe\xd6\x0a\xc0\xce\x22\x33\x04\x69\x00\xea\xee\x4b\
+\x9f\x67\x2b\xd2\xb3\x5f\x9e\x95\x71\x40\xca\x81\x78\x99\x85\x00\
+\xe0\x66\xe1\x64\x13\xbb\x00\xd4\x7f\x7b\x55\xdc\x59\x74\x48\xfc\
+\xc0\x66\x35\x34\xd1\x68\xeb\x5f\xda\xd8\x71\x00\xf0\x14\x8e\xa7\
+\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+"
+
+qt_resource_name = b"\
+\x00\x05\
+\x00\x6f\xa6\x53\
+\x00\x69\
+\x00\x63\x00\x6f\x00\x6e\x00\x73\
+\x00\x08\
+\x04\xd2\x59\x47\
+\x00\x69\
+\x00\x6e\x00\x66\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0c\
+\x07\x08\x5a\x67\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x73\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x16\
+\x02\x3c\x62\x67\
+\x00\x74\
+\x00\x72\x00\x75\x00\x73\x00\x74\x00\x65\x00\x64\x00\x63\x00\x6f\x00\x69\x00\x6e\x00\x2d\x00\x77\x00\x69\x00\x7a\x00\x61\x00\x72\
+\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x13\
+\x0b\x09\xcd\x87\
+\x00\x74\
+\x00\x72\x00\x65\x00\x7a\x00\x6f\x00\x72\x00\x5f\x00\x75\x00\x6e\x00\x70\x00\x61\x00\x69\x00\x72\x00\x65\x00\x64\x00\x2e\x00\x70\
+\x00\x6e\x00\x67\
+\x00\x17\
+\x0d\x0f\x89\x47\
+\x00\x65\
+\x00\x6c\x00\x65\x00\x63\x00\x74\x00\x72\x00\x75\x00\x6d\x00\x5f\x00\x6c\x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x69\x00\x63\
+\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x0b\xb7\x58\x67\
+\x00\x73\
+\x00\x65\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x16\
+\x01\x80\x75\x67\
+\x00\x74\
+\x00\x72\x00\x75\x00\x73\x00\x74\x00\x65\x00\x64\x00\x63\x00\x6f\x00\x69\x00\x6e\x00\x2d\x00\x73\x00\x74\x00\x61\x00\x74\x00\x75\
+\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x06\x60\x47\x67\
+\x00\x7a\
+\x00\x6f\x00\x6f\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x10\
+\x0b\xf0\xae\x27\
+\x00\x71\
+\x00\x72\x00\x63\x00\x6f\x00\x64\x00\x65\x00\x5f\x00\x77\x00\x68\x00\x69\x00\x74\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x07\xf8\xdd\x87\
+\x00\x75\
+\x00\x6e\x00\x70\x00\x61\x00\x69\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0d\
+\x07\x7e\xf7\xc7\
+\x00\x63\
+\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x72\x00\x6d\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x0b\x7f\x58\x67\
+\x00\x73\
+\x00\x65\x00\x61\x00\x6c\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x11\
+\x0c\xc5\x8e\x27\
+\x00\x64\
+\x00\x69\x00\x67\x00\x69\x00\x74\x00\x61\x00\x6c\x00\x62\x00\x69\x00\x74\x00\x62\x00\x6f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+\x00\x0c\
+\x04\x3d\x22\x27\
+\x00\x65\
+\x00\x6c\x00\x65\x00\x63\x00\x74\x00\x72\x00\x75\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x09\xed\x11\xe7\
+\x00\x63\
+\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x35\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x12\
+\x04\x28\x83\xe7\
+\x00\x73\
+\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x5f\x00\x6c\x00\x61\x00\x67\x00\x67\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\x00\x6e\
+\x00\x67\
+\x00\x0f\
+\x06\x95\xf4\x67\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x73\x00\x6f\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x1a\
+\x0e\x47\x86\xa7\
+\x00\x73\
+\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x65\x00\x64\x00\x5f\
+\x00\x70\x00\x72\x00\x6f\x00\x78\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0c\
+\x0a\xcf\x5b\x27\
+\x00\x74\
+\x00\x6f\x00\x72\x00\x5f\x00\x6c\x00\x6f\x00\x67\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x09\xea\x11\xe7\
+\x00\x63\
+\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x34\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x05\x9e\x59\x27\
+\x00\x6c\
+\x00\x6f\x00\x63\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0e\
+\x0f\x1d\x43\x47\
+\x00\x6f\
+\x00\x66\x00\x66\x00\x6c\x00\x69\x00\x6e\x00\x65\x00\x5f\x00\x74\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x13\
+\x0f\x56\x95\xc7\
+\x00\x6c\
+\x00\x65\x00\x64\x00\x67\x00\x65\x00\x72\x00\x5f\x00\x75\x00\x6e\x00\x70\x00\x61\x00\x69\x00\x72\x00\x65\x00\x64\x00\x2e\x00\x70\
+\x00\x6e\x00\x67\
+\x00\x0b\
+\x08\x6e\xb9\x47\
+\x00\x65\
+\x00\x78\x00\x70\x00\x69\x00\x72\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x09\xeb\x11\xe7\
+\x00\x63\
+\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x33\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0b\
+\x06\x67\xc0\x87\
+\x00\x6e\
+\x00\x65\x00\x74\x00\x77\x00\x6f\x00\x72\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x00\x18\xcd\xa7\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x72\x00\x65\x00\x63\x00\x65\x00\x69\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x05\x95\xdd\x27\
+\x00\x75\
+\x00\x6e\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0b\
+\x0d\x66\x2b\x87\
+\x00\x62\
+\x00\x74\x00\x78\x00\x5f\x00\x77\x00\x65\x00\x62\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x14\
+\x0a\x21\xdc\x47\
+\x00\x6b\
+\x00\x65\x00\x65\x00\x70\x00\x6b\x00\x65\x00\x79\x00\x5f\x00\x75\x00\x6e\x00\x70\x00\x61\x00\x69\x00\x72\x00\x65\x00\x64\x00\x2e\
+\x00\x70\x00\x6e\x00\x67\
+\x00\x10\
+\x02\xf3\xdb\xc7\
+\x00\x65\
+\x00\x6c\x00\x65\x00\x63\x00\x74\x00\x72\x00\x75\x00\x6d\x00\x5f\x00\x42\x00\x54\x00\x58\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x14\
+\x04\x52\xd8\x27\
+\x00\x73\
+\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\x00\x65\x00\x64\x00\x2e\
+\x00\x70\x00\x6e\x00\x67\
+\x00\x10\
+\x01\xf0\x7d\xa7\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x63\x00\x74\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x06\x27\xab\xc7\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x68\x00\x69\x00\x73\x00\x74\x00\x6f\x00\x72\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0b\
+\x00\xb5\x45\xe7\
+\x00\x77\
+\x00\x61\x00\x72\x00\x6e\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x1a\
+\x0e\x11\x56\x67\
+\x00\x64\
+\x00\x69\x00\x67\x00\x69\x00\x74\x00\x61\x00\x6c\x00\x62\x00\x69\x00\x74\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x75\x00\x6e\x00\x70\
+\x00\x61\x00\x69\x00\x72\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x09\xe8\x11\xe7\
+\x00\x63\
+\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0b\
+\x01\x31\x80\x47\
+\x00\x73\
+\x00\x70\x00\x65\x00\x61\x00\x6b\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x07\x7f\x07\x47\
+\x00\x75\
+\x00\x6e\x00\x63\x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x72\x00\x6d\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x0d\xcb\x00\x07\
+\x00\x6c\
+\x00\x65\x00\x64\x00\x67\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x09\xf1\x11\xe7\
+\x00\x63\
+\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x31\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0d\
+\x0c\xa6\xe2\x07\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x63\x00\x6f\x00\x69\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0b\
+\x01\x2b\x2f\xa7\
+\x00\x6b\
+\x00\x65\x00\x65\x00\x70\x00\x6b\x00\x65\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x05\xab\x46\x07\
+\x00\x71\
+\x00\x72\x00\x63\x00\x6f\x00\x64\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x11\
+\x07\x7c\xaf\x47\
+\x00\x74\
+\x00\x61\x00\x62\x00\x5f\x00\x61\x00\x64\x00\x64\x00\x72\x00\x65\x00\x73\x00\x73\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+\x00\x0e\
+\x0d\x16\x86\x47\
+\x00\x6d\
+\x00\x69\x00\x63\x00\x72\x00\x6f\x00\x70\x00\x68\x00\x6f\x00\x6e\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x06\x7c\x5a\x07\
+\x00\x63\
+\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0a\
+\x01\x6a\x26\xe7\
+\x00\x74\
+\x00\x72\x00\x65\x00\x7a\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x16\
+\x09\xa2\x64\x07\
+\x00\x65\
+\x00\x6c\x00\x65\x00\x63\x00\x74\x00\x72\x00\x75\x00\x6d\x00\x5f\x00\x64\x00\x61\x00\x72\x00\x6b\x00\x5f\x00\x69\x00\x63\x00\x6f\
+\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x07\
+\x01\xcc\x57\xa7\
+\x00\x6b\
+\x00\x65\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x17\
+\x0f\xfe\x47\x87\
+\x00\x73\
+\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x63\x00\x6f\x00\x6e\x00\x6e\x00\x65\x00\x63\x00\x74\
+\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x0e\x5e\x99\xe7\
+\x00\x70\
+\x00\x72\x00\x65\x00\x66\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x63\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x08\
+\x00\x28\x5a\xe7\
+\x00\x66\
+\x00\x69\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x12\
+\x04\x3b\x79\x07\
+\x00\x73\
+\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x5f\x00\x77\x00\x61\x00\x69\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x70\x00\x6e\
+\x00\x67\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x36\x00\x00\x00\x02\
+\x00\x00\x03\x80\x00\x00\x00\x00\x00\x01\x00\x02\x58\x04\
+\x00\x00\x06\xf8\x00\x00\x00\x00\x00\x01\x00\x03\x5e\x06\
+\x00\x00\x04\xa6\x00\x00\x00\x00\x00\x01\x00\x02\xdb\xfc\
+\x00\x00\x05\xaa\x00\x00\x00\x00\x00\x01\x00\x03\x16\xb1\
+\x00\x00\x05\x16\x00\x00\x00\x00\x00\x01\x00\x02\xfc\x94\
+\x00\x00\x06\x40\x00\x00\x00\x00\x00\x01\x00\x03\x2f\x49\
+\x00\x00\x00\xec\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x93\
+\x00\x00\x06\x8c\x00\x00\x00\x00\x00\x01\x00\x03\x3e\x73\
+\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x02\xcd\xc8\
+\x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x0d\x01\
+\x00\x00\x04\x08\x00\x00\x00\x00\x00\x01\x00\x02\x7b\xd4\
+\x00\x00\x02\x0a\x00\x00\x00\x00\x00\x01\x00\x01\xad\x5d\
+\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x03\x6b\x6e\
+\x00\x00\x01\xd2\x00\x00\x00\x00\x00\x01\x00\x01\x85\x2f\
+\x00\x00\x04\x2e\x00\x00\x00\x00\x00\x01\x00\x02\xc9\xb0\
+\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x03\xa4\x00\x00\x00\x00\x00\x01\x00\x02\x5f\x59\
+\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x01\xc7\x04\
+\x00\x00\x05\xc6\x00\x00\x00\x00\x00\x01\x00\x03\x21\x83\
+\x00\x00\x04\x82\x00\x00\x00\x00\x00\x01\x00\x02\xd3\x72\
+\x00\x00\x01\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x52\x89\
+\x00\x00\x03\x64\x00\x00\x00\x00\x00\x01\x00\x02\x4c\x29\
+\x00\x00\x06\x2a\x00\x00\x00\x00\x00\x01\x00\x03\x27\x06\
+\x00\x00\x02\x34\x00\x00\x00\x00\x00\x01\x00\x01\xb1\x6d\
+\x00\x00\x00\x26\x00\x00\x00\x00\x00\x01\x00\x00\x06\xef\
+\x00\x00\x05\xe0\x00\x00\x00\x00\x00\x01\x00\x03\x22\xc1\
+\x00\x00\x01\x74\x00\x00\x00\x00\x00\x01\x00\x00\xdf\x18\
+\x00\x00\x05\x32\x00\x00\x00\x00\x00\x01\x00\x02\xfe\x20\
+\x00\x00\x01\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x82\x17\
+\x00\x00\x03\x2e\x00\x00\x00\x00\x00\x01\x00\x01\xd9\x05\
+\x00\x00\x06\x5a\x00\x00\x00\x00\x00\x01\x00\x03\x39\x7b\
+\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x02\xf8\xe0\
+\x00\x00\x02\xb0\x00\x00\x00\x00\x00\x01\x00\x01\xc3\x3e\
+\x00\x00\x03\x4a\x00\x00\x00\x00\x00\x01\x00\x02\x48\x73\
+\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x01\xa9\x9d\
+\x00\x00\x05\x70\x00\x00\x00\x00\x00\x01\x00\x03\x0c\xbf\
+\x00\x00\x03\xda\x00\x00\x00\x00\x00\x01\x00\x02\x71\x2a\
+\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x01\xb8\xa2\
+\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x15\xa3\
+\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\xe4\x0f\
+\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x22\x3d\
+\x00\x00\x01\x34\x00\x00\x00\x00\x00\x01\x00\x00\x80\x97\
+\x00\x00\x05\x8a\x00\x00\x00\x00\x00\x01\x00\x03\x10\x75\
+\x00\x00\x01\xaa\x00\x00\x00\x00\x00\x01\x00\x01\x79\x97\
+\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x45\
+\x00\x00\x06\x08\x00\x00\x00\x00\x00\x01\x00\x03\x26\x3b\
+\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x02\x67\xd3\
+\x00\x00\x05\x56\x00\x00\x00\x00\x00\x01\x00\x03\x04\x4a\
+\x00\x00\x04\xc2\x00\x00\x00\x00\x00\x01\x00\x02\xee\xe7\
+\x00\x00\x02\x58\x00\x00\x00\x00\x00\x01\x00\x01\xb4\xa9\
+\x00\x00\x06\xd4\x00\x00\x00\x00\x00\x01\x00\x03\x57\x67\
+\x00\x00\x02\xe0\x00\x00\x00\x00\x00\x01\x00\x01\xce\xd2\
+\x00\x00\x03\x02\x00\x00\x00\x00\x00\x01\x00\x01\xd0\xa5\
+\x00\x00\x06\xa0\x00\x00\x00\x00\x00\x01\x00\x03\x53\xab\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py
new file mode 100644
index 000000000..d7103ae3b
--- /dev/null
+++ b/electrum/gui/qt/installwizard.py
@@ -0,0 +1,635 @@
+
+import os
+import sys
+import threading
+import traceback
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+
+from electrum.wallet import Wallet
+from electrum.storage import WalletStorage
+from electrum.util import UserCancelled, InvalidPassword
+from electrum.base_wizard import BaseWizard, HWD_SETUP_DECRYPT_WALLET, GoBack
+from electrum.i18n import _
+
+from .seed_dialog import SeedLayout, KeysLayout
+from .network_dialog import NetworkChoiceLayout
+from .util import *
+from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
+
+
+MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
+ + _("Leave this field empty if you want to disable encryption.")
+MSG_HW_STORAGE_ENCRYPTION = _("Set wallet file encryption.") + '\n'\
+ + _("Your wallet file does not contain secrets, mostly just metadata. ") \
+ + _("It also contains your master public key that allows watching your addresses.") + '\n\n'\
+ + _("Note: If you enable this setting, you will need your hardware device to open your wallet.")
+WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
+ _('A few examples') + ':\n' +
+ 'p2pkh:KxZcY47uGp9a... \t-> 1DckmggQM...\n' +
+ 'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' +
+ 'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...')
+# note: full key is KxZcY47uGp9aVQAb6VVvuBs8SwHKgkSR2DbZUzjDzXf2N2GPhG9n
+MSG_PASSPHRASE_WARN_ISSUE4566 = _("Warning") + ": "\
+ + _("You have multiple consecutive whitespaces or leading/trailing "
+ "whitespaces in your passphrase.") + " " \
+ + _("This is discouraged.") + " " \
+ + _("Due to a bug, old versions of Electrum will NOT be creating the "
+ "same wallet as newer versions or other software.")
+
+
+class CosignWidget(QWidget):
+ size = 120
+
+ def __init__(self, m, n):
+ QWidget.__init__(self)
+ self.R = QRect(0, 0, self.size, self.size)
+ self.setGeometry(self.R)
+ self.setMinimumHeight(self.size)
+ self.setMaximumHeight(self.size)
+ self.m = m
+ self.n = n
+
+ def set_n(self, n):
+ self.n = n
+ self.update()
+
+ def set_m(self, m):
+ self.m = m
+ self.update()
+
+ def paintEvent(self, event):
+ bgcolor = self.palette().color(QPalette.Background)
+ pen = QPen(bgcolor, 7, Qt.SolidLine)
+ qp = QPainter()
+ qp.begin(self)
+ qp.setPen(pen)
+ qp.setRenderHint(QPainter.Antialiasing)
+ qp.setBrush(Qt.gray)
+ for i in range(self.n):
+ alpha = int(16* 360 * i/self.n)
+ alpha2 = int(16* 360 * 1/self.n)
+ qp.setBrush(Qt.green if i%s"%title if title else "")
+ self.title.setVisible(bool(title))
+ # Get rid of any prior layout by assigning it to a temporary widget
+ prior_layout = self.main_widget.layout()
+ if prior_layout:
+ QWidget().setLayout(prior_layout)
+ self.main_widget.setLayout(layout)
+ self.back_button.setEnabled(True)
+ self.next_button.setEnabled(next_enabled)
+ if next_enabled:
+ self.next_button.setFocus()
+ self.main_widget.setVisible(True)
+ self.please_wait.setVisible(False)
+
+ def exec_layout(self, layout, title=None, raise_on_cancel=True,
+ next_enabled=True):
+ self.set_layout(layout, title, next_enabled)
+ result = self.loop.exec_()
+ if not result and raise_on_cancel:
+ raise UserCancelled
+ if result == 1:
+ raise GoBack from None
+ self.title.setVisible(False)
+ self.back_button.setEnabled(False)
+ self.next_button.setEnabled(False)
+ self.main_widget.setVisible(False)
+ self.please_wait.setVisible(True)
+ self.refresh_gui()
+ return result
+
+ def refresh_gui(self):
+ # For some reason, to refresh the GUI this needs to be called twice
+ self.app.processEvents()
+ self.app.processEvents()
+
+ def remove_from_recently_open(self, filename):
+ self.config.remove_from_recently_open(filename)
+
+ def text_input(self, title, message, is_valid, allow_multi=False):
+ slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid,
+ allow_multi=allow_multi)
+ self.exec_layout(slayout, title, next_enabled=False)
+ return slayout.get_text()
+
+ def seed_input(self, title, message, is_seed, options):
+ slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self)
+ self.exec_layout(slayout, title, next_enabled=False)
+ return slayout.get_seed(), slayout.is_bip39, slayout.is_ext
+
+ @wizard_dialog
+ def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False):
+ header_layout = QHBoxLayout()
+ label = WWLabel(message)
+ label.setMinimumWidth(400)
+ header_layout.addWidget(label)
+ if show_wif_help:
+ header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
+ return self.text_input(title, header_layout, is_valid, allow_multi)
+
+ @wizard_dialog
+ def add_cosigner_dialog(self, run_next, index, is_valid):
+ title = _("Add Cosigner") + " %d"%index
+ message = ' '.join([
+ _('Please enter the master public key (xpub) of your cosigner.'),
+ _('Enter their master private key (xprv) if you want to be able to sign for them.')
+ ])
+ return self.text_input(title, message, is_valid)
+
+ @wizard_dialog
+ def restore_seed_dialog(self, run_next, test):
+ options = []
+ if self.opt_ext:
+ options.append('ext')
+ if self.opt_bip39:
+ options.append('bip39')
+ title = _('Enter Seed')
+ message = _('Please enter your seed phrase in order to restore your wallet.')
+ return self.seed_input(title, message, test, options)
+
+ @wizard_dialog
+ def confirm_seed_dialog(self, run_next, test):
+ self.app.clipboard().clear()
+ title = _('Confirm Seed')
+ message = ' '.join([
+ _('Your seed is important!'),
+ _('If you lose your seed, your money will be permanently lost.'),
+ _('To make sure that you have properly saved your seed, please retype it here.')
+ ])
+ seed, is_bip39, is_ext = self.seed_input(title, message, test, None)
+ return seed
+
+ @wizard_dialog
+ def show_seed_dialog(self, run_next, seed_text):
+ title = _("Your wallet generation seed is:")
+ slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext'])
+ self.exec_layout(slayout)
+ return slayout.is_ext
+
+ def pw_layout(self, msg, kind, force_disable_encrypt_cb):
+ playout = PasswordLayout(None, msg, kind, self.next_button,
+ force_disable_encrypt_cb=force_disable_encrypt_cb)
+ playout.encrypt_cb.setChecked(True)
+ self.exec_layout(playout.layout())
+ return playout.new_password(), playout.encrypt_cb.isChecked()
+
+ @wizard_dialog
+ def request_password(self, run_next, force_disable_encrypt_cb=False):
+ """Request the user enter a new password and confirm it. Return
+ the password or None for no password."""
+ return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb)
+
+ @wizard_dialog
+ def request_storage_encryption(self, run_next):
+ playout = PasswordLayoutForHW(None, MSG_HW_STORAGE_ENCRYPTION, PW_NEW, self.next_button)
+ playout.encrypt_cb.setChecked(True)
+ self.exec_layout(playout.layout())
+ return playout.encrypt_cb.isChecked()
+
+ @wizard_dialog
+ def confirm_dialog(self, title, message, run_next):
+ self.confirm(message, title)
+
+ def confirm(self, message, title):
+ label = WWLabel(message)
+ vbox = QVBoxLayout()
+ vbox.addWidget(label)
+ self.exec_layout(vbox, title)
+
+ @wizard_dialog
+ def action_dialog(self, action, run_next):
+ self.run(action)
+
+ def terminate(self):
+ self.accept_signal.emit()
+
+ def waiting_dialog(self, task, msg, on_finished=None):
+ label = WWLabel(msg)
+ vbox = QVBoxLayout()
+ vbox.addSpacing(100)
+ label.setMinimumWidth(300)
+ label.setAlignment(Qt.AlignCenter)
+ vbox.addWidget(label)
+ self.set_layout(vbox, next_enabled=False)
+ self.back_button.setEnabled(False)
+
+ t = threading.Thread(target=task)
+ t.start()
+ while True:
+ t.join(1.0/60)
+ if t.is_alive():
+ self.refresh_gui()
+ else:
+ break
+ if on_finished:
+ on_finished()
+
+ @wizard_dialog
+ def choice_dialog(self, title, message, choices, run_next):
+ c_values = [x[0] for x in choices]
+ c_titles = [x[1] for x in choices]
+ clayout = ChoicesLayout(message, c_titles)
+ vbox = QVBoxLayout()
+ vbox.addLayout(clayout.layout())
+ self.exec_layout(vbox, title)
+ action = c_values[clayout.selected_index()]
+ return action
+
+ def query_choice(self, msg, choices):
+ """called by hardware wallets"""
+ clayout = ChoicesLayout(msg, choices)
+ vbox = QVBoxLayout()
+ vbox.addLayout(clayout.layout())
+ self.exec_layout(vbox, '')
+ return clayout.selected_index()
+
+ @wizard_dialog
+ def choice_and_line_dialog(self, title, message1, choices, message2,
+ test_text, run_next) -> (str, str):
+ vbox = QVBoxLayout()
+
+ c_values = [x[0] for x in choices]
+ c_titles = [x[1] for x in choices]
+ c_default_text = [x[2] for x in choices]
+ def on_choice_click(clayout):
+ idx = clayout.selected_index()
+ line.setText(c_default_text[idx])
+ clayout = ChoicesLayout(message1, c_titles, on_choice_click)
+ vbox.addLayout(clayout.layout())
+
+ vbox.addSpacing(50)
+ vbox.addWidget(WWLabel(message2))
+
+ line = QLineEdit()
+ def on_text_change(text):
+ self.next_button.setEnabled(test_text(text))
+ line.textEdited.connect(on_text_change)
+ on_choice_click(clayout) # set default text for "line"
+ vbox.addWidget(line)
+
+ self.exec_layout(vbox, title)
+ choice = c_values[clayout.selected_index()]
+ return str(line.text()), choice
+
+ @wizard_dialog
+ def line_dialog(self, run_next, title, message, default, test, warning='',
+ presets=(), warn_issue4566=False):
+ vbox = QVBoxLayout()
+ vbox.addWidget(WWLabel(message))
+ line = QLineEdit()
+ line.setText(default)
+ def f(text):
+ self.next_button.setEnabled(test(text))
+ if warn_issue4566:
+ text_whitespace_normalised = ' '.join(text.split())
+ warn_issue4566_label.setVisible(text != text_whitespace_normalised)
+ line.textEdited.connect(f)
+ vbox.addWidget(line)
+ vbox.addWidget(WWLabel(warning))
+
+ warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566)
+ warn_issue4566_label.setVisible(False)
+ vbox.addWidget(warn_issue4566_label)
+
+ for preset in presets:
+ button = QPushButton(preset[0])
+ button.clicked.connect(lambda __, text=preset[1]: line.setText(text))
+ button.setMinimumWidth(150)
+ hbox = QHBoxLayout()
+ hbox.addWidget(button, alignment=Qt.AlignCenter)
+ vbox.addLayout(hbox)
+
+ self.exec_layout(vbox, title, next_enabled=test(default))
+ return line.text()
+
+ @wizard_dialog
+ def show_xpub_dialog(self, xpub, run_next):
+ msg = ' '.join([
+ _("Here is your master public key."),
+ _("Please share it with your cosigners.")
+ ])
+ vbox = QVBoxLayout()
+ layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False)
+ vbox.addLayout(layout.layout())
+ self.exec_layout(vbox, _('Master Public Key'))
+ return None
+
+ def init_network(self, network):
+ message = _("Electrum communicates with remote servers to get "
+ "information about your transactions and addresses. The "
+ "servers all fulfill the same purpose only differing in "
+ "hardware. In most cases you simply want to let Electrum "
+ "pick one at random. However if you prefer feel free to "
+ "select a server manually.")
+ choices = [_("Auto connect"), _("Select server manually")]
+ title = _("How do you want to connect to a server? ")
+ clayout = ChoicesLayout(message, choices)
+ self.back_button.setText(_('Cancel'))
+ self.exec_layout(clayout.layout(), title)
+ r = clayout.selected_index()
+ if r == 1:
+ nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
+ if self.exec_layout(nlayout.layout()):
+ nlayout.accept()
+ else:
+ network.auto_connect = True
+ self.config.set_key('auto_connect', True, True)
+
+ @wizard_dialog
+ def multisig_dialog(self, run_next):
+ cw = CosignWidget(2, 2)
+ m_edit = QSlider(Qt.Horizontal, self)
+ n_edit = QSlider(Qt.Horizontal, self)
+ n_edit.setMinimum(2)
+ n_edit.setMaximum(15)
+ m_edit.setMinimum(1)
+ m_edit.setMaximum(2)
+ n_edit.setValue(2)
+ m_edit.setValue(2)
+ n_label = QLabel()
+ m_label = QLabel()
+ grid = QGridLayout()
+ grid.addWidget(n_label, 0, 0)
+ grid.addWidget(n_edit, 0, 1)
+ grid.addWidget(m_label, 1, 0)
+ grid.addWidget(m_edit, 1, 1)
+ def on_m(m):
+ m_label.setText(_('Require {0} signatures').format(m))
+ cw.set_m(m)
+ def on_n(n):
+ n_label.setText(_('From {0} cosigners').format(n))
+ cw.set_n(n)
+ m_edit.setMaximum(n)
+ n_edit.valueChanged.connect(on_n)
+ m_edit.valueChanged.connect(on_m)
+ on_n(2)
+ on_m(2)
+ vbox = QVBoxLayout()
+ vbox.addWidget(cw)
+ vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
+ vbox.addLayout(grid)
+ self.exec_layout(vbox, _("Multi-Signature Wallet"))
+ m = int(m_edit.value())
+ n = int(n_edit.value())
+ return (m, n)
diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
new file mode 100644
index 000000000..462aadd85
--- /dev/null
+++ b/electrum/gui/qt/invoice_list.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.i18n import _
+from electrum.util import format_time
+
+from .util import *
+
+
+class InvoiceList(MyTreeWidget):
+ filter_columns = [0, 1, 2, 3] # Date, Requestor, Description, Amount
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')], 2)
+ self.setSortingEnabled(True)
+ self.header().setSectionResizeMode(1, QHeaderView.Interactive)
+ self.setColumnWidth(1, 200)
+
+ def on_update(self):
+ inv_list = self.parent.invoices.unpaid_invoices()
+ self.clear()
+ for pr in inv_list:
+ key = pr.get_id()
+ status = self.parent.invoices.get_status(key)
+ requestor = pr.get_requestor()
+ exp = pr.get_expiration_date()
+ date_str = format_time(exp) if exp else _('Never')
+ item = QTreeWidgetItem([date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')])
+ item.setIcon(4, self.icon_cache.get(pr_icons.get(status)))
+ item.setData(0, Qt.UserRole, key)
+ item.setFont(1, QFont(MONOSPACE_FONT))
+ item.setFont(3, QFont(MONOSPACE_FONT))
+ self.addTopLevelItem(item)
+ self.setCurrentItem(self.topLevelItem(0))
+ self.setVisible(len(inv_list))
+ self.parent.invoices_label.setVisible(len(inv_list))
+
+ def import_invoices(self):
+ import_meta_gui(self.parent, _('invoices'), self.parent.invoices.import_file, self.on_update)
+
+ def export_invoices(self):
+ export_meta_gui(self.parent, _('invoices'), self.parent.invoices.export_file)
+
+ def create_menu(self, position):
+ menu = QMenu()
+ item = self.itemAt(position)
+ if not item:
+ return
+ key = item.data(0, Qt.UserRole)
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+ pr = self.parent.invoices.get(key)
+ status = self.parent.invoices.get_status(key)
+ if column_data:
+ menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
+ menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))
+ if status == PR_UNPAID:
+ menu.addAction(_("Pay Now"), lambda: self.parent.do_pay_invoice(key))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(key))
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
new file mode 100644
index 000000000..691d21bae
--- /dev/null
+++ b/electrum/gui/qt/main_window.py
@@ -0,0 +1,3222 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcore client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import sys, time, threading
+import os, json, traceback
+import shutil
+import weakref
+import webbrowser
+import csv
+from decimal import Decimal
+import base64
+from functools import partial
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+import PyQt5.QtCore as QtCore
+
+from .exception_window import Exception_Hook
+from PyQt5.QtWidgets import *
+
+from electrum import (keystore, simple_config, ecc, constants, util, bitcoin, commands,
+ coinchooser, paymentrequest)
+from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS
+from electrum.plugin import run_hook
+from electrum.i18n import _
+from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
+ format_satoshis_plain, NotEnoughFunds, PrintError,
+ UserCancelled, NoDynamicFeeEstimates, profiler,
+ export_meta, import_meta, bh2u, bfh, InvalidPassword,
+ base_units, base_units_list, base_unit_name_to_decimal_point,
+ decimal_point_to_base_unit_name, quantize_feerate)
+from electrum.transaction import Transaction, TxOutput
+from electrum.address_synchronizer import AddTransactionException
+from electrum.wallet import Multisig_Wallet, CannotBumpFee
+
+from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
+from .qrcodewidget import QRCodeWidget, QRDialog
+from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
+from .transaction_dialog import show_transaction
+from .fee_slider import FeeSlider
+from .util import *
+from .installwizard import WIF_HELP_TEXT
+
+
+class StatusBarButton(QPushButton):
+ def __init__(self, icon, tooltip, func):
+ QPushButton.__init__(self, icon, '')
+ self.setToolTip(tooltip)
+ self.setFlat(True)
+ self.setMaximumWidth(25)
+ self.clicked.connect(self.onPress)
+ self.func = func
+ self.setIconSize(QSize(25,25))
+
+ def onPress(self, checked=False):
+ '''Drops the unwanted PyQt5 "checked" argument'''
+ self.func()
+
+ def keyPressEvent(self, e):
+ if e.key() == Qt.Key_Return:
+ self.func()
+
+
+from electrum.paymentrequest import PR_PAID
+
+
+class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
+
+ payment_request_ok_signal = pyqtSignal()
+ payment_request_error_signal = pyqtSignal()
+ notify_transactions_signal = pyqtSignal()
+ new_fx_quotes_signal = pyqtSignal()
+ new_fx_history_signal = pyqtSignal()
+ network_signal = pyqtSignal(str, object)
+ alias_received_signal = pyqtSignal()
+ computing_privkeys_signal = pyqtSignal()
+ show_privkeys_signal = pyqtSignal()
+
+ def __init__(self, gui_object, wallet):
+ QMainWindow.__init__(self)
+
+ self.gui_object = gui_object
+ self.config = config = gui_object.config
+
+ self.setup_exception_hook()
+
+ self.network = gui_object.daemon.network
+ self.fx = gui_object.daemon.fx
+ self.invoices = wallet.invoices
+ self.contacts = wallet.contacts
+ self.tray = gui_object.tray
+ self.app = gui_object.app
+ self.cleaned_up = False
+ self.is_max = False
+ self.payment_request = None
+ self.checking_accounts = False
+ self.qr_window = None
+ self.not_enough_funds = False
+ self.pluginsdialog = None
+ self.require_fee_update = False
+ self.tx_notifications = []
+ self.tl_windows = []
+ self.tx_external_keypairs = {}
+
+ self.create_status_bar()
+ self.need_update = threading.Event()
+
+ self.decimal_point = config.get('decimal_point', 8)
+ self.num_zeros = int(config.get('num_zeros',0))
+
+ self.completions = QStringListModel()
+
+ self.tabs = tabs = QTabWidget(self)
+ self.send_tab = self.create_send_tab()
+ self.receive_tab = self.create_receive_tab()
+ self.addresses_tab = self.create_addresses_tab()
+ self.utxo_tab = self.create_utxo_tab()
+ self.console_tab = self.create_console_tab()
+ self.contacts_tab = self.create_contacts_tab()
+ tabs.addTab(self.create_history_tab(), QIcon(":icons/tab_history.png"), _('History'))
+ tabs.addTab(self.send_tab, QIcon(":icons/tab_send.png"), _('Send'))
+ tabs.addTab(self.receive_tab, QIcon(":icons/tab_receive.png"), _('Receive'))
+
+ def add_optional_tab(tabs, tab, icon, description, name):
+ tab.tab_icon = icon
+ tab.tab_description = description
+ tab.tab_pos = len(tabs)
+ tab.tab_name = name
+ if self.config.get('show_{}_tab'.format(name), False):
+ tabs.addTab(tab, icon, description.replace("&", ""))
+
+ add_optional_tab(tabs, self.addresses_tab, QIcon(":icons/tab_addresses.png"), _("&Addresses"), "addresses")
+ add_optional_tab(tabs, self.utxo_tab, QIcon(":icons/tab_coins.png"), _("Co&ins"), "utxo")
+ add_optional_tab(tabs, self.contacts_tab, QIcon(":icons/tab_contacts.png"), _("Con&tacts"), "contacts")
+ add_optional_tab(tabs, self.console_tab, QIcon(":icons/tab_console.png"), _("Con&sole"), "console")
+
+ tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.setCentralWidget(tabs)
+
+ if self.config.get("is_maximized"):
+ self.showMaximized()
+
+ self.setWindowIcon(QIcon(":icons/electrum_BTX.png"))
+ self.init_menubar()
+
+ wrtabs = weakref.proxy(tabs)
+ QShortcut(QKeySequence("Ctrl+W"), self, self.close)
+ QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
+ QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
+ QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() - 1)%wrtabs.count()))
+ QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() + 1)%wrtabs.count()))
+
+ for i in range(wrtabs.count()):
+ QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: wrtabs.setCurrentIndex(i))
+
+ self.payment_request_ok_signal.connect(self.payment_request_ok)
+ self.payment_request_error_signal.connect(self.payment_request_error)
+ self.notify_transactions_signal.connect(self.notify_transactions)
+ self.history_list.setFocus(True)
+
+ # network callbacks
+ if self.network:
+ self.network_signal.connect(self.on_network_qt)
+ interests = ['updated', 'new_transaction', 'status',
+ 'banner', 'verified', 'fee']
+ # To avoid leaking references to "self" that prevent the
+ # window from being GC-ed when closed, callbacks should be
+ # methods of this class only, and specifically not be
+ # partials, lambdas or methods of subobjects. Hence...
+ self.network.register_callback(self.on_network, interests)
+ # set initial message
+ self.console.showMessage(self.network.banner)
+ self.network.register_callback(self.on_quotes, ['on_quotes'])
+ self.network.register_callback(self.on_history, ['on_history'])
+ self.new_fx_quotes_signal.connect(self.on_fx_quotes)
+ self.new_fx_history_signal.connect(self.on_fx_history)
+
+ # update fee slider in case we missed the callback
+ self.fee_slider.update()
+ self.load_wallet(wallet)
+ self.connect_slots(gui_object.timer)
+ self.fetch_alias()
+
+ def on_history(self, b):
+ self.new_fx_history_signal.emit()
+
+ def setup_exception_hook(self):
+ Exception_Hook(self)
+
+ def on_fx_history(self):
+ self.history_list.refresh_headers()
+ self.history_list.update()
+ self.address_list.update()
+
+ def on_quotes(self, b):
+ self.new_fx_quotes_signal.emit()
+
+ def on_fx_quotes(self):
+ self.update_status()
+ # Refresh edits with the new rate
+ edit = self.fiat_send_e if self.fiat_send_e.is_last_edited else self.amount_e
+ edit.textEdited.emit(edit.text())
+ edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e
+ edit.textEdited.emit(edit.text())
+ # History tab needs updating if it used spot
+ if self.fx.history_used_spot:
+ self.history_list.update()
+
+ def toggle_tab(self, tab):
+ show = not self.config.get('show_{}_tab'.format(tab.tab_name), False)
+ self.config.set_key('show_{}_tab'.format(tab.tab_name), show)
+ item_text = (_("Hide") if show else _("Show")) + " " + tab.tab_description
+ tab.menu_action.setText(item_text)
+ if show:
+ # Find out where to place the tab
+ index = len(self.tabs)
+ for i in range(len(self.tabs)):
+ try:
+ if tab.tab_pos < self.tabs.widget(i).tab_pos:
+ index = i
+ break
+ except AttributeError:
+ pass
+ self.tabs.insertTab(index, tab, tab.tab_icon, tab.tab_description.replace("&", ""))
+ else:
+ i = self.tabs.indexOf(tab)
+ self.tabs.removeTab(i)
+
+ def push_top_level_window(self, window):
+ '''Used for e.g. tx dialog box to ensure new dialogs are appropriately
+ parented. This used to be done by explicitly providing the parent
+ window, but that isn't something hardware wallet prompts know.'''
+ self.tl_windows.append(window)
+
+ def pop_top_level_window(self, window):
+ self.tl_windows.remove(window)
+
+ def top_level_window(self, test_func=None):
+ '''Do the right thing in the presence of tx dialog windows'''
+ override = self.tl_windows[-1] if self.tl_windows else None
+ if override and test_func and not test_func(override):
+ override = None # only override if ok for test_func
+ return self.top_level_window_recurse(override, test_func)
+
+ def diagnostic_name(self):
+ return "%s/%s" % (PrintError.diagnostic_name(self),
+ self.wallet.basename() if self.wallet else "None")
+
+ def is_hidden(self):
+ return self.isMinimized() or self.isHidden()
+
+ def show_or_hide(self):
+ if self.is_hidden():
+ self.bring_to_top()
+ else:
+ self.hide()
+
+ def bring_to_top(self):
+ self.show()
+ self.raise_()
+
+ def on_error(self, exc_info):
+ if not isinstance(exc_info[1], UserCancelled):
+ try:
+ traceback.print_exception(*exc_info)
+ except OSError:
+ pass # see #4418; try to at least show popup:
+ self.show_error(str(exc_info[1]))
+
+ def on_network(self, event, *args):
+ if event == 'updated':
+ self.need_update.set()
+ self.gui_object.network_updated_signal_obj.network_updated_signal \
+ .emit(event, args)
+ elif event == 'new_transaction':
+ self.tx_notifications.append(args[0])
+ self.notify_transactions_signal.emit()
+ elif event in ['status', 'banner', 'verified', 'fee']:
+ # Handle in GUI thread
+ self.network_signal.emit(event, args)
+ else:
+ self.print_error("unexpected network message:", event, args)
+
+ def on_network_qt(self, event, args=None):
+ # Handle a network message in the GUI thread
+ if event == 'status':
+ self.update_status()
+ elif event == 'banner':
+ self.console.showMessage(args[0])
+ elif event == 'verified':
+ self.history_list.update_item(*args)
+ elif event == 'fee':
+ if self.config.is_dynfee():
+ self.fee_slider.update()
+ self.do_update_fee()
+ elif event == 'fee_histogram':
+ if self.config.is_dynfee():
+ self.fee_slider.update()
+ self.do_update_fee()
+ # todo: update only unconfirmed tx
+ self.history_list.update()
+ else:
+ self.print_error("unexpected network_qt signal:", event, args)
+
+ def fetch_alias(self):
+ self.alias_info = None
+ alias = self.config.get('alias')
+ if alias:
+ alias = str(alias)
+ def f():
+ self.alias_info = self.contacts.resolve_openalias(alias)
+ self.alias_received_signal.emit()
+ t = threading.Thread(target=f)
+ t.setDaemon(True)
+ t.start()
+
+ def close_wallet(self):
+ if self.wallet:
+ self.print_error('close_wallet', self.wallet.storage.path)
+ run_hook('close_wallet', self.wallet)
+
+ @profiler
+ def load_wallet(self, wallet):
+ wallet.thread = TaskThread(self, self.on_error)
+ self.wallet = wallet
+ self.update_recently_visited(wallet.storage.path)
+ # address used to create a dummy transaction and estimate transaction fee
+ self.history_list.update()
+ self.address_list.update()
+ self.utxo_list.update()
+ self.need_update.set()
+ # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
+ self.notify_transactions()
+ # update menus
+ self.seed_menu.setEnabled(self.wallet.has_seed())
+ self.update_lock_icon()
+ self.update_buttons_on_seed()
+ self.update_console()
+ self.clear_receive_tab()
+ self.request_list.update()
+ self.tabs.show()
+ self.init_geometry()
+ if self.config.get('hide_gui') and self.gui_object.tray.isVisible():
+ self.hide()
+ else:
+ self.show()
+ self.watching_only_changed()
+ run_hook('load_wallet', wallet, self)
+
+ def init_geometry(self):
+ winpos = self.wallet.storage.get("winpos-qt")
+ try:
+ screen = self.app.desktop().screenGeometry()
+ assert screen.contains(QRect(*winpos))
+ self.setGeometry(*winpos)
+ except:
+ self.print_error("using default geometry")
+ self.setGeometry(100, 100, 840, 400)
+
+ def watching_only_changed(self):
+ name = "Electrum Testnet" if constants.net.TESTNET else "Electrum"
+ title = '%s %s - %s' % (name, self.wallet.electrum_version,
+ self.wallet.basename())
+ extra = [self.wallet.storage.get('wallet_type', '?')]
+ if self.wallet.is_watching_only():
+ self.warn_if_watching_only()
+ extra.append(_('watching only'))
+ title += ' [%s]'% ', '.join(extra)
+ self.setWindowTitle(title)
+ self.password_menu.setEnabled(self.wallet.may_have_password())
+ self.import_privkey_menu.setVisible(self.wallet.can_import_privkey())
+ self.import_address_menu.setVisible(self.wallet.can_import_address())
+ self.export_menu.setEnabled(self.wallet.can_export())
+
+ def warn_if_watching_only(self):
+ if self.wallet.is_watching_only():
+ msg = ' '.join([
+ _("This wallet is watching-only."),
+ _("This means you will not be able to spend Bitcores with it."),
+ _("Make sure you own the seed phrase or the private keys, before you request Bitcores to be sent to this wallet.")
+
+ ])
+ self.show_warning(msg, title=_('Information'))
+
+ def open_wallet(self):
+ try:
+ wallet_folder = self.get_wallet_folder()
+ except FileNotFoundError as e:
+ self.show_error(str(e))
+ return
+ filename, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
+ if not filename:
+ return
+ self.gui_object.new_window(filename)
+
+
+ def backup_wallet(self):
+ path = self.wallet.storage.path
+ wallet_folder = os.path.dirname(path)
+ filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder)
+ if not filename:
+ return
+ new_path = os.path.join(wallet_folder, filename)
+ if new_path != path:
+ try:
+ shutil.copy2(path, new_path)
+ self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created"))
+ except BaseException as reason:
+ self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup"))
+
+ def update_recently_visited(self, filename):
+ recent = self.config.get('recently_open', [])
+ try:
+ sorted(recent)
+ except:
+ recent = []
+ if filename in recent:
+ recent.remove(filename)
+ recent.insert(0, filename)
+ recent = recent[:5]
+ self.config.set_key('recently_open', recent)
+ self.recently_visited_menu.clear()
+ for i, k in enumerate(sorted(recent)):
+ b = os.path.basename(k)
+ def loader(k):
+ return lambda: self.gui_object.new_window(k)
+ self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%(i+1)))
+ self.recently_visited_menu.setEnabled(len(recent))
+
+ def get_wallet_folder(self):
+ return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
+
+ def new_wallet(self):
+ try:
+ wallet_folder = self.get_wallet_folder()
+ except FileNotFoundError as e:
+ self.show_error(str(e))
+ return
+ i = 1
+ while True:
+ filename = "wallet_%d" % i
+ if filename in os.listdir(wallet_folder):
+ i += 1
+ else:
+ break
+ full_path = os.path.join(wallet_folder, filename)
+ self.gui_object.start_new_window(full_path, None)
+
+ def init_menubar(self):
+ menubar = QMenuBar()
+
+ file_menu = menubar.addMenu(_("&File"))
+ self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
+ file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
+ file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
+ file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
+ file_menu.addAction(_("Delete"), self.remove_wallet)
+ file_menu.addSeparator()
+ file_menu.addAction(_("&Quit"), self.close)
+
+ wallet_menu = menubar.addMenu(_("&Wallet"))
+ wallet_menu.addAction(_("&Information"), self.show_master_public_keys)
+ wallet_menu.addSeparator()
+ self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
+ self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
+ self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
+ self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
+ self.import_privkey_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
+ self.export_menu = self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
+ self.import_address_menu = wallet_menu.addAction(_("Import addresses"), self.import_addresses)
+ wallet_menu.addSeparator()
+
+ addresses_menu = wallet_menu.addMenu(_("&Addresses"))
+ addresses_menu.addAction(_("&Filter"), lambda: self.address_list.toggle_toolbar(self.config))
+ labels_menu = wallet_menu.addMenu(_("&Labels"))
+ labels_menu.addAction(_("&Import"), self.do_import_labels)
+ labels_menu.addAction(_("&Export"), self.do_export_labels)
+ history_menu = wallet_menu.addMenu(_("&History"))
+ history_menu.addAction(_("&Filter"), lambda: self.history_list.toggle_toolbar(self.config))
+ history_menu.addAction(_("&Summary"), self.history_list.show_summary)
+ history_menu.addAction(_("&Plot"), self.history_list.plot_history_dialog)
+ history_menu.addAction(_("&Export"), self.history_list.export_history_dialog)
+ contacts_menu = wallet_menu.addMenu(_("Contacts"))
+ contacts_menu.addAction(_("&New"), self.new_contact_dialog)
+ contacts_menu.addAction(_("Import"), lambda: self.contact_list.import_contacts())
+ contacts_menu.addAction(_("Export"), lambda: self.contact_list.export_contacts())
+ invoices_menu = wallet_menu.addMenu(_("Invoices"))
+ invoices_menu.addAction(_("Import"), lambda: self.invoice_list.import_invoices())
+ invoices_menu.addAction(_("Export"), lambda: self.invoice_list.export_invoices())
+
+ wallet_menu.addSeparator()
+ wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F"))
+
+ def add_toggle_action(view_menu, tab):
+ is_shown = self.config.get('show_{}_tab'.format(tab.tab_name), False)
+ item_name = (_("Hide") if is_shown else _("Show")) + " " + tab.tab_description
+ tab.menu_action = view_menu.addAction(item_name, lambda: self.toggle_tab(tab))
+
+ view_menu = menubar.addMenu(_("&View"))
+ add_toggle_action(view_menu, self.addresses_tab)
+ add_toggle_action(view_menu, self.utxo_tab)
+ add_toggle_action(view_menu, self.contacts_tab)
+ add_toggle_action(view_menu, self.console_tab)
+
+ tools_menu = menubar.addMenu(_("&Tools"))
+
+ # Settings / Preferences are all reserved keywords in macOS using this as work around
+ tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
+ tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self))
+ tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
+ tools_menu.addSeparator()
+ tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
+ tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
+ tools_menu.addSeparator()
+
+ paytomany_menu = tools_menu.addAction(_("&Pay to many"), self.paytomany)
+
+ raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
+ raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
+ raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
+ raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
+ raw_transaction_menu.addAction(_("&From QR code"), self.read_tx_from_qrcode)
+ self.raw_transaction_menu = raw_transaction_menu
+ run_hook('init_menubar_tools', self, tools_menu)
+
+ help_menu = menubar.addMenu(_("&Help"))
+ help_menu.addAction(_("&About"), self.show_about)
+ help_menu.addAction(_("&Official website"), lambda: webbrowser.open("https://bitcore.cc"))
+ help_menu.addSeparator()
+ help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents)
+ help_menu.addAction(_("&Report Bug"), self.show_report_bug)
+ help_menu.addSeparator()
+ help_menu.addAction(_("&Donate to server"), self.donate_to_server)
+
+ self.setMenuBar(menubar)
+
+ def donate_to_server(self):
+ d = self.network.get_donation_address()
+ if d:
+ host = self.network.get_parameters()[0]
+ self.pay_to_URI('bitcore:%s?message=donation for %s'%(d, host))
+ else:
+ self.show_error(_('No donation address for this server'))
+
+ def show_about(self):
+ QMessageBox.about(self, "Electrum",
+ (_("Version")+" %s" % self.wallet.electrum_version + "\n\n" +
+ _("Electrum's focus is speed, with low resource usage and simplifying Bitcore.") + " " +
+ _("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 Bitcore system.") + "\n\n" +
+ _("Uses icons from the Icons8 icon pack (icons8.com).")))
+
+ def show_report_bug(self):
+ msg = ' '.join([
+ _("Please report any bugs as issues on github: "),
+ "https://github.com/spesmilo/electrum/issues ",
+ _("Before reporting a bug, upgrade to the most recent version of Electrum (latest release or git HEAD), and include the version number in your report."),
+ _("Try to explain not only what the bug is, but how it occurs.")
+ ])
+ self.show_message(msg, title="Electrum - " + _("Reporting Bugs"))
+
+ def notify_transactions(self):
+ if not self.network or not self.network.is_connected():
+ return
+ self.print_error("Notifying GUI")
+ if len(self.tx_notifications) > 0:
+ # Combine the transactions if there are at least three
+ num_txns = len(self.tx_notifications)
+ if num_txns >= 3:
+ total_amount = 0
+ for tx in self.tx_notifications:
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
+ if v > 0:
+ total_amount += v
+ self.notify(_("{} new transactions received: Total amount received in the new transactions {}")
+ .format(num_txns, self.format_amount_and_units(total_amount)))
+ self.tx_notifications = []
+ else:
+ for tx in self.tx_notifications:
+ if tx:
+ self.tx_notifications.remove(tx)
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
+ if v > 0:
+ self.notify(_("New transaction received: {}").format(self.format_amount_and_units(v)))
+
+ def notify(self, message):
+ if self.tray:
+ try:
+ # this requires Qt 5.9
+ self.tray.showMessage("Electrum", message, QIcon(":icons/electrum_dark_icon"), 20000)
+ except TypeError:
+ self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
+
+
+
+ # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
+ def getOpenFileName(self, title, filter = ""):
+ directory = self.config.get('io_dir', os.path.expanduser('~'))
+ fileName, __ = QFileDialog.getOpenFileName(self, title, directory, filter)
+ if fileName and directory != os.path.dirname(fileName):
+ self.config.set_key('io_dir', os.path.dirname(fileName), True)
+ return fileName
+
+ def getSaveFileName(self, title, filename, filter = ""):
+ directory = self.config.get('io_dir', os.path.expanduser('~'))
+ path = os.path.join( directory, filename )
+ fileName, __ = QFileDialog.getSaveFileName(self, title, path, filter)
+ if fileName and directory != os.path.dirname(fileName):
+ self.config.set_key('io_dir', os.path.dirname(fileName), True)
+ return fileName
+
+ def connect_slots(self, sender):
+ sender.timer_signal.connect(self.timer_actions)
+
+ def timer_actions(self):
+ # Note this runs in the GUI thread
+ if self.need_update.is_set():
+ self.need_update.clear()
+ self.update_wallet()
+ # resolve aliases
+ # FIXME this is a blocking network call that has a timeout of 5 sec
+ self.payto_e.resolve()
+ # update fee
+ if self.require_fee_update:
+ self.do_update_fee()
+ self.require_fee_update = False
+
+ def format_amount(self, x, is_diff=False, whitespaces=False):
+ return format_satoshis(x, self.num_zeros, self.decimal_point, is_diff=is_diff, whitespaces=whitespaces)
+
+ def format_amount_and_units(self, amount):
+ text = self.format_amount(amount) + ' '+ self.base_unit()
+ x = self.fx.format_amount_and_units(amount) if self.fx else None
+ if text and x:
+ text += ' (%s)'%x
+ return text
+
+ def format_fee_rate(self, fee_rate):
+ return format_fee_satoshis(fee_rate/1000, self.num_zeros) + ' sat/byte'
+
+ def get_decimal_point(self):
+ return self.decimal_point
+
+ def base_unit(self):
+ return decimal_point_to_base_unit_name(self.decimal_point)
+
+ def connect_fields(self, window, btc_e, fiat_e, fee_e):
+
+ def edit_changed(edit):
+ if edit.follows:
+ return
+ edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
+ fiat_e.is_last_edited = (edit == fiat_e)
+ amount = edit.get_amount()
+ rate = self.fx.exchange_rate() if self.fx else Decimal('NaN')
+ if rate.is_nan() or amount is None:
+ if edit is fiat_e:
+ btc_e.setText("")
+ if fee_e:
+ fee_e.setText("")
+ else:
+ fiat_e.setText("")
+ else:
+ if edit is fiat_e:
+ btc_e.follows = True
+ btc_e.setAmount(int(amount / Decimal(rate) * COIN))
+ btc_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
+ btc_e.follows = False
+ if fee_e:
+ window.update_fee()
+ else:
+ fiat_e.follows = True
+ fiat_e.setText(self.fx.ccy_amount_str(
+ amount * Decimal(rate) / COIN, False))
+ fiat_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
+ fiat_e.follows = False
+
+ btc_e.follows = False
+ fiat_e.follows = False
+ fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
+ btc_e.textChanged.connect(partial(edit_changed, btc_e))
+ fiat_e.is_last_edited = False
+
+ def update_status(self):
+ if not self.wallet:
+ return
+
+ if self.network is None or not self.network.is_running():
+ text = _("Offline")
+ icon = QIcon(":icons/status_disconnected.png")
+
+ elif self.network.is_connected():
+ server_height = self.network.get_server_height()
+ server_lag = self.network.get_local_height() - server_height
+ # Server height can be 0 after switching to a new server
+ # until we get a headers subscription request response.
+ # Display the synchronizing message in that case.
+ if not self.wallet.up_to_date or server_height == 0:
+ text = _("Synchronizing...")
+ icon = QIcon(":icons/status_waiting.png")
+ elif server_lag > 1:
+ text = _("Server is lagging ({} blocks)").format(server_lag)
+ icon = QIcon(":icons/status_lagging.png")
+ else:
+ c, u, x = self.wallet.get_balance()
+ text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c))
+ if u:
+ text += " [%s unconfirmed]"%(self.format_amount(u, is_diff=True).strip())
+ if x:
+ text += " [%s unmatured]"%(self.format_amount(x, is_diff=True).strip())
+
+ # append fiat balance and price
+ if self.fx.is_enabled():
+ text += self.fx.get_fiat_status_text(c + u + x,
+ self.base_unit(), self.get_decimal_point()) or ''
+ if not self.network.proxy:
+ icon = QIcon(":icons/status_connected.png")
+ else:
+ icon = QIcon(":icons/status_connected_proxy.png")
+ else:
+ if self.network.proxy:
+ text = "{} ({})".format(_("Not connected"), _("proxy enabled"))
+ else:
+ text = _("Not connected")
+ icon = QIcon(":icons/status_disconnected.png")
+
+ self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename()))
+ self.balance_label.setText(text)
+ self.status_button.setIcon( icon )
+
+
+ def update_wallet(self):
+ self.update_status()
+ if self.wallet.up_to_date or not self.network or not self.network.is_connected():
+ self.update_tabs()
+
+ def update_tabs(self):
+ self.history_list.update()
+ self.request_list.update()
+ self.address_list.update()
+ self.utxo_list.update()
+ self.contact_list.update()
+ self.invoice_list.update()
+ self.update_completions()
+
+ def create_history_tab(self):
+ from .history_list import HistoryList
+ self.history_list = l = HistoryList(self)
+ l.searchable_list = l
+ toolbar = l.create_toolbar(self.config)
+ toolbar_shown = self.config.get('show_toolbar_history', False)
+ l.show_toolbar(toolbar_shown)
+ return self.create_list_tab(l, toolbar)
+
+ def show_address(self, addr):
+ from . import address_dialog
+ d = address_dialog.AddressDialog(self, addr)
+ d.exec_()
+
+ def show_transaction(self, tx, tx_desc = None):
+ '''tx_desc is set only for txs created in the Send tab'''
+ show_transaction(tx, self, tx_desc)
+
+ def create_receive_tab(self):
+ # A 4-column grid layout. All the stretch is in the last column.
+ # The exchange rate plugin adds a fiat widget in column 2
+ self.receive_grid = grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnStretch(3, 1)
+
+ self.receive_address_e = ButtonsLineEdit()
+ self.receive_address_e.addCopyButton(self.app)
+ self.receive_address_e.setReadOnly(True)
+ msg = _('Bitcore address where the payment should be received. Note that each payment request uses a different Bitcore address.')
+ self.receive_address_label = HelpLabel(_('Receiving address'), msg)
+ self.receive_address_e.textChanged.connect(self.update_receive_qr)
+ self.receive_address_e.setFocusPolicy(Qt.ClickFocus)
+ grid.addWidget(self.receive_address_label, 0, 0)
+ grid.addWidget(self.receive_address_e, 0, 1, 1, -1)
+
+ self.receive_message_e = QLineEdit()
+ grid.addWidget(QLabel(_('Description')), 1, 0)
+ grid.addWidget(self.receive_message_e, 1, 1, 1, -1)
+ self.receive_message_e.textChanged.connect(self.update_receive_qr)
+
+ self.receive_amount_e = BTCAmountEdit(self.get_decimal_point)
+ grid.addWidget(QLabel(_('Requested amount')), 2, 0)
+ grid.addWidget(self.receive_amount_e, 2, 1)
+ self.receive_amount_e.textChanged.connect(self.update_receive_qr)
+
+ self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
+ if not self.fx or not self.fx.is_enabled():
+ self.fiat_receive_e.setVisible(False)
+ grid.addWidget(self.fiat_receive_e, 2, 2, Qt.AlignLeft)
+ self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
+
+ self.expires_combo = QComboBox()
+ self.expires_combo.addItems([i[0] for i in expiration_values])
+ self.expires_combo.setCurrentIndex(3)
+ self.expires_combo.setFixedWidth(self.receive_amount_e.width())
+ msg = ' '.join([
+ _('Expiration date of your request.'),
+ _('This information is seen by the recipient if you send them a signed payment request.'),
+ _('Expired requests have to be deleted manually from your list, in order to free the corresponding Bitcore addresses.'),
+ _('The bitcore address never expires and will always be part of this electrum wallet.'),
+ ])
+ grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)
+ grid.addWidget(self.expires_combo, 3, 1)
+ self.expires_label = QLineEdit('')
+ self.expires_label.setReadOnly(1)
+ self.expires_label.setFocusPolicy(Qt.NoFocus)
+ self.expires_label.hide()
+ grid.addWidget(self.expires_label, 3, 1)
+
+ self.save_request_button = QPushButton(_('Save'))
+ self.save_request_button.clicked.connect(self.save_payment_request)
+
+ self.new_request_button = QPushButton(_('New'))
+ self.new_request_button.clicked.connect(self.new_payment_request)
+
+ self.receive_qr = QRCodeWidget(fixedSize=200)
+ self.receive_qr.mouseReleaseEvent = lambda x: self.toggle_qr_window()
+ self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
+ self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
+
+ self.receive_buttons = buttons = QHBoxLayout()
+ buttons.addStretch(1)
+ buttons.addWidget(self.save_request_button)
+ buttons.addWidget(self.new_request_button)
+ grid.addLayout(buttons, 4, 1, 1, 2)
+
+ self.receive_requests_label = QLabel(_('Requests'))
+
+ from .request_list import RequestList
+ self.request_list = RequestList(self)
+
+ # layout
+ vbox_g = QVBoxLayout()
+ vbox_g.addLayout(grid)
+ vbox_g.addStretch()
+
+ hbox = QHBoxLayout()
+ hbox.addLayout(vbox_g)
+ hbox.addWidget(self.receive_qr)
+
+ w = QWidget()
+ w.searchable_list = self.request_list
+ vbox = QVBoxLayout(w)
+ vbox.addLayout(hbox)
+ vbox.addStretch(1)
+ vbox.addWidget(self.receive_requests_label)
+ vbox.addWidget(self.request_list)
+ vbox.setStretchFactor(self.request_list, 1000)
+
+ return w
+
+
+ def delete_payment_request(self, addr):
+ self.wallet.remove_payment_request(addr, self.config)
+ self.request_list.update()
+ self.clear_receive_tab()
+
+ def get_request_URI(self, addr):
+ req = self.wallet.receive_requests[addr]
+ message = self.wallet.labels.get(addr, '')
+ amount = req['amount']
+ URI = util.create_URI(addr, amount, message)
+ if req.get('time'):
+ URI += "&time=%d"%req.get('time')
+ if req.get('exp'):
+ URI += "&exp=%d"%req.get('exp')
+ if req.get('name') and req.get('sig'):
+ sig = bfh(req.get('sig'))
+ sig = bitcoin.base_encode(sig, base=58)
+ URI += "&name=" + req['name'] + "&sig="+sig
+ return str(URI)
+
+
+ def sign_payment_request(self, addr):
+ alias = self.config.get('alias')
+ alias_privkey = None
+ if alias and self.alias_info:
+ alias_addr, alias_name, validated = self.alias_info
+ if alias_addr:
+ if self.wallet.is_mine(alias_addr):
+ msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
+ password = None
+ if self.wallet.has_keystore_encryption():
+ password = self.password_dialog(msg)
+ if not password:
+ return
+ try:
+ self.wallet.sign_payment_request(addr, alias, alias_addr, password)
+ except Exception as e:
+ self.show_error(str(e))
+ return
+ else:
+ return
+
+ def save_payment_request(self):
+ addr = str(self.receive_address_e.text())
+ amount = self.receive_amount_e.get_amount()
+ message = self.receive_message_e.text()
+ if not message and not amount:
+ self.show_error(_('No message or amount'))
+ return False
+ i = self.expires_combo.currentIndex()
+ expiration = list(map(lambda x: x[1], expiration_values))[i]
+ req = self.wallet.make_payment_request(addr, amount, message, expiration)
+ try:
+ self.wallet.add_payment_request(req, self.config)
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ self.show_error(_('Error adding payment request') + ':\n' + str(e))
+ else:
+ self.sign_payment_request(addr)
+ self.save_request_button.setEnabled(False)
+ finally:
+ self.request_list.update()
+ self.address_list.update()
+
+ def view_and_paste(self, title, msg, data):
+ dialog = WindowModalDialog(self, title)
+ vbox = QVBoxLayout()
+ label = QLabel(msg)
+ label.setWordWrap(True)
+ vbox.addWidget(label)
+ pr_e = ShowQRTextEdit(text=data)
+ vbox.addWidget(pr_e)
+ vbox.addLayout(Buttons(CopyCloseButton(pr_e.text, self.app, dialog)))
+ dialog.setLayout(vbox)
+ dialog.exec_()
+
+ def export_payment_request(self, addr):
+ r = self.wallet.receive_requests.get(addr)
+ pr = paymentrequest.serialize_request(r).SerializeToString()
+ name = r['id'] + '.bip70'
+ fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
+ if fileName:
+ with open(fileName, "wb+") as f:
+ f.write(util.to_bytes(pr))
+ self.show_message(_("Request saved successfully"))
+ self.saved = True
+
+ def new_payment_request(self):
+ addr = self.wallet.get_unused_address()
+ if addr is None:
+ if not self.wallet.is_deterministic():
+ msg = [
+ _('No more addresses in your wallet.'),
+ _('You are using a non-deterministic wallet, which cannot create new addresses.'),
+ _('If you want to create new addresses, use a deterministic wallet instead.')
+ ]
+ self.show_message(' '.join(msg))
+ return
+ 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?")):
+ return
+ addr = self.wallet.create_new_address(False)
+ self.set_receive_address(addr)
+ self.expires_label.hide()
+ self.expires_combo.show()
+ self.new_request_button.setEnabled(False)
+ self.receive_message_e.setFocus(1)
+
+ def set_receive_address(self, addr):
+ self.receive_address_e.setText(addr)
+ self.receive_message_e.setText('')
+ self.receive_amount_e.setAmount(None)
+
+ def clear_receive_tab(self):
+ addr = self.wallet.get_receiving_address() or ''
+ self.receive_address_e.setText(addr)
+ self.receive_message_e.setText('')
+ self.receive_amount_e.setAmount(None)
+ self.expires_label.hide()
+ self.expires_combo.show()
+
+ def toggle_qr_window(self):
+ from . import qrwindow
+ if not self.qr_window:
+ self.qr_window = qrwindow.QR_Window(self)
+ self.qr_window.setVisible(True)
+ self.qr_window_geometry = self.qr_window.geometry()
+ else:
+ if not self.qr_window.isVisible():
+ self.qr_window.setVisible(True)
+ self.qr_window.setGeometry(self.qr_window_geometry)
+ else:
+ self.qr_window_geometry = self.qr_window.geometry()
+ self.qr_window.setVisible(False)
+ self.update_receive_qr()
+
+ def show_send_tab(self):
+ self.tabs.setCurrentIndex(self.tabs.indexOf(self.send_tab))
+
+ def show_receive_tab(self):
+ self.tabs.setCurrentIndex(self.tabs.indexOf(self.receive_tab))
+
+ def receive_at(self, addr):
+ if not bitcoin.is_address(addr):
+ return
+ self.show_receive_tab()
+ self.receive_address_e.setText(addr)
+ self.new_request_button.setEnabled(True)
+
+ def update_receive_qr(self):
+ addr = str(self.receive_address_e.text())
+ amount = self.receive_amount_e.get_amount()
+ message = self.receive_message_e.text()
+ self.save_request_button.setEnabled((amount is not None) or (message != ""))
+ uri = util.create_URI(addr, amount, message)
+ self.receive_qr.setData(uri)
+ if self.qr_window and self.qr_window.isVisible():
+ self.qr_window.set_content(addr, amount, message, uri)
+
+ def set_feerounding_text(self, num_satoshis_added):
+ self.feerounding_text = (_('Additional {} satoshis are going to be added.')
+ .format(num_satoshis_added))
+
+ def create_send_tab(self):
+ # A 4-column grid layout. All the stretch is in the last column.
+ # The exchange rate plugin adds a fiat widget in column 2
+ self.send_grid = grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnStretch(3, 1)
+
+ from .paytoedit import PayToEdit
+ self.amount_e = BTCAmountEdit(self.get_decimal_point)
+ self.payto_e = PayToEdit(self)
+ msg = _('Recipient of the funds.') + '\n\n'\
+ + _('You may enter a Bitcore 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 Bitcore address)')
+ payto_label = HelpLabel(_('Pay to'), msg)
+ grid.addWidget(payto_label, 1, 0)
+ grid.addWidget(self.payto_e, 1, 1, 1, -1)
+
+ completer = QCompleter()
+ completer.setCaseSensitivity(False)
+ self.payto_e.set_completer(completer)
+ completer.setModel(self.completions)
+
+ msg = _('Description of the transaction (not mandatory).') + '\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.')
+ description_label = HelpLabel(_('Description'), msg)
+ grid.addWidget(description_label, 2, 0)
+ self.message_e = MyLineEdit()
+ grid.addWidget(self.message_e, 2, 1, 1, -1)
+
+ self.from_label = QLabel(_('From'))
+ grid.addWidget(self.from_label, 3, 0)
+ self.from_list = MyTreeWidget(self, self.from_list_menu, ['',''])
+ self.from_list.setHeaderHidden(True)
+ self.from_list.setMaximumHeight(80)
+ grid.addWidget(self.from_list, 3, 1, 1, -1)
+ self.set_pay_from([])
+
+ msg = _('Amount to be sent.') + '\n\n' \
+ + _('The amount will be displayed in red if you do not have enough funds in your wallet.') + ' ' \
+ + _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\n\n' \
+ + _('Keyboard shortcut: type "!" to send all your coins.')
+ amount_label = HelpLabel(_('Amount'), msg)
+ grid.addWidget(amount_label, 4, 0)
+ grid.addWidget(self.amount_e, 4, 1)
+
+ self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
+ if not self.fx or not self.fx.is_enabled():
+ self.fiat_send_e.setVisible(False)
+ grid.addWidget(self.fiat_send_e, 4, 2)
+ self.amount_e.frozen.connect(
+ lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
+
+ self.max_button = EnterButton(_("Max"), self.spend_max)
+ self.max_button.setFixedWidth(140)
+ grid.addWidget(self.max_button, 4, 3)
+ hbox = QHBoxLayout()
+ hbox.addStretch(1)
+ grid.addLayout(hbox, 4, 4)
+
+ msg = _('Bitcore transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\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'\
+ + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
+ self.fee_e_label = HelpLabel(_('Fee'), msg)
+
+ def fee_cb(dyn, pos, fee_rate):
+ if dyn:
+ if self.config.use_mempool_fees():
+ self.config.set_key('depth_level', pos, False)
+ else:
+ self.config.set_key('fee_level', pos, False)
+ else:
+ self.config.set_key('fee_per_kb', fee_rate, False)
+
+ if fee_rate:
+ fee_rate = Decimal(fee_rate)
+ self.feerate_e.setAmount(quantize_feerate(fee_rate / 1000))
+ else:
+ self.feerate_e.setAmount(None)
+ self.fee_e.setModified(False)
+
+ self.fee_slider.activate()
+ self.spend_max() if self.is_max else self.update_fee()
+
+ self.fee_slider = FeeSlider(self, self.config, fee_cb)
+ self.fee_slider.setFixedWidth(140)
+
+ def on_fee_or_feerate(edit_changed, editing_finished):
+ edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e
+ if editing_finished:
+ if edit_changed.get_amount() is None:
+ # This is so that when the user blanks the fee and moves on,
+ # we go back to auto-calculate mode and put a fee back.
+ edit_changed.setModified(False)
+ else:
+ # edit_changed was edited just now, so make sure we will
+ # freeze the correct fee setting (this)
+ edit_other.setModified(False)
+ self.fee_slider.deactivate()
+ self.update_fee()
+
+ class TxSizeLabel(QLabel):
+ def setAmount(self, byte_size):
+ self.setText(('x %s bytes =' % byte_size) if byte_size else '')
+
+ self.size_e = TxSizeLabel()
+ self.size_e.setAlignment(Qt.AlignCenter)
+ self.size_e.setAmount(0)
+ self.size_e.setFixedWidth(140)
+ self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
+
+ self.feerate_e = FeerateEdit(lambda: 0)
+ self.feerate_e.setAmount(self.config.fee_per_byte())
+ self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False))
+ self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True))
+
+ self.fee_e = BTCAmountEdit(self.get_decimal_point)
+ self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False))
+ self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True))
+
+ def feerounding_onclick():
+ text = (self.feerounding_text + '\n\n' +
+ _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.') + ' ' +
+ _('At most 100 satoshis might be lost due to this rounding.') + ' ' +
+ _("You can disable this setting in '{}'.").format(_('Preferences')) + '\n' +
+ _('Also, dust is not kept as change, but added to the fee.'))
+ QMessageBox.information(self, 'Fee rounding', text)
+
+ self.feerounding_icon = QPushButton(QIcon(':icons/info.png'), '')
+ self.feerounding_icon.setFixedWidth(20)
+ self.feerounding_icon.setFlat(True)
+ self.feerounding_icon.clicked.connect(feerounding_onclick)
+ self.feerounding_icon.setVisible(False)
+
+ self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
+
+ vbox_feelabel = QVBoxLayout()
+ vbox_feelabel.addWidget(self.fee_e_label)
+ vbox_feelabel.addStretch(1)
+ grid.addLayout(vbox_feelabel, 5, 0)
+
+ self.fee_adv_controls = QWidget()
+ hbox = QHBoxLayout(self.fee_adv_controls)
+ hbox.setContentsMargins(0, 0, 0, 0)
+ hbox.addWidget(self.feerate_e)
+ hbox.addWidget(self.size_e)
+ hbox.addWidget(self.fee_e)
+ hbox.addWidget(self.feerounding_icon, Qt.AlignLeft)
+ hbox.addStretch(1)
+
+ vbox_feecontrol = QVBoxLayout()
+ vbox_feecontrol.addWidget(self.fee_adv_controls)
+ vbox_feecontrol.addWidget(self.fee_slider)
+
+ grid.addLayout(vbox_feecontrol, 5, 1, 1, -1)
+
+ if not self.config.get('show_fee', False):
+ self.fee_adv_controls.setVisible(False)
+
+ self.preview_button = EnterButton(_("Preview"), self.do_preview)
+ self.preview_button.setToolTip(_('Display the details of your transaction before signing it.'))
+ self.send_button = EnterButton(_("Send"), self.do_send)
+ self.clear_button = EnterButton(_("Clear"), self.do_clear)
+ buttons = QHBoxLayout()
+ buttons.addStretch(1)
+ buttons.addWidget(self.clear_button)
+ buttons.addWidget(self.preview_button)
+ buttons.addWidget(self.send_button)
+ grid.addLayout(buttons, 6, 1, 1, 3)
+
+ self.amount_e.shortcut.connect(self.spend_max)
+ self.payto_e.textChanged.connect(self.update_fee)
+ self.amount_e.textEdited.connect(self.update_fee)
+
+ def reset_max(text):
+ self.is_max = False
+ enable = not bool(text) and not self.amount_e.isReadOnly()
+ self.max_button.setEnabled(enable)
+ self.amount_e.textEdited.connect(reset_max)
+ self.fiat_send_e.textEdited.connect(reset_max)
+
+ def entry_changed():
+ text = ""
+
+ amt_color = ColorScheme.DEFAULT
+ fee_color = ColorScheme.DEFAULT
+ feerate_color = ColorScheme.DEFAULT
+
+ if self.not_enough_funds:
+ amt_color, fee_color = ColorScheme.RED, ColorScheme.RED
+ feerate_color = ColorScheme.RED
+ text = _( "Not enough funds" )
+ c, u, x = self.wallet.get_frozen_balance()
+ if c+u+x:
+ text += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
+
+ # blue color denotes auto-filled values
+ elif self.fee_e.isModified():
+ feerate_color = ColorScheme.BLUE
+ elif self.feerate_e.isModified():
+ fee_color = ColorScheme.BLUE
+ elif self.amount_e.isModified():
+ fee_color = ColorScheme.BLUE
+ feerate_color = ColorScheme.BLUE
+ else:
+ amt_color = ColorScheme.BLUE
+ fee_color = ColorScheme.BLUE
+ feerate_color = ColorScheme.BLUE
+
+ self.statusBar().showMessage(text)
+ self.amount_e.setStyleSheet(amt_color.as_stylesheet())
+ self.fee_e.setStyleSheet(fee_color.as_stylesheet())
+ self.feerate_e.setStyleSheet(feerate_color.as_stylesheet())
+
+ self.amount_e.textChanged.connect(entry_changed)
+ self.fee_e.textChanged.connect(entry_changed)
+ self.feerate_e.textChanged.connect(entry_changed)
+
+ self.invoices_label = QLabel(_('Invoices'))
+ from .invoice_list import InvoiceList
+ self.invoice_list = InvoiceList(self)
+
+ vbox0 = QVBoxLayout()
+ vbox0.addLayout(grid)
+ hbox = QHBoxLayout()
+ hbox.addLayout(vbox0)
+ w = QWidget()
+ vbox = QVBoxLayout(w)
+ vbox.addLayout(hbox)
+ vbox.addStretch(1)
+ vbox.addWidget(self.invoices_label)
+ vbox.addWidget(self.invoice_list)
+ vbox.setStretchFactor(self.invoice_list, 1000)
+ w.searchable_list = self.invoice_list
+ run_hook('create_send_tab', grid)
+ return w
+
+ def spend_max(self):
+ if run_hook('abort_send', self):
+ return
+ self.is_max = True
+ self.do_update_fee()
+
+ def update_fee(self):
+ self.require_fee_update = True
+
+ def get_payto_or_dummy(self):
+ r = self.payto_e.get_recipient()
+ if r:
+ return r
+ return (TYPE_ADDRESS, self.wallet.dummy_address())
+
+ def do_update_fee(self):
+ '''Recalculate the fee. If the fee was manually input, retain it, but
+ still build the TX to see if there are enough funds.
+ '''
+ freeze_fee = self.is_send_fee_frozen()
+ freeze_feerate = self.is_send_feerate_frozen()
+ amount = '!' if self.is_max else self.amount_e.get_amount()
+ if amount is None:
+ if not freeze_fee:
+ self.fee_e.setAmount(None)
+ self.not_enough_funds = False
+ self.statusBar().showMessage('')
+ else:
+ fee_estimator = self.get_send_fee_estimator()
+ outputs = self.payto_e.get_outputs(self.is_max)
+ if not outputs:
+ _type, addr = self.get_payto_or_dummy()
+ outputs = [TxOutput(_type, addr, amount)]
+ is_sweep = bool(self.tx_external_keypairs)
+ make_tx = lambda fee_est: \
+ self.wallet.make_unsigned_transaction(
+ self.get_coins(), outputs, self.config,
+ fixed_fee=fee_est, is_sweep=is_sweep)
+ try:
+ tx = make_tx(fee_estimator)
+ self.not_enough_funds = False
+ except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
+ if not freeze_fee:
+ self.fee_e.setAmount(None)
+ if not freeze_feerate:
+ self.feerate_e.setAmount(None)
+ self.feerounding_icon.setVisible(False)
+
+ if isinstance(e, NotEnoughFunds):
+ self.not_enough_funds = True
+ elif isinstance(e, NoDynamicFeeEstimates):
+ try:
+ tx = make_tx(0)
+ size = tx.estimated_size()
+ self.size_e.setAmount(size)
+ except BaseException:
+ pass
+ return
+ except BaseException:
+ traceback.print_exc(file=sys.stderr)
+ return
+
+ size = tx.estimated_size()
+ self.size_e.setAmount(size)
+
+ fee = tx.get_fee()
+ fee = None if self.not_enough_funds else fee
+
+ # Displayed fee/fee_rate values are set according to user input.
+ # Due to rounding or dropping dust in CoinChooser,
+ # actual fees often differ somewhat.
+ if freeze_feerate or self.fee_slider.is_active():
+ displayed_feerate = self.feerate_e.get_amount()
+ if displayed_feerate is not None:
+ displayed_feerate = quantize_feerate(displayed_feerate)
+ else:
+ # fallback to actual fee
+ displayed_feerate = quantize_feerate(fee / size) if fee is not None else None
+ self.feerate_e.setAmount(displayed_feerate)
+ displayed_fee = round(displayed_feerate * size) if displayed_feerate is not None else None
+ self.fee_e.setAmount(displayed_fee)
+ else:
+ if freeze_fee:
+ displayed_fee = self.fee_e.get_amount()
+ else:
+ # fallback to actual fee if nothing is frozen
+ displayed_fee = fee
+ self.fee_e.setAmount(displayed_fee)
+ displayed_fee = displayed_fee if displayed_fee else 0
+ displayed_feerate = quantize_feerate(displayed_fee / size) if displayed_fee is not None else None
+ self.feerate_e.setAmount(displayed_feerate)
+
+ # show/hide fee rounding icon
+ feerounding = (fee - displayed_fee) if fee else 0
+ self.set_feerounding_text(int(feerounding))
+ self.feerounding_icon.setToolTip(self.feerounding_text)
+ self.feerounding_icon.setVisible(abs(feerounding) >= 1)
+
+ if self.is_max:
+ amount = tx.output_value()
+ __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
+ amount_after_all_fees = amount - x_fee_amount
+ self.amount_e.setAmount(amount_after_all_fees)
+
+ def from_list_delete(self, item):
+ i = self.from_list.indexOfTopLevelItem(item)
+ self.pay_from.pop(i)
+ self.redraw_from_list()
+ self.update_fee()
+
+ def from_list_menu(self, position):
+ item = self.from_list.itemAt(position)
+ menu = QMenu()
+ menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
+ menu.exec_(self.from_list.viewport().mapToGlobal(position))
+
+ def set_pay_from(self, coins):
+ self.pay_from = list(coins)
+ self.redraw_from_list()
+
+ def redraw_from_list(self):
+ self.from_list.clear()
+ self.from_label.setHidden(len(self.pay_from) == 0)
+ self.from_list.setHidden(len(self.pay_from) == 0)
+
+ def format(x):
+ h = x.get('prevout_hash')
+ return h[0:10] + '...' + h[-10:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
+
+ for item in self.pay_from:
+ self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
+
+ def get_contact_payto(self, key):
+ _type, label = self.contacts.get(key)
+ return label + ' <' + key + '>' if _type == 'address' else key
+
+ def update_completions(self):
+ l = [self.get_contact_payto(key) for key in self.contacts.keys()]
+ self.completions.setStringList(l)
+
+ def protected(func):
+ '''Password request wrapper. The password is passed to the function
+ as the 'password' named argument. "None" indicates either an
+ unencrypted wallet, or the user cancelled the password request.
+ An empty input is passed as the empty string.'''
+ def request_password(self, *args, **kwargs):
+ parent = self.top_level_window()
+ password = None
+ while self.wallet.has_keystore_encryption():
+ password = self.password_dialog(parent=parent)
+ if password is None:
+ # User cancelled password input
+ return
+ try:
+ self.wallet.check_password(password)
+ break
+ except Exception as e:
+ self.show_error(str(e), parent=parent)
+ continue
+
+ kwargs['password'] = password
+ return func(self, *args, **kwargs)
+ return request_password
+
+ def is_send_fee_frozen(self):
+ return self.fee_e.isVisible() and self.fee_e.isModified() \
+ and (self.fee_e.text() or self.fee_e.hasFocus())
+
+ def is_send_feerate_frozen(self):
+ return self.feerate_e.isVisible() and self.feerate_e.isModified() \
+ and (self.feerate_e.text() or self.feerate_e.hasFocus())
+
+ def get_send_fee_estimator(self):
+ if self.is_send_fee_frozen():
+ fee_estimator = self.fee_e.get_amount()
+ elif self.is_send_feerate_frozen():
+ amount = self.feerate_e.get_amount() # sat/byte feerate
+ amount = 0 if amount is None else amount * 1000 # sat/kilobyte feerate
+ fee_estimator = partial(
+ simple_config.SimpleConfig.estimate_fee_for_feerate, amount)
+ else:
+ fee_estimator = None
+ return fee_estimator
+
+ def read_send_tab(self):
+ if self.payment_request and self.payment_request.has_expired():
+ self.show_error(_('Payment request has expired'))
+ return
+ label = self.message_e.text()
+
+ if self.payment_request:
+ outputs = self.payment_request.get_outputs()
+ else:
+ errors = self.payto_e.get_errors()
+ if errors:
+ self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
+ return
+ outputs = self.payto_e.get_outputs(self.is_max)
+
+ if self.payto_e.is_alias and self.payto_e.validated is False:
+ alias = self.payto_e.toPlainText()
+ msg = _('WARNING: the alias "{}" could not be validated via an additional '
+ 'security check, DNSSEC, and thus may not be correct.').format(alias) + '\n'
+ msg += _('Do you wish to continue?')
+ if not self.question(msg):
+ return
+
+ if not outputs:
+ self.show_error(_('No outputs'))
+ return
+
+ for o in outputs:
+ if o.address is None:
+ self.show_error(_('Bitcore Address is None'))
+ return
+ if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
+ self.show_error(_('Invalid Bitcore Address'))
+ return
+ if o.value is None:
+ self.show_error(_('Invalid Amount'))
+ return
+
+ fee_estimator = self.get_send_fee_estimator()
+ coins = self.get_coins()
+ return outputs, fee_estimator, label, coins
+
+ def do_preview(self):
+ self.do_send(preview = True)
+
+ def do_send(self, preview = False):
+ if run_hook('abort_send', self):
+ return
+ r = self.read_send_tab()
+ if not r:
+ return
+ outputs, fee_estimator, tx_desc, coins = r
+ try:
+ is_sweep = bool(self.tx_external_keypairs)
+ tx = self.wallet.make_unsigned_transaction(
+ coins, outputs, self.config, fixed_fee=fee_estimator,
+ is_sweep=is_sweep)
+ except NotEnoughFunds:
+ self.show_message(_("Insufficient funds"))
+ return
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ self.show_message(str(e))
+ return
+
+ amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))
+ fee = tx.get_fee()
+
+ use_rbf = self.config.get('use_rbf', False)
+ if use_rbf:
+ tx.set_rbf(True)
+
+ if fee < self.wallet.relayfee() * tx.estimated_size() / 1000:
+ self.show_error('\n'.join([
+ _("This transaction requires a higher fee, or it will not be propagated by your current server"),
+ _("Try to raise your transaction fee, or use a server with a lower relay fee.")
+ ]))
+ return
+
+ if preview:
+ self.show_transaction(tx, tx_desc)
+ return
+
+ if not self.network:
+ self.show_error(_("You can't broadcast a transaction without a live network connection."))
+ return
+
+ # confirmation dialog
+ msg = [
+ _("Amount to be sent") + ": " + self.format_amount_and_units(amount),
+ _("Mining fee") + ": " + self.format_amount_and_units(fee),
+ ]
+
+ x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
+ if x_fee:
+ x_fee_address, x_fee_amount = x_fee
+ msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) )
+
+ confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
+ if fee > confirm_rate * tx.estimated_size() / 1000:
+ msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
+
+ if self.wallet.has_keystore_encryption():
+ msg.append("")
+ msg.append(_("Enter your password to proceed"))
+ password = self.password_dialog('\n'.join(msg))
+ if not password:
+ return
+ else:
+ msg.append(_('Proceed?'))
+ password = None
+ if not self.question('\n'.join(msg)):
+ return
+
+ def sign_done(success):
+ if success:
+ if not tx.is_complete():
+ self.show_transaction(tx)
+ self.do_clear()
+ else:
+ self.broadcast_transaction(tx, tx_desc)
+ self.sign_tx_with_password(tx, sign_done, password)
+
+ @protected
+ def sign_tx(self, tx, callback, password):
+ self.sign_tx_with_password(tx, callback, password)
+
+ def sign_tx_with_password(self, tx, callback, password):
+ '''Sign the transaction in a separate thread. When done, calls
+ the callback with a success code of True or False.
+ '''
+ def on_success(result):
+ callback(True)
+ def on_failure(exc_info):
+ self.on_error(exc_info)
+ callback(False)
+ on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
+ if self.tx_external_keypairs:
+ # can sign directly
+ task = partial(Transaction.sign, tx, self.tx_external_keypairs)
+ else:
+ task = partial(self.wallet.sign_transaction, tx, password)
+ msg = _('Signing transaction...')
+ WaitingDialog(self, msg, task, on_success, on_failure)
+
+ def broadcast_transaction(self, tx, tx_desc):
+
+ def broadcast_thread():
+ # non-GUI thread
+ pr = self.payment_request
+ if pr and pr.has_expired():
+ self.payment_request = None
+ return False, _("Payment request has expired")
+ status, msg = self.network.broadcast_transaction(tx)
+ if pr and status is True:
+ self.invoices.set_paid(pr, tx.txid())
+ self.invoices.save()
+ self.payment_request = None
+ refund_address = self.wallet.get_receiving_addresses()[0]
+ ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
+ if ack_status:
+ msg = ack_msg
+ return status, msg
+
+ # Capture current TL window; override might be removed on return
+ parent = self.top_level_window(lambda win: isinstance(win, MessageBoxMixin))
+
+ def broadcast_done(result):
+ # GUI thread
+ if result:
+ status, msg = result
+ if status:
+ if tx_desc is not None and tx.is_complete():
+ self.wallet.set_label(tx.txid(), tx_desc)
+ parent.show_message(_('Payment sent.') + '\n' + msg)
+ self.invoice_list.update()
+ self.do_clear()
+ else:
+ parent.show_error(msg)
+
+ WaitingDialog(self, _('Broadcasting transaction...'),
+ broadcast_thread, broadcast_done, self.on_error)
+
+ def query_choice(self, msg, choices):
+ # Needed by QtHandler for hardware wallets
+ dialog = WindowModalDialog(self.top_level_window())
+ clayout = ChoicesLayout(msg, choices)
+ vbox = QVBoxLayout(dialog)
+ vbox.addLayout(clayout.layout())
+ vbox.addLayout(Buttons(OkButton(dialog)))
+ if not dialog.exec_():
+ return None
+ return clayout.selected_index()
+
+ def lock_amount(self, b):
+ self.amount_e.setFrozen(b)
+ self.max_button.setEnabled(not b)
+
+ def prepare_for_payment_request(self):
+ self.show_send_tab()
+ self.payto_e.is_pr = True
+ for e in [self.payto_e, self.message_e]:
+ e.setFrozen(True)
+ self.lock_amount(True)
+ self.payto_e.setText(_("please wait..."))
+ return True
+
+ def delete_invoice(self, key):
+ self.invoices.remove(key)
+ self.invoice_list.update()
+
+ def payment_request_ok(self):
+ pr = self.payment_request
+ key = self.invoices.add(pr)
+ status = self.invoices.get_status(key)
+ self.invoice_list.update()
+ if status == PR_PAID:
+ self.show_message("invoice already paid")
+ self.do_clear()
+ self.payment_request = None
+ return
+ self.payto_e.is_pr = True
+ if not pr.has_expired():
+ self.payto_e.setGreen()
+ else:
+ self.payto_e.setExpired()
+ self.payto_e.setText(pr.get_requestor())
+ self.amount_e.setText(format_satoshis_plain(pr.get_amount(), self.decimal_point))
+ self.message_e.setText(pr.get_memo())
+ # signal to set fee
+ self.amount_e.textEdited.emit("")
+
+ def payment_request_error(self):
+ self.show_message(self.payment_request.error)
+ self.payment_request = None
+ self.do_clear()
+
+ def on_pr(self, request):
+ self.payment_request = request
+ if self.payment_request.verify(self.contacts):
+ self.payment_request_ok_signal.emit()
+ else:
+ self.payment_request_error_signal.emit()
+
+ def pay_to_URI(self, URI):
+ if not URI:
+ return
+ try:
+ out = util.parse_URI(URI, self.on_pr)
+ except BaseException as e:
+ self.show_error(_('Invalid bitcore URI:') + '\n' + str(e))
+ return
+ self.show_send_tab()
+ r = out.get('r')
+ sig = out.get('sig')
+ name = out.get('name')
+ if r or (name and sig):
+ self.prepare_for_payment_request()
+ return
+ address = out.get('address')
+ amount = out.get('amount')
+ label = out.get('label')
+ message = out.get('message')
+ # use label as description (not BIP21 compliant)
+ if label and not message:
+ message = label
+ if address:
+ self.payto_e.setText(address)
+ if message:
+ self.message_e.setText(message)
+ if amount:
+ self.amount_e.setAmount(amount)
+ self.amount_e.textEdited.emit("")
+
+
+ def do_clear(self):
+ self.is_max = False
+ self.not_enough_funds = False
+ self.payment_request = None
+ self.payto_e.is_pr = False
+ for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
+ self.fee_e, self.feerate_e]:
+ e.setText('')
+ e.setFrozen(False)
+ self.fee_slider.activate()
+ self.feerate_e.setAmount(self.config.fee_per_byte())
+ self.size_e.setAmount(0)
+ self.feerounding_icon.setVisible(False)
+ self.set_pay_from([])
+ self.tx_external_keypairs = {}
+ self.update_status()
+ run_hook('do_clear', self)
+
+ def set_frozen_state(self, addrs, freeze):
+ self.wallet.set_frozen_state(addrs, freeze)
+ self.address_list.update()
+ self.utxo_list.update()
+ self.update_fee()
+
+ def create_list_tab(self, l, toolbar=None):
+ w = QWidget()
+ w.searchable_list = l
+ vbox = QVBoxLayout()
+ w.setLayout(vbox)
+ vbox.setContentsMargins(0, 0, 0, 0)
+ vbox.setSpacing(0)
+ if toolbar:
+ vbox.addLayout(toolbar)
+ vbox.addWidget(l)
+ return w
+
+ def create_addresses_tab(self):
+ from .address_list import AddressList
+ self.address_list = l = AddressList(self)
+ toolbar = l.create_toolbar(self.config)
+ toolbar_shown = self.config.get('show_toolbar_addresses', False)
+ l.show_toolbar(toolbar_shown)
+ return self.create_list_tab(l, toolbar)
+
+ def create_utxo_tab(self):
+ from .utxo_list import UTXOList
+ self.utxo_list = l = UTXOList(self)
+ return self.create_list_tab(l)
+
+ def create_contacts_tab(self):
+ from .contact_list import ContactList
+ self.contact_list = l = ContactList(self)
+ return self.create_list_tab(l)
+
+ def remove_address(self, addr):
+ if self.question(_("Do you want to remove {} from your wallet?").format(addr)):
+ self.wallet.delete_address(addr)
+ self.need_update.set() # history, addresses, coins
+ self.clear_receive_tab()
+
+ def get_coins(self):
+ if self.pay_from:
+ return self.pay_from
+ else:
+ return self.wallet.get_spendable_coins(None, self.config)
+
+ def spend_coins(self, coins):
+ self.set_pay_from(coins)
+ self.show_send_tab()
+ self.update_fee()
+
+ def paytomany(self):
+ self.show_send_tab()
+ self.payto_e.paytomany()
+ msg = '\n'.join([
+ _('Enter a list of outputs in the \'Pay to\' field.'),
+ _('One output per line.'),
+ _('Format: address, amount'),
+ _('You may load a CSV file using the file icon.')
+ ])
+ self.show_message(msg, title=_('Pay to many'))
+
+ def payto_contacts(self, labels):
+ paytos = [self.get_contact_payto(label) for label in labels]
+ self.show_send_tab()
+ if len(paytos) == 1:
+ self.payto_e.setText(paytos[0])
+ self.amount_e.setFocus()
+ else:
+ text = "\n".join([payto + ", 0" for payto in paytos])
+ self.payto_e.setText(text)
+ self.payto_e.setFocus()
+
+ def set_contact(self, label, address):
+ if not is_address(address):
+ self.show_error(_('Invalid Address'))
+ self.contact_list.update() # Displays original unchanged value
+ return False
+ self.contacts[address] = ('address', label)
+ self.contact_list.update()
+ self.history_list.update()
+ self.update_completions()
+ return True
+
+ def delete_contacts(self, labels):
+ if not self.question(_("Remove {} from your list of contacts?")
+ .format(" + ".join(labels))):
+ return
+ for label in labels:
+ self.contacts.pop(label)
+ self.history_list.update()
+ self.contact_list.update()
+ self.update_completions()
+
+ def show_invoice(self, key):
+ pr = self.invoices.get(key)
+ if pr is None:
+ self.show_error('Cannot find payment request in wallet.')
+ return
+ pr.verify(self.contacts)
+ self.show_pr_details(pr)
+
+ def show_pr_details(self, pr):
+ key = pr.get_id()
+ d = WindowModalDialog(self, _("Invoice"))
+ vbox = QVBoxLayout(d)
+ grid = QGridLayout()
+ grid.addWidget(QLabel(_("Requestor") + ':'), 0, 0)
+ grid.addWidget(QLabel(pr.get_requestor()), 0, 1)
+ grid.addWidget(QLabel(_("Amount") + ':'), 1, 0)
+ outputs_str = '\n'.join(map(lambda x: self.format_amount(x[2])+ self.base_unit() + ' @ ' + x[1], pr.get_outputs()))
+ grid.addWidget(QLabel(outputs_str), 1, 1)
+ expires = pr.get_expiration_date()
+ grid.addWidget(QLabel(_("Memo") + ':'), 2, 0)
+ grid.addWidget(QLabel(pr.get_memo()), 2, 1)
+ grid.addWidget(QLabel(_("Signature") + ':'), 3, 0)
+ grid.addWidget(QLabel(pr.get_verify_status()), 3, 1)
+ if expires:
+ grid.addWidget(QLabel(_("Expires") + ':'), 4, 0)
+ grid.addWidget(QLabel(format_time(expires)), 4, 1)
+ vbox.addLayout(grid)
+ def do_export():
+ fn = self.getSaveFileName(_("Save invoice to file"), "*.bip70")
+ if not fn:
+ return
+ with open(fn, 'wb') as f:
+ data = f.write(pr.raw)
+ self.show_message(_('Invoice saved as' + ' ' + fn))
+ exportButton = EnterButton(_('Save'), do_export)
+ def do_delete():
+ if self.question(_('Delete invoice?')):
+ self.invoices.remove(key)
+ self.history_list.update()
+ self.invoice_list.update()
+ d.close()
+ deleteButton = EnterButton(_('Delete'), do_delete)
+ vbox.addLayout(Buttons(exportButton, deleteButton, CloseButton(d)))
+ d.exec_()
+
+ def do_pay_invoice(self, key):
+ pr = self.invoices.get(key)
+ self.payment_request = pr
+ self.prepare_for_payment_request()
+ pr.error = None # this forces verify() to re-run
+ if pr.verify(self.contacts):
+ self.payment_request_ok()
+ else:
+ self.payment_request_error()
+
+ def create_console_tab(self):
+ from .console import Console
+ self.console = console = Console()
+ return console
+
+ def update_console(self):
+ console = self.console
+ console.history = self.config.get("console-history",[])
+ console.history_index = len(console.history)
+
+ console.updateNamespace({'wallet' : self.wallet,
+ 'network' : self.network,
+ 'plugins' : self.gui_object.plugins,
+ 'window': self})
+ console.updateNamespace({'util' : util, 'bitcore':bitcoin})
+
+ c = commands.Commands(self.config, self.wallet, self.network, lambda: self.console.set_json(True))
+ methods = {}
+ def mkfunc(f, method):
+ return lambda *args: f(method, args, self.password_dialog)
+ for m in dir(c):
+ if m[0]=='_' or m in ['network','wallet']: continue
+ methods[m] = mkfunc(c._run, m)
+
+ console.updateNamespace(methods)
+
+ def create_status_bar(self):
+
+ sb = QStatusBar()
+ sb.setFixedHeight(35)
+ qtVersion = qVersion()
+
+ self.balance_label = QLabel("")
+ self.balance_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
+ self.balance_label.setStyleSheet("""QLabel { padding: 0 }""")
+ sb.addWidget(self.balance_label)
+
+ self.search_box = QLineEdit()
+ self.search_box.textChanged.connect(self.do_search)
+ self.search_box.hide()
+ sb.addPermanentWidget(self.search_box)
+
+ self.lock_icon = QIcon()
+ self.password_button = StatusBarButton(self.lock_icon, _("Password"), self.change_password_dialog )
+ sb.addPermanentWidget(self.password_button)
+
+ sb.addPermanentWidget(StatusBarButton(QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
+ self.seed_button = StatusBarButton(QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
+ sb.addPermanentWidget(self.seed_button)
+ self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self))
+ sb.addPermanentWidget(self.status_button)
+ run_hook('create_status_bar', sb)
+ self.setStatusBar(sb)
+
+ def update_lock_icon(self):
+ icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png")
+ self.password_button.setIcon(icon)
+
+ def update_buttons_on_seed(self):
+ self.seed_button.setVisible(self.wallet.has_seed())
+ self.password_button.setVisible(self.wallet.may_have_password())
+ self.send_button.setVisible(not self.wallet.is_watching_only())
+
+ def change_password_dialog(self):
+ from electrum.storage import STO_EV_XPUB_PW
+ if self.wallet.get_available_storage_encryption_version() == STO_EV_XPUB_PW:
+ from .password_dialog import ChangePasswordDialogForHW
+ d = ChangePasswordDialogForHW(self, self.wallet)
+ ok, encrypt_file = d.run()
+ if not ok:
+ return
+
+ try:
+ hw_dev_pw = self.wallet.keystore.get_password_for_storage_encryption()
+ except UserCancelled:
+ return
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.show_error(str(e))
+ return
+ old_password = hw_dev_pw if self.wallet.has_password() else None
+ new_password = hw_dev_pw if encrypt_file else None
+ else:
+ from .password_dialog import ChangePasswordDialogForSW
+ d = ChangePasswordDialogForSW(self, self.wallet)
+ ok, old_password, new_password, encrypt_file = d.run()
+
+ if not ok:
+ return
+ try:
+ self.wallet.update_password(old_password, new_password, encrypt_file)
+ except InvalidPassword as e:
+ self.show_error(str(e))
+ return
+ except BaseException:
+ traceback.print_exc(file=sys.stdout)
+ self.show_error(_('Failed to update password'))
+ return
+ msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected')
+ self.show_message(msg, title=_("Success"))
+ self.update_lock_icon()
+
+ def toggle_search(self):
+ tab = self.tabs.currentWidget()
+ #if hasattr(tab, 'searchable_list'):
+ # tab.searchable_list.toggle_toolbar()
+ #return
+ self.search_box.setHidden(not self.search_box.isHidden())
+ if not self.search_box.isHidden():
+ self.search_box.setFocus(1)
+ else:
+ self.do_search('')
+
+ def do_search(self, t):
+ tab = self.tabs.currentWidget()
+ if hasattr(tab, 'searchable_list'):
+ tab.searchable_list.filter(t)
+
+ def new_contact_dialog(self):
+ d = WindowModalDialog(self, _("New Contact"))
+ vbox = QVBoxLayout(d)
+ vbox.addWidget(QLabel(_('New Contact') + ':'))
+ grid = QGridLayout()
+ line1 = QLineEdit()
+ line1.setFixedWidth(280)
+ line2 = QLineEdit()
+ line2.setFixedWidth(280)
+ grid.addWidget(QLabel(_("Address")), 1, 0)
+ grid.addWidget(line1, 1, 1)
+ grid.addWidget(QLabel(_("Name")), 2, 0)
+ grid.addWidget(line2, 2, 1)
+ vbox.addLayout(grid)
+ vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
+ if d.exec_():
+ self.set_contact(line2.text(), line1.text())
+
+ def show_master_public_keys(self):
+ dialog = WindowModalDialog(self, _("Wallet Information"))
+ dialog.setMinimumSize(500, 100)
+ mpk_list = self.wallet.get_master_public_keys()
+ vbox = QVBoxLayout()
+ wallet_type = self.wallet.storage.get('wallet_type', '')
+ grid = QGridLayout()
+ basename = os.path.basename(self.wallet.storage.path)
+ grid.addWidget(QLabel(_("Wallet name")+ ':'), 0, 0)
+ grid.addWidget(QLabel(basename), 0, 1)
+ grid.addWidget(QLabel(_("Wallet type")+ ':'), 1, 0)
+ grid.addWidget(QLabel(wallet_type), 1, 1)
+ grid.addWidget(QLabel(_("Script type")+ ':'), 2, 0)
+ grid.addWidget(QLabel(self.wallet.txin_type), 2, 1)
+ vbox.addLayout(grid)
+ if self.wallet.is_deterministic():
+ mpk_text = ShowQRTextEdit()
+ mpk_text.setMaximumHeight(150)
+ mpk_text.addCopyButton(self.app)
+ def show_mpk(index):
+ mpk_text.setText(mpk_list[index])
+ # only show the combobox in case multiple accounts are available
+ if len(mpk_list) > 1:
+ def label(key):
+ if isinstance(self.wallet, Multisig_Wallet):
+ return _("cosigner") + ' ' + str(key+1)
+ return ''
+ labels = [label(i) for i in range(len(mpk_list))]
+ on_click = lambda clayout: show_mpk(clayout.selected_index())
+ labels_clayout = ChoicesLayout(_("Master Public Keys"), labels, on_click)
+ vbox.addLayout(labels_clayout.layout())
+ else:
+ vbox.addWidget(QLabel(_("Master Public Key")))
+ show_mpk(0)
+ vbox.addWidget(mpk_text)
+ vbox.addStretch(1)
+ vbox.addLayout(Buttons(CloseButton(dialog)))
+ dialog.setLayout(vbox)
+ dialog.exec_()
+
+ def remove_wallet(self):
+ if self.question('\n'.join([
+ _('Delete wallet file?'),
+ "%s"%self.wallet.storage.path,
+ _('If your wallet contains funds, make sure you have saved its seed.')])):
+ self._delete_wallet()
+
+ @protected
+ def _delete_wallet(self, password):
+ wallet_path = self.wallet.storage.path
+ basename = os.path.basename(wallet_path)
+ self.gui_object.daemon.stop_wallet(wallet_path)
+ self.close()
+ os.unlink(wallet_path)
+ self.show_error(_("Wallet removed: {}").format(basename))
+
+ @protected
+ def show_seed_dialog(self, password):
+ if not self.wallet.has_seed():
+ self.show_message(_('This wallet has no seed'))
+ return
+ keystore = self.wallet.get_keystore()
+ try:
+ seed = keystore.get_seed(password)
+ passphrase = keystore.get_passphrase(password)
+ except BaseException as e:
+ self.show_error(str(e))
+ return
+ from .seed_dialog import SeedDialog
+ d = SeedDialog(self, seed, passphrase)
+ d.exec_()
+
+ def show_qrcode(self, data, title = _("QR code"), parent=None):
+ if not data:
+ return
+ d = QRDialog(data, parent or self, title)
+ d.exec_()
+
+ @protected
+ def show_private_key(self, address, password):
+ if not address:
+ return
+ try:
+ pk, redeem_script = self.wallet.export_private_key(address, password)
+ except Exception as e:
+ traceback.print_exc(file=sys.stdout)
+ self.show_message(str(e))
+ return
+ xtype = bitcoin.deserialize_privkey(pk)[0]
+ d = WindowModalDialog(self, _("Private key"))
+ d.setMinimumSize(600, 150)
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(_("Address") + ': ' + address))
+ vbox.addWidget(QLabel(_("Script type") + ': ' + xtype))
+ vbox.addWidget(QLabel(_("Private key") + ':'))
+ keys_e = ShowQRTextEdit(text=pk)
+ keys_e.addCopyButton(self.app)
+ vbox.addWidget(keys_e)
+ if redeem_script:
+ vbox.addWidget(QLabel(_("Redeem Script") + ':'))
+ rds_e = ShowQRTextEdit(text=redeem_script)
+ rds_e.addCopyButton(self.app)
+ vbox.addWidget(rds_e)
+ vbox.addLayout(Buttons(CloseButton(d)))
+ d.setLayout(vbox)
+ d.exec_()
+
+ msg_sign = _("Signing with an address actually means signing with the corresponding "
+ "private key, and verifying with the corresponding public key. The "
+ "address you have entered does not have a unique public key, so these "
+ "operations cannot be performed.") + '\n\n' + \
+ _('The operation is undefined. Not just in Electrum, but in general.')
+
+ @protected
+ def do_sign(self, address, message, signature, password):
+ address = address.text().strip()
+ message = message.toPlainText().strip()
+ if not bitcoin.is_address(address):
+ self.show_message(_('Invalid Bitcore address.'))
+ return
+ if self.wallet.is_watching_only():
+ self.show_message(_('This is a watching-only wallet.'))
+ return
+ if not self.wallet.is_mine(address):
+ self.show_message(_('Address not in wallet.'))
+ return
+ txin_type = self.wallet.get_txin_type(address)
+ if txin_type not in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
+ self.show_message(_('Cannot sign messages with this type of address:') + \
+ ' ' + txin_type + '\n\n' + self.msg_sign)
+ return
+ task = partial(self.wallet.sign_message, address, message, password)
+
+ def show_signed_message(sig):
+ try:
+ signature.setText(base64.b64encode(sig).decode('ascii'))
+ except RuntimeError:
+ # (signature) wrapped C/C++ object has been deleted
+ pass
+
+ self.wallet.thread.add(task, on_success=show_signed_message)
+
+ def do_verify(self, address, message, signature):
+ address = address.text().strip()
+ message = message.toPlainText().strip().encode('utf-8')
+ if not bitcoin.is_address(address):
+ self.show_message(_('Invalid Bitcore address.'))
+ return
+ try:
+ # This can throw on invalid base64
+ sig = base64.b64decode(str(signature.toPlainText()))
+ verified = ecc.verify_message_with_address(address, sig, message)
+ except Exception as e:
+ verified = False
+ if verified:
+ self.show_message(_("Signature verified"))
+ else:
+ self.show_error(_("Wrong signature"))
+
+ def sign_verify_message(self, address=''):
+ d = WindowModalDialog(self, _('Sign/verify Message'))
+ d.setMinimumSize(610, 290)
+
+ layout = QGridLayout(d)
+
+ message_e = QTextEdit()
+ layout.addWidget(QLabel(_('Message')), 1, 0)
+ layout.addWidget(message_e, 1, 1)
+ layout.setRowStretch(2,3)
+
+ address_e = QLineEdit()
+ address_e.setText(address)
+ layout.addWidget(QLabel(_('Address')), 2, 0)
+ layout.addWidget(address_e, 2, 1)
+
+ signature_e = QTextEdit()
+ layout.addWidget(QLabel(_('Signature')), 3, 0)
+ layout.addWidget(signature_e, 3, 1)
+ layout.setRowStretch(3,1)
+
+ hbox = QHBoxLayout()
+
+ b = QPushButton(_("Sign"))
+ b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
+ hbox.addWidget(b)
+
+ b = QPushButton(_("Verify"))
+ b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
+ hbox.addWidget(b)
+
+ b = QPushButton(_("Close"))
+ b.clicked.connect(d.accept)
+ hbox.addWidget(b)
+ layout.addLayout(hbox, 4, 1)
+ d.exec_()
+
+ @protected
+ def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
+ if self.wallet.is_watching_only():
+ self.show_message(_('This is a watching-only wallet.'))
+ return
+ cyphertext = encrypted_e.toPlainText()
+ task = partial(self.wallet.decrypt_message, pubkey_e.text(), cyphertext, password)
+
+ def setText(text):
+ try:
+ message_e.setText(text.decode('utf-8'))
+ except RuntimeError:
+ # (message_e) wrapped C/C++ object has been deleted
+ pass
+
+ self.wallet.thread.add(task, on_success=setText)
+
+ def do_encrypt(self, message_e, pubkey_e, encrypted_e):
+ message = message_e.toPlainText()
+ message = message.encode('utf-8')
+ try:
+ public_key = ecc.ECPubkey(bfh(pubkey_e.text()))
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ self.show_warning(_('Invalid Public key'))
+ return
+ encrypted = public_key.encrypt_message(message)
+ encrypted_e.setText(encrypted.decode('ascii'))
+
+ def encrypt_message(self, address=''):
+ d = WindowModalDialog(self, _('Encrypt/decrypt Message'))
+ d.setMinimumSize(610, 490)
+
+ layout = QGridLayout(d)
+
+ message_e = QTextEdit()
+ layout.addWidget(QLabel(_('Message')), 1, 0)
+ layout.addWidget(message_e, 1, 1)
+ layout.setRowStretch(2,3)
+
+ pubkey_e = QLineEdit()
+ if address:
+ pubkey = self.wallet.get_public_key(address)
+ pubkey_e.setText(pubkey)
+ layout.addWidget(QLabel(_('Public key')), 2, 0)
+ layout.addWidget(pubkey_e, 2, 1)
+
+ encrypted_e = QTextEdit()
+ layout.addWidget(QLabel(_('Encrypted')), 3, 0)
+ layout.addWidget(encrypted_e, 3, 1)
+ layout.setRowStretch(3,1)
+
+ hbox = QHBoxLayout()
+ b = QPushButton(_("Encrypt"))
+ b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
+ hbox.addWidget(b)
+
+ b = QPushButton(_("Decrypt"))
+ b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
+ hbox.addWidget(b)
+
+ b = QPushButton(_("Close"))
+ b.clicked.connect(d.accept)
+ hbox.addWidget(b)
+
+ layout.addLayout(hbox, 4, 1)
+ d.exec_()
+
+ def password_dialog(self, msg=None, parent=None):
+ from .password_dialog import PasswordDialog
+ parent = parent or self
+ d = PasswordDialog(parent, msg)
+ return d.run()
+
+ def tx_from_text(self, txt):
+ from electrum.transaction import tx_from_str
+ try:
+ tx = tx_from_str(txt)
+ return Transaction(tx)
+ except BaseException as e:
+ self.show_critical(_("Electrum was unable to parse your transaction") + ":\n" + str(e))
+ return
+
+ def read_tx_from_qrcode(self):
+ from electrum import qrscanner
+ try:
+ data = qrscanner.scan_barcode(self.config.get_video_device())
+ except BaseException as e:
+ self.show_error(str(e))
+ return
+ if not data:
+ return
+ # if the user scanned a bitcore URI
+ if str(data).startswith("bitcore:"):
+ self.pay_to_URI(data)
+ return
+ # else if the user scanned an offline signed tx
+ try:
+ data = bh2u(bitcoin.base_decode(data, length=None, base=43))
+ except BaseException as e:
+ self.show_error((_('Could not decode QR code')+':\n{}').format(e))
+ return
+ tx = self.tx_from_text(data)
+ if not tx:
+ return
+ self.show_transaction(tx)
+
+ def read_tx_from_file(self):
+ fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
+ if not fileName:
+ return
+ try:
+ with open(fileName, "r") as f:
+ file_content = f.read()
+ except (ValueError, IOError, os.error) as reason:
+ self.show_critical(_("Electrum was unable to open your transaction file") + "\n" + str(reason), title=_("Unable to read file or no transaction found"))
+ return
+ return self.tx_from_text(file_content)
+
+ def do_process_from_text(self):
+ text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
+ if not text:
+ return
+ tx = self.tx_from_text(text)
+ if tx:
+ self.show_transaction(tx)
+
+ def do_process_from_file(self):
+ tx = self.read_tx_from_file()
+ if tx:
+ self.show_transaction(tx)
+
+ def do_process_from_txid(self):
+ from electrum import transaction
+ txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
+ if ok and txid:
+ txid = str(txid).strip()
+ try:
+ r = self.network.get_transaction(txid)
+ except BaseException as e:
+ self.show_message(str(e))
+ return
+ tx = transaction.Transaction(r)
+ self.show_transaction(tx)
+
+ @protected
+ def export_privkeys_dialog(self, password):
+ if self.wallet.is_watching_only():
+ self.show_message(_("This is a watching-only wallet"))
+ return
+
+ if isinstance(self.wallet, Multisig_Wallet):
+ self.show_message(_('WARNING: This is a multi-signature wallet.') + '\n' +
+ _('It cannot be "backed up" by simply exporting these private keys.'))
+
+ d = WindowModalDialog(self, _('Private keys'))
+ d.setMinimumSize(980, 300)
+ vbox = QVBoxLayout(d)
+
+ msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),
+ _("Exposing a single private key can compromise your entire wallet!"),
+ _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
+ vbox.addWidget(QLabel(msg))
+
+ e = QTextEdit()
+ e.setReadOnly(True)
+ vbox.addWidget(e)
+
+ defaultname = 'electrum-private-keys.csv'
+ select_msg = _('Select file to export your private keys to')
+ hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
+ vbox.addLayout(hbox)
+
+ b = OkButton(d, _('Export'))
+ b.setEnabled(False)
+ vbox.addLayout(Buttons(CancelButton(d), b))
+
+ private_keys = {}
+ addresses = self.wallet.get_addresses()
+ done = False
+ cancelled = False
+ def privkeys_thread():
+ for addr in addresses:
+ time.sleep(0.1)
+ if done or cancelled:
+ break
+ privkey = self.wallet.export_private_key(addr, password)[0]
+ private_keys[addr] = privkey
+ self.computing_privkeys_signal.emit()
+ if not cancelled:
+ self.computing_privkeys_signal.disconnect()
+ self.show_privkeys_signal.emit()
+
+ def show_privkeys():
+ s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
+ e.setText(s)
+ b.setEnabled(True)
+ self.show_privkeys_signal.disconnect()
+ nonlocal done
+ done = True
+
+ def on_dialog_closed(*args):
+ nonlocal done
+ nonlocal cancelled
+ if not done:
+ cancelled = True
+ self.computing_privkeys_signal.disconnect()
+ self.show_privkeys_signal.disconnect()
+
+ self.computing_privkeys_signal.connect(lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
+ self.show_privkeys_signal.connect(show_privkeys)
+ d.finished.connect(on_dialog_closed)
+ threading.Thread(target=privkeys_thread).start()
+
+ if not d.exec_():
+ done = True
+ return
+
+ filename = filename_e.text()
+ if not filename:
+ return
+
+ try:
+ self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
+ except (IOError, os.error) as reason:
+ txt = "\n".join([
+ _("Electrum was unable to produce a private key-export."),
+ str(reason)
+ ])
+ self.show_critical(txt, title=_("Unable to create csv"))
+
+ except Exception as e:
+ self.show_message(str(e))
+ return
+
+ self.show_message(_("Private keys exported."))
+
+ def do_export_privkeys(self, fileName, pklist, is_csv):
+ with open(fileName, "w+") as f:
+ if is_csv:
+ transaction = csv.writer(f)
+ transaction.writerow(["address", "private_key"])
+ for addr, pk in pklist.items():
+ transaction.writerow(["%34s"%addr,pk])
+ else:
+ import json
+ f.write(json.dumps(pklist, indent = 4))
+
+ def do_import_labels(self):
+ def import_labels(path):
+ def _validate(data):
+ return data # TODO
+
+ def import_labels_assign(data):
+ for key, value in data.items():
+ self.wallet.set_label(key, value)
+ import_meta(path, _validate, import_labels_assign)
+
+ def on_import():
+ self.need_update.set()
+ import_meta_gui(self, _('labels'), import_labels, on_import)
+
+ def do_export_labels(self):
+ def export_labels(filename):
+ export_meta(self.wallet.labels, filename)
+ export_meta_gui(self, _('labels'), export_labels)
+
+ def sweep_key_dialog(self):
+ d = WindowModalDialog(self, title=_('Sweep private keys'))
+ d.setMinimumSize(600, 300)
+
+ vbox = QVBoxLayout(d)
+
+ hbox_top = QHBoxLayout()
+ hbox_top.addWidget(QLabel(_("Enter private keys:")))
+ hbox_top.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
+ vbox.addLayout(hbox_top)
+
+ keys_e = ScanQRTextEdit(allow_multi=True)
+ keys_e.setTabChangesFocus(True)
+ vbox.addWidget(keys_e)
+
+ addresses = self.wallet.get_unused_addresses()
+ if not addresses:
+ try:
+ addresses = self.wallet.get_receiving_addresses()
+ except AttributeError:
+ addresses = self.wallet.get_addresses()
+ h, address_e = address_field(addresses)
+ vbox.addLayout(h)
+
+ vbox.addStretch(1)
+ button = OkButton(d, _('Sweep'))
+ vbox.addLayout(Buttons(CancelButton(d), button))
+ button.setEnabled(False)
+
+ def get_address():
+ addr = str(address_e.text()).strip()
+ if bitcoin.is_address(addr):
+ return addr
+
+ def get_pk():
+ text = str(keys_e.toPlainText())
+ return keystore.get_private_keys(text)
+
+ f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
+ on_address = lambda text: address_e.setStyleSheet((ColorScheme.DEFAULT if get_address() else ColorScheme.RED).as_stylesheet())
+ keys_e.textChanged.connect(f)
+ address_e.textChanged.connect(f)
+ address_e.textChanged.connect(on_address)
+ if not d.exec_():
+ return
+ from electrum.wallet import sweep_preparations
+ try:
+ self.do_clear()
+ coins, keypairs = sweep_preparations(get_pk(), self.network)
+ self.tx_external_keypairs = keypairs
+ self.spend_coins(coins)
+ self.payto_e.setText(get_address())
+ self.spend_max()
+ self.payto_e.setFrozen(True)
+ self.amount_e.setFrozen(True)
+ except BaseException as e:
+ self.show_message(str(e))
+ return
+ self.warn_if_watching_only()
+
+ def _do_import(self, title, header_layout, func):
+ text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
+ if not text:
+ return
+ bad = []
+ good = []
+ for key in str(text).split():
+ try:
+ addr = func(key)
+ good.append(addr)
+ except BaseException as e:
+ bad.append(key)
+ continue
+ if good:
+ self.show_message(_("The following addresses were added") + ':\n' + '\n'.join(good))
+ if bad:
+ self.show_critical(_("The following inputs could not be imported") + ':\n'+ '\n'.join(bad))
+ self.address_list.update()
+ self.history_list.update()
+
+ def import_addresses(self):
+ if not self.wallet.can_import_address():
+ return
+ title, msg = _('Import addresses'), _("Enter addresses")+':'
+ self._do_import(title, msg, self.wallet.import_address)
+
+ @protected
+ def do_import_privkey(self, password):
+ if not self.wallet.can_import_privkey():
+ return
+ title = _('Import private keys')
+ header_layout = QHBoxLayout()
+ header_layout.addWidget(QLabel(_("Enter private keys")+':'))
+ header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
+ self._do_import(title, header_layout, lambda x: self.wallet.import_private_key(x, password))
+
+ def update_fiat(self):
+ b = self.fx and self.fx.is_enabled()
+ self.fiat_send_e.setVisible(b)
+ self.fiat_receive_e.setVisible(b)
+ self.history_list.refresh_headers()
+ self.history_list.update()
+ self.address_list.refresh_headers()
+ self.address_list.update()
+ self.update_status()
+
+ def settings_dialog(self):
+ self.need_restart = False
+ d = WindowModalDialog(self, _('Preferences'))
+ vbox = QVBoxLayout()
+ tabs = QTabWidget()
+ gui_widgets = []
+ fee_widgets = []
+ tx_widgets = []
+ id_widgets = []
+
+ # language
+ lang_help = _('Select which language is used in the GUI (after restart).')
+ lang_label = HelpLabel(_('Language') + ':', lang_help)
+ lang_combo = QComboBox()
+ from electrum.i18n import languages
+ lang_combo.addItems(list(languages.values()))
+ lang_keys = list(languages.keys())
+ lang_cur_setting = self.config.get("language", '')
+ try:
+ index = lang_keys.index(lang_cur_setting)
+ except ValueError: # not in list
+ index = 0
+ lang_combo.setCurrentIndex(index)
+ if not self.config.is_modifiable('language'):
+ for w in [lang_combo, lang_label]: w.setEnabled(False)
+ def on_lang(x):
+ lang_request = list(languages.keys())[lang_combo.currentIndex()]
+ if lang_request != self.config.get('language'):
+ self.config.set_key("language", lang_request, True)
+ self.need_restart = True
+ lang_combo.currentIndexChanged.connect(on_lang)
+ gui_widgets.append((lang_label, lang_combo))
+
+ 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"')
+ nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
+ nz = QSpinBox()
+ nz.setMinimum(0)
+ nz.setMaximum(self.decimal_point)
+ nz.setValue(self.num_zeros)
+ if not self.config.is_modifiable('num_zeros'):
+ for w in [nz, nz_label]: w.setEnabled(False)
+ def on_nz():
+ value = nz.value()
+ if self.num_zeros != value:
+ self.num_zeros = value
+ self.config.set_key('num_zeros', value, True)
+ self.history_list.update()
+ self.address_list.update()
+ nz.valueChanged.connect(on_nz)
+ gui_widgets.append((nz_label, nz))
+
+ msg = '\n'.join([
+ _('Time based: fee rate is based on average confirmation time estimates'),
+ _('Mempool based: fee rate is targeting a depth in the memory pool')
+ ]
+ )
+ fee_type_label = HelpLabel(_('Fee estimation') + ':', msg)
+ fee_type_combo = QComboBox()
+ fee_type_combo.addItems([_('Static'), _('ETA'), _('Mempool')])
+ fee_type_combo.setCurrentIndex((2 if self.config.use_mempool_fees() else 1) if self.config.is_dynfee() else 0)
+ def on_fee_type(x):
+ self.config.set_key('mempool_fees', x==2)
+ self.config.set_key('dynamic_fees', x>0)
+ self.fee_slider.update()
+ fee_type_combo.currentIndexChanged.connect(on_fee_type)
+ fee_widgets.append((fee_type_label, fee_type_combo))
+
+ feebox_cb = QCheckBox(_('Edit fees manually'))
+ feebox_cb.setChecked(self.config.get('show_fee', False))
+ feebox_cb.setToolTip(_("Show fee edit box in send tab."))
+ def on_feebox(x):
+ self.config.set_key('show_fee', x == Qt.Checked)
+ self.fee_adv_controls.setVisible(bool(x))
+ feebox_cb.stateChanged.connect(on_feebox)
+ fee_widgets.append((feebox_cb, None))
+
+ use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
+ use_rbf_cb.setChecked(self.config.get('use_rbf', False))
+ use_rbf_cb.setToolTip(
+ _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
+ _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \
+ _('Note that some merchants do not accept non-final transactions until they are confirmed.'))
+ def on_use_rbf(x):
+ self.config.set_key('use_rbf', x == Qt.Checked)
+ use_rbf_cb.stateChanged.connect(on_use_rbf)
+ fee_widgets.append((use_rbf_cb, None))
+
+ msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
+ + _('The following alias providers are available:') + '\n'\
+ + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
+ + 'For more information, see https://openalias.org'
+ alias_label = HelpLabel(_('OpenAlias') + ':', msg)
+ alias = self.config.get('alias','')
+ alias_e = QLineEdit(alias)
+ def set_alias_color():
+ if not self.config.get('alias'):
+ alias_e.setStyleSheet("")
+ return
+ if self.alias_info:
+ alias_addr, alias_name, validated = self.alias_info
+ alias_e.setStyleSheet((ColorScheme.GREEN if validated else ColorScheme.RED).as_stylesheet(True))
+ else:
+ alias_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
+ def on_alias_edit():
+ alias_e.setStyleSheet("")
+ alias = str(alias_e.text())
+ self.config.set_key('alias', alias, True)
+ if alias:
+ self.fetch_alias()
+ set_alias_color()
+ self.alias_received_signal.connect(set_alias_color)
+ alias_e.editingFinished.connect(on_alias_edit)
+ id_widgets.append((alias_label, alias_e))
+
+ # SSL certificate
+ msg = ' '.join([
+ _('SSL certificate used to sign payment requests.'),
+ _('Use setconfig to set ssl_chain and ssl_privkey.'),
+ ])
+ if self.config.get('ssl_privkey') or self.config.get('ssl_chain'):
+ try:
+ SSL_identity = paymentrequest.check_ssl_config(self.config)
+ SSL_error = None
+ except BaseException as e:
+ SSL_identity = "error"
+ SSL_error = str(e)
+ else:
+ SSL_identity = ""
+ SSL_error = None
+ SSL_id_label = HelpLabel(_('SSL certificate') + ':', msg)
+ SSL_id_e = QLineEdit(SSL_identity)
+ SSL_id_e.setStyleSheet((ColorScheme.RED if SSL_error else ColorScheme.GREEN).as_stylesheet(True) if SSL_identity else '')
+ if SSL_error:
+ SSL_id_e.setToolTip(SSL_error)
+ SSL_id_e.setReadOnly(True)
+ id_widgets.append((SSL_id_label, SSL_id_e))
+
+ units = base_units_list
+ msg = (_('Base unit of your wallet.')
+ + '\n1 BTX = 1000 mBTX. 1 mBTX = 1000 bits. 1 bit = 100 sat.\n'
+ + _('This setting affects the Send tab, and all balance related fields.'))
+ unit_label = HelpLabel(_('Base unit') + ':', msg)
+ unit_combo = QComboBox()
+ unit_combo.addItems(units)
+ unit_combo.setCurrentIndex(units.index(self.base_unit()))
+ def on_unit(x, nz):
+ unit_result = units[unit_combo.currentIndex()]
+ if self.base_unit() == unit_result:
+ return
+ edits = self.amount_e, self.fee_e, self.receive_amount_e
+ amounts = [edit.get_amount() for edit in edits]
+ self.decimal_point = base_unit_name_to_decimal_point(unit_result)
+ self.config.set_key('decimal_point', self.decimal_point, True)
+ nz.setMaximum(self.decimal_point)
+ self.history_list.update()
+ self.request_list.update()
+ self.address_list.update()
+ for edit, amount in zip(edits, amounts):
+ edit.setAmount(amount)
+ self.update_status()
+ unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
+ gui_widgets.append((unit_label, unit_combo))
+
+ block_explorers = sorted(util.block_explorer_info().keys())
+ msg = _('Choose which online block explorer to use for functions that open a web browser')
+ block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
+ block_ex_combo = QComboBox()
+ block_ex_combo.addItems(block_explorers)
+ block_ex_combo.setCurrentIndex(block_ex_combo.findText(util.block_explorer(self.config)))
+ def on_be(x):
+ be_result = block_explorers[block_ex_combo.currentIndex()]
+ self.config.set_key('block_explorer', be_result, True)
+ block_ex_combo.currentIndexChanged.connect(on_be)
+ gui_widgets.append((block_ex_label, block_ex_combo))
+
+ from electrum import qrscanner
+ system_cameras = qrscanner._find_system_cameras()
+ qr_combo = QComboBox()
+ qr_combo.addItem("Default","default")
+ for camera, device in system_cameras.items():
+ qr_combo.addItem(camera, device)
+ #combo.addItem("Manually specify a device", config.get("video_device"))
+ index = qr_combo.findData(self.config.get("video_device"))
+ qr_combo.setCurrentIndex(index)
+ msg = _("Install the zbar package to enable this.")
+ qr_label = HelpLabel(_('Video Device') + ':', msg)
+ qr_combo.setEnabled(qrscanner.libzbar is not None)
+ on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), True)
+ qr_combo.currentIndexChanged.connect(on_video_device)
+ gui_widgets.append((qr_label, qr_combo))
+
+ colortheme_combo = QComboBox()
+ colortheme_combo.addItem(_('Light'), 'default')
+ colortheme_combo.addItem(_('Dark'), 'dark')
+ index = colortheme_combo.findData(self.config.get('qt_gui_color_theme', 'default'))
+ colortheme_combo.setCurrentIndex(index)
+ colortheme_label = QLabel(_('Color theme') + ':')
+ def on_colortheme(x):
+ self.config.set_key('qt_gui_color_theme', colortheme_combo.itemData(x), True)
+ self.need_restart = True
+ colortheme_combo.currentIndexChanged.connect(on_colortheme)
+ gui_widgets.append((colortheme_label, colortheme_combo))
+
+ usechange_cb = QCheckBox(_('Use change addresses'))
+ usechange_cb.setChecked(self.wallet.use_change)
+ if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
+ def on_usechange(x):
+ usechange_result = x == Qt.Checked
+ if self.wallet.use_change != usechange_result:
+ self.wallet.use_change = usechange_result
+ self.wallet.storage.put('use_change', self.wallet.use_change)
+ multiple_cb.setEnabled(self.wallet.use_change)
+ usechange_cb.stateChanged.connect(on_usechange)
+ usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
+ tx_widgets.append((usechange_cb, None))
+
+ def on_multiple(x):
+ multiple = x == Qt.Checked
+ if self.wallet.multiple_change != multiple:
+ self.wallet.multiple_change = multiple
+ self.wallet.storage.put('multiple_change', multiple)
+ multiple_change = self.wallet.multiple_change
+ multiple_cb = QCheckBox(_('Use multiple change addresses'))
+ multiple_cb.setEnabled(self.wallet.use_change)
+ multiple_cb.setToolTip('\n'.join([
+ _('In some cases, use up to 3 change addresses in order to break '
+ 'up large coin amounts and obfuscate the recipient address.'),
+ _('This may result in higher transactions fees.')
+ ]))
+ multiple_cb.setChecked(multiple_change)
+ multiple_cb.stateChanged.connect(on_multiple)
+ tx_widgets.append((multiple_cb, None))
+
+ def fmt_docs(key, klass):
+ lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
+ return '\n'.join([key, "", " ".join(lines)])
+
+ choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
+ if len(choosers) > 1:
+ chooser_name = coinchooser.get_name(self.config)
+ msg = _('Choose coin (UTXO) selection method. The following are available:\n\n')
+ msg += '\n\n'.join(fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
+ chooser_label = HelpLabel(_('Coin selection') + ':', msg)
+ chooser_combo = QComboBox()
+ chooser_combo.addItems(choosers)
+ i = choosers.index(chooser_name) if chooser_name in choosers else 0
+ chooser_combo.setCurrentIndex(i)
+ def on_chooser(x):
+ chooser_name = choosers[chooser_combo.currentIndex()]
+ self.config.set_key('coin_chooser', chooser_name)
+ chooser_combo.currentIndexChanged.connect(on_chooser)
+ tx_widgets.append((chooser_label, chooser_combo))
+
+ def on_unconf(x):
+ self.config.set_key('confirmed_only', bool(x))
+ conf_only = self.config.get('confirmed_only', False)
+ unconf_cb = QCheckBox(_('Spend only confirmed coins'))
+ unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
+ unconf_cb.setChecked(conf_only)
+ unconf_cb.stateChanged.connect(on_unconf)
+ tx_widgets.append((unconf_cb, None))
+
+ def on_outrounding(x):
+ self.config.set_key('coin_chooser_output_rounding', bool(x))
+ enable_outrounding = self.config.get('coin_chooser_output_rounding', False)
+ outrounding_cb = QCheckBox(_('Enable output value rounding'))
+ outrounding_cb.setToolTip(
+ _('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' +
+ _('This might improve your privacy somewhat.') + '\n' +
+ _('If enabled, at most 100 satoshis might be lost due to this, per transaction.'))
+ outrounding_cb.setChecked(enable_outrounding)
+ outrounding_cb.stateChanged.connect(on_outrounding)
+ tx_widgets.append((outrounding_cb, None))
+
+ # Fiat Currency
+ hist_checkbox = QCheckBox()
+ hist_capgains_checkbox = QCheckBox()
+ fiat_address_checkbox = QCheckBox()
+ ccy_combo = QComboBox()
+ ex_combo = QComboBox()
+
+ def update_currencies():
+ if not self.fx: return
+ currencies = sorted(self.fx.get_currencies(self.fx.get_history_config()))
+ ccy_combo.clear()
+ ccy_combo.addItems([_('None')] + currencies)
+ if self.fx.is_enabled():
+ ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))
+
+ def update_history_cb():
+ if not self.fx: return
+ hist_checkbox.setChecked(self.fx.get_history_config())
+ hist_checkbox.setEnabled(self.fx.is_enabled())
+
+ def update_fiat_address_cb():
+ if not self.fx: return
+ fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())
+
+ def update_history_capgains_cb():
+ if not self.fx: return
+ hist_capgains_checkbox.setChecked(self.fx.get_history_capital_gains_config())
+ hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked())
+
+ def update_exchanges():
+ if not self.fx: return
+ b = self.fx.is_enabled()
+ ex_combo.setEnabled(b)
+ if b:
+ h = self.fx.get_history_config()
+ c = self.fx.get_currency()
+ exchanges = self.fx.get_exchanges_by_ccy(c, h)
+ else:
+ exchanges = self.fx.get_exchanges_by_ccy('USD', False)
+ ex_combo.clear()
+ ex_combo.addItems(sorted(exchanges))
+ ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))
+
+ def on_currency(hh):
+ if not self.fx: return
+ b = bool(ccy_combo.currentIndex())
+ ccy = str(ccy_combo.currentText()) if b else None
+ self.fx.set_enabled(b)
+ if b and ccy != self.fx.ccy:
+ self.fx.set_currency(ccy)
+ update_history_cb()
+ update_exchanges()
+ self.update_fiat()
+
+ def on_exchange(idx):
+ exchange = str(ex_combo.currentText())
+ if self.fx and self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name():
+ self.fx.set_exchange(exchange)
+
+ def on_history(checked):
+ if not self.fx: return
+ self.fx.set_history_config(checked)
+ update_exchanges()
+ self.history_list.refresh_headers()
+ if self.fx.is_enabled() and checked:
+ # reset timeout to get historical rates
+ self.fx.timeout = 0
+ update_history_capgains_cb()
+
+ def on_history_capgains(checked):
+ if not self.fx: return
+ self.fx.set_history_capital_gains_config(checked)
+ self.history_list.refresh_headers()
+
+ def on_fiat_address(checked):
+ if not self.fx: return
+ self.fx.set_fiat_address_config(checked)
+ self.address_list.refresh_headers()
+ self.address_list.update()
+
+ update_currencies()
+ update_history_cb()
+ update_history_capgains_cb()
+ update_fiat_address_cb()
+ update_exchanges()
+ ccy_combo.currentIndexChanged.connect(on_currency)
+ hist_checkbox.stateChanged.connect(on_history)
+ hist_capgains_checkbox.stateChanged.connect(on_history_capgains)
+ fiat_address_checkbox.stateChanged.connect(on_fiat_address)
+ ex_combo.currentIndexChanged.connect(on_exchange)
+
+ fiat_widgets = []
+ fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
+ fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
+ fiat_widgets.append((QLabel(_('Show capital gains in history')), hist_capgains_checkbox))
+ fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')), fiat_address_checkbox))
+ fiat_widgets.append((QLabel(_('Source')), ex_combo))
+
+ tabs_info = [
+ (fee_widgets, _('Fees')),
+ (tx_widgets, _('Transactions')),
+ (gui_widgets, _('Appearance')),
+ (fiat_widgets, _('Fiat')),
+ (id_widgets, _('Identity')),
+ ]
+ for widgets, name in tabs_info:
+ tab = QWidget()
+ grid = QGridLayout(tab)
+ grid.setColumnStretch(0,1)
+ for a,b in widgets:
+ i = grid.rowCount()
+ if b:
+ if a:
+ grid.addWidget(a, i, 0)
+ grid.addWidget(b, i, 1)
+ else:
+ grid.addWidget(a, i, 0, 1, 2)
+ tabs.addTab(tab, name)
+
+ vbox.addWidget(tabs)
+ vbox.addStretch(1)
+ vbox.addLayout(Buttons(CloseButton(d)))
+ d.setLayout(vbox)
+
+ # run the dialog
+ d.exec_()
+
+ if self.fx:
+ self.fx.timeout = 0
+
+ self.alias_received_signal.disconnect(set_alias_color)
+
+ run_hook('close_settings_dialog')
+ if self.need_restart:
+ self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
+
+
+ def closeEvent(self, event):
+ # It seems in some rare cases this closeEvent() is called twice
+ if not self.cleaned_up:
+ self.cleaned_up = True
+ self.clean_up()
+ event.accept()
+
+ def clean_up(self):
+ self.wallet.thread.stop()
+ if self.network:
+ self.network.unregister_callback(self.on_network)
+ self.config.set_key("is_maximized", self.isMaximized())
+ if not self.isMaximized():
+ g = self.geometry()
+ self.wallet.storage.put("winpos-qt", [g.left(),g.top(),
+ g.width(),g.height()])
+ self.config.set_key("console-history", self.console.history[-50:],
+ True)
+ if self.qr_window:
+ self.qr_window.close()
+ self.close_wallet()
+ self.gui_object.close_window(self)
+
+ def plugins_dialog(self):
+ self.pluginsdialog = d = WindowModalDialog(self, _('Electrum Plugins'))
+
+ plugins = self.gui_object.plugins
+
+ vbox = QVBoxLayout(d)
+
+ # plugins
+ scroll = QScrollArea()
+ scroll.setEnabled(True)
+ scroll.setWidgetResizable(True)
+ scroll.setMinimumSize(400,250)
+ vbox.addWidget(scroll)
+
+ w = QWidget()
+ scroll.setWidget(w)
+ w.setMinimumHeight(plugins.count() * 35)
+
+ grid = QGridLayout()
+ grid.setColumnStretch(0,1)
+ w.setLayout(grid)
+
+ settings_widgets = {}
+
+ def enable_settings_widget(p, name, i):
+ widget = settings_widgets.get(name)
+ if not widget and p and p.requires_settings():
+ widget = settings_widgets[name] = p.settings_widget(d)
+ grid.addWidget(widget, i, 1)
+ if widget:
+ widget.setEnabled(bool(p and p.is_enabled()))
+
+ def do_toggle(cb, name, i):
+ p = plugins.toggle(name)
+ cb.setChecked(bool(p))
+ enable_settings_widget(p, name, i)
+ run_hook('init_qt', self.gui_object)
+
+ for i, descr in enumerate(plugins.descriptions.values()):
+ full_name = descr['__name__']
+ prefix, _separator, name = full_name.rpartition('.')
+ p = plugins.get(name)
+ if descr.get('registers_keystore'):
+ continue
+ try:
+ cb = QCheckBox(descr['fullname'])
+ plugin_is_loaded = p is not None
+ cb_enabled = (not plugin_is_loaded and plugins.is_available(name, self.wallet)
+ or plugin_is_loaded and p.can_user_disable())
+ cb.setEnabled(cb_enabled)
+ cb.setChecked(plugin_is_loaded and p.is_enabled())
+ grid.addWidget(cb, i, 0)
+ enable_settings_widget(p, name, i)
+ cb.clicked.connect(partial(do_toggle, cb, name, i))
+ msg = descr['description']
+ if descr.get('requires'):
+ msg += '\n\n' + _('Requires') + ':\n' + '\n'.join(map(lambda x: x[1], descr.get('requires')))
+ grid.addWidget(HelpButton(msg), i, 2)
+ except Exception:
+ self.print_msg("error: cannot display plugin", name)
+ traceback.print_exc(file=sys.stdout)
+ grid.setRowStretch(len(plugins.descriptions.values()), 1)
+ vbox.addLayout(Buttons(CloseButton(d)))
+ d.exec_()
+
+ def cpfp(self, parent_tx, new_tx):
+ total_size = parent_tx.estimated_size() + new_tx.estimated_size()
+ d = WindowModalDialog(self, _('Child Pays for Parent'))
+ vbox = QVBoxLayout(d)
+ msg = (
+ "A CPFP is a transaction that sends an unconfirmed output back to "
+ "yourself, with a high fee. The goal is to have miners confirm "
+ "the parent transaction in order to get the fee attached to the "
+ "child transaction.")
+ vbox.addWidget(WWLabel(_(msg)))
+ msg2 = ("The proposed fee is computed using your "
+ "fee/kB settings, applied to the total size of both child and "
+ "parent transactions. After you broadcast a CPFP transaction, "
+ "it is normal to see a new unconfirmed transaction in your history.")
+ vbox.addWidget(WWLabel(_(msg2)))
+ grid = QGridLayout()
+ grid.addWidget(QLabel(_('Total size') + ':'), 0, 0)
+ grid.addWidget(QLabel('%d bytes'% total_size), 0, 1)
+ max_fee = new_tx.output_value()
+ grid.addWidget(QLabel(_('Input amount') + ':'), 1, 0)
+ grid.addWidget(QLabel(self.format_amount(max_fee) + ' ' + self.base_unit()), 1, 1)
+ output_amount = QLabel('')
+ grid.addWidget(QLabel(_('Output amount') + ':'), 2, 0)
+ grid.addWidget(output_amount, 2, 1)
+ fee_e = BTCAmountEdit(self.get_decimal_point)
+ # FIXME with dyn fees, without estimates, there are all kinds of crashes here
+ def f(x):
+ a = max_fee - fee_e.get_amount()
+ output_amount.setText((self.format_amount(a) + ' ' + self.base_unit()) if a else '')
+ fee_e.textChanged.connect(f)
+ fee = self.config.fee_per_kb() * total_size / 1000
+ fee_e.setAmount(fee)
+ grid.addWidget(QLabel(_('Fee' + ':')), 3, 0)
+ grid.addWidget(fee_e, 3, 1)
+ def on_rate(dyn, pos, fee_rate):
+ fee = fee_rate * total_size / 1000
+ fee = min(max_fee, fee)
+ fee_e.setAmount(fee)
+ fee_slider = FeeSlider(self, self.config, on_rate)
+ fee_slider.update()
+ grid.addWidget(fee_slider, 4, 1)
+ vbox.addLayout(grid)
+ vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
+ if not d.exec_():
+ return
+ fee = fee_e.get_amount()
+ if fee > max_fee:
+ self.show_error(_('Max fee exceeded'))
+ return
+ new_tx = self.wallet.cpfp(parent_tx, fee)
+ new_tx.set_rbf(True)
+ self.show_transaction(new_tx)
+
+ def bump_fee_dialog(self, tx):
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
+ if fee is None:
+ self.show_error(_("Can't bump fee: unknown fee for original transaction."))
+ return
+ tx_label = self.wallet.get_label(tx.txid())
+ tx_size = tx.estimated_size()
+ d = WindowModalDialog(self, _('Bump Fee'))
+ vbox = QVBoxLayout(d)
+ vbox.addWidget(QLabel(_('Current fee') + ': %s'% self.format_amount(fee) + ' ' + self.base_unit()))
+ vbox.addWidget(QLabel(_('New fee' + ':')))
+
+ fee_e = BTCAmountEdit(self.get_decimal_point)
+ fee_e.setAmount(fee * 1.5)
+ vbox.addWidget(fee_e)
+
+ def on_rate(dyn, pos, fee_rate):
+ fee = fee_rate * tx_size / 1000
+ fee_e.setAmount(fee)
+ fee_slider = FeeSlider(self, self.config, on_rate)
+ vbox.addWidget(fee_slider)
+ cb = QCheckBox(_('Final'))
+ vbox.addWidget(cb)
+ vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
+ if not d.exec_():
+ return
+ is_final = cb.isChecked()
+ new_fee = fee_e.get_amount()
+ delta = new_fee - fee
+ if delta < 0:
+ self.show_error("fee too low")
+ return
+ try:
+ new_tx = self.wallet.bump_fee(tx, delta)
+ except CannotBumpFee as e:
+ self.show_error(str(e))
+ return
+ if is_final:
+ new_tx.set_rbf(False)
+ self.show_transaction(new_tx, tx_label)
+
+ def save_transaction_into_wallet(self, tx):
+ win = self.top_level_window()
+ try:
+ if not self.wallet.add_transaction(tx.txid(), tx):
+ win.show_error(_("Transaction could not be saved.") + "\n" +
+ _("It conflicts with current history."))
+ return False
+ except AddTransactionException as e:
+ win.show_error(e)
+ return False
+ else:
+ self.wallet.save_transactions(write=True)
+ # need to update at least: history_list, utxo_list, address_list
+ self.need_update.set()
+ msg = (_("Transaction added to wallet history.") + '\n\n' +
+ _("Note: this is an offline transaction, if you want the network "
+ "to see it, you need to broadcast it."))
+ win.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), msg)
+ return True
diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
new file mode 100644
index 000000000..ecc7695b8
--- /dev/null
+++ b/electrum/gui/qt/network_dialog.py
@@ -0,0 +1,519 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import socket
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+import PyQt5.QtCore as QtCore
+
+from electrum.i18n import _
+from electrum import constants
+from electrum.util import print_error
+from electrum.network import serialize_server, deserialize_server
+
+from .util import *
+
+protocol_names = ['TCP', 'SSL']
+protocol_letters = 'ts'
+
+class NetworkDialog(QDialog):
+ def __init__(self, network, config, network_updated_signal_obj):
+ QDialog.__init__(self)
+ self.setWindowTitle(_('Network'))
+ self.setMinimumSize(500, 20)
+ self.nlayout = NetworkChoiceLayout(network, config)
+ self.network_updated_signal_obj = network_updated_signal_obj
+ vbox = QVBoxLayout(self)
+ vbox.addLayout(self.nlayout.layout())
+ vbox.addLayout(Buttons(CloseButton(self)))
+ self.network_updated_signal_obj.network_updated_signal.connect(
+ self.on_update)
+ network.register_callback(self.on_network, ['updated', 'interfaces'])
+
+ def on_network(self, event, *args):
+ self.network_updated_signal_obj.network_updated_signal.emit(event, args)
+
+ def on_update(self):
+ self.nlayout.update()
+
+
+
+class NodesListWidget(QTreeWidget):
+
+ def __init__(self, parent):
+ QTreeWidget.__init__(self)
+ self.parent = parent
+ self.setHeaderLabels([_('Connected node'), _('Height')])
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(self.create_menu)
+
+ def create_menu(self, position):
+ item = self.currentItem()
+ if not item:
+ return
+ is_server = not bool(item.data(0, Qt.UserRole))
+ menu = QMenu()
+ if is_server:
+ server = item.data(1, Qt.UserRole)
+ menu.addAction(_("Use as server"), lambda: self.parent.follow_server(server))
+ else:
+ index = item.data(1, Qt.UserRole)
+ menu.addAction(_("Follow this branch"), lambda: self.parent.follow_branch(index))
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def keyPressEvent(self, event):
+ if event.key() in [ Qt.Key_F2, Qt.Key_Return ]:
+ self.on_activated(self.currentItem(), self.currentColumn())
+ else:
+ QTreeWidget.keyPressEvent(self, event)
+
+ def on_activated(self, item, column):
+ # on 'enter' we show the menu
+ pt = self.visualItemRect(item).bottomLeft()
+ pt.setX(50)
+ self.customContextMenuRequested.emit(pt)
+
+ def update(self, network):
+ self.clear()
+ self.addChild = self.addTopLevelItem
+ chains = network.get_blockchains()
+ n_chains = len(chains)
+ for k, items in chains.items():
+ b = network.blockchains[k]
+ name = b.get_name()
+ if n_chains >1:
+ x = QTreeWidgetItem([name + '@%d'%b.get_forkpoint(), '%d'%b.height()])
+ x.setData(0, Qt.UserRole, 1)
+ x.setData(1, Qt.UserRole, b.forkpoint)
+ else:
+ x = self
+ for i in items:
+ star = ' *' if i == network.interface else ''
+ item = QTreeWidgetItem([i.host + star, '%d'%i.tip])
+ item.setData(0, Qt.UserRole, 0)
+ item.setData(1, Qt.UserRole, i.server)
+ x.addChild(item)
+ if n_chains>1:
+ self.addTopLevelItem(x)
+ x.setExpanded(True)
+
+ h = self.header()
+ h.setStretchLastSection(False)
+ h.setSectionResizeMode(0, QHeaderView.Stretch)
+ h.setSectionResizeMode(1, QHeaderView.ResizeToContents)
+
+
+class ServerListWidget(QTreeWidget):
+
+ def __init__(self, parent):
+ QTreeWidget.__init__(self)
+ self.parent = parent
+ self.setHeaderLabels([_('Host'), _('Port')])
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(self.create_menu)
+
+ def create_menu(self, position):
+ item = self.currentItem()
+ if not item:
+ return
+ menu = QMenu()
+ server = item.data(1, Qt.UserRole)
+ menu.addAction(_("Use as server"), lambda: self.set_server(server))
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def set_server(self, s):
+ host, port, protocol = deserialize_server(s)
+ self.parent.server_host.setText(host)
+ self.parent.server_port.setText(port)
+ self.parent.set_server()
+
+ def keyPressEvent(self, event):
+ if event.key() in [ Qt.Key_F2, Qt.Key_Return ]:
+ self.on_activated(self.currentItem(), self.currentColumn())
+ else:
+ QTreeWidget.keyPressEvent(self, event)
+
+ def on_activated(self, item, column):
+ # on 'enter' we show the menu
+ pt = self.visualItemRect(item).bottomLeft()
+ pt.setX(50)
+ self.customContextMenuRequested.emit(pt)
+
+ def update(self, servers, protocol, use_tor):
+ self.clear()
+ for _host, d in sorted(servers.items()):
+ if _host.endswith('.onion') and not use_tor:
+ continue
+ port = d.get(protocol)
+ if port:
+ x = QTreeWidgetItem([_host, port])
+ server = serialize_server(_host, port, protocol)
+ x.setData(1, Qt.UserRole, server)
+ self.addTopLevelItem(x)
+
+ h = self.header()
+ h.setStretchLastSection(False)
+ h.setSectionResizeMode(0, QHeaderView.Stretch)
+ h.setSectionResizeMode(1, QHeaderView.ResizeToContents)
+
+
+class NetworkChoiceLayout(object):
+
+ def __init__(self, network, config, wizard=False):
+ self.network = network
+ self.config = config
+ self.protocol = None
+ self.tor_proxy = None
+
+ self.tabs = tabs = QTabWidget()
+ server_tab = QWidget()
+ proxy_tab = QWidget()
+ blockchain_tab = QWidget()
+ tabs.addTab(blockchain_tab, _('Overview'))
+ tabs.addTab(server_tab, _('Server'))
+ tabs.addTab(proxy_tab, _('Proxy'))
+
+ # server tab
+ grid = QGridLayout(server_tab)
+ grid.setSpacing(8)
+
+ self.server_host = QLineEdit()
+ self.server_host.setFixedWidth(200)
+ self.server_port = QLineEdit()
+ self.server_port.setFixedWidth(60)
+ self.autoconnect_cb = QCheckBox(_('Select server automatically'))
+ self.autoconnect_cb.setEnabled(self.config.is_modifiable('auto_connect'))
+
+ self.server_host.editingFinished.connect(self.set_server)
+ self.server_port.editingFinished.connect(self.set_server)
+ self.autoconnect_cb.clicked.connect(self.set_server)
+ self.autoconnect_cb.clicked.connect(self.update)
+
+ msg = ' '.join([
+ _("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain."),
+ _("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging.")
+ ])
+ grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3)
+ grid.addWidget(HelpButton(msg), 0, 4)
+
+ grid.addWidget(QLabel(_('Server') + ':'), 1, 0)
+ grid.addWidget(self.server_host, 1, 1, 1, 2)
+ grid.addWidget(self.server_port, 1, 3)
+
+ label = _('Server peers') if network.is_connected() else _('Default Servers')
+ grid.addWidget(QLabel(label), 2, 0, 1, 5)
+ self.servers_list = ServerListWidget(self)
+ grid.addWidget(self.servers_list, 3, 0, 1, 5)
+
+ # Proxy tab
+ grid = QGridLayout(proxy_tab)
+ grid.setSpacing(8)
+
+ # proxy setting
+ self.proxy_cb = QCheckBox(_('Use proxy'))
+ self.proxy_cb.clicked.connect(self.check_disable_proxy)
+ self.proxy_cb.clicked.connect(self.set_proxy)
+
+ self.proxy_mode = QComboBox()
+ self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP'])
+ self.proxy_host = QLineEdit()
+ self.proxy_host.setFixedWidth(200)
+ self.proxy_port = QLineEdit()
+ self.proxy_port.setFixedWidth(60)
+ self.proxy_user = QLineEdit()
+ self.proxy_user.setPlaceholderText(_("Proxy user"))
+ self.proxy_password = QLineEdit()
+ self.proxy_password.setPlaceholderText(_("Password"))
+ self.proxy_password.setEchoMode(QLineEdit.Password)
+ self.proxy_password.setFixedWidth(60)
+
+ self.proxy_mode.currentIndexChanged.connect(self.set_proxy)
+ self.proxy_host.editingFinished.connect(self.set_proxy)
+ self.proxy_port.editingFinished.connect(self.set_proxy)
+ self.proxy_user.editingFinished.connect(self.set_proxy)
+ self.proxy_password.editingFinished.connect(self.set_proxy)
+
+ self.proxy_mode.currentIndexChanged.connect(self.proxy_settings_changed)
+ self.proxy_host.textEdited.connect(self.proxy_settings_changed)
+ self.proxy_port.textEdited.connect(self.proxy_settings_changed)
+ self.proxy_user.textEdited.connect(self.proxy_settings_changed)
+ self.proxy_password.textEdited.connect(self.proxy_settings_changed)
+
+ self.tor_cb = QCheckBox(_("Use Tor Proxy"))
+ self.tor_cb.setIcon(QIcon(":icons/tor_logo.png"))
+ self.tor_cb.hide()
+ self.tor_cb.clicked.connect(self.use_tor_proxy)
+
+ grid.addWidget(self.tor_cb, 1, 0, 1, 3)
+ grid.addWidget(self.proxy_cb, 2, 0, 1, 3)
+ grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 2, 4)
+ grid.addWidget(self.proxy_mode, 4, 1)
+ grid.addWidget(self.proxy_host, 4, 2)
+ grid.addWidget(self.proxy_port, 4, 3)
+ grid.addWidget(self.proxy_user, 5, 2)
+ grid.addWidget(self.proxy_password, 5, 3)
+ grid.setRowStretch(7, 1)
+
+ # Blockchain Tab
+ grid = QGridLayout(blockchain_tab)
+ msg = ' '.join([
+ _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."),
+ _("This blockchain is used to verify the transactions sent by your transaction server.")
+ ])
+ self.status_label = QLabel('')
+ grid.addWidget(QLabel(_('Status') + ':'), 0, 0)
+ grid.addWidget(self.status_label, 0, 1, 1, 3)
+ grid.addWidget(HelpButton(msg), 0, 4)
+
+ self.server_label = QLabel('')
+ msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.")
+ grid.addWidget(QLabel(_('Server') + ':'), 1, 0)
+ grid.addWidget(self.server_label, 1, 1, 1, 3)
+ grid.addWidget(HelpButton(msg), 1, 4)
+
+ self.height_label = QLabel('')
+ msg = _('This is the height of your local copy of the blockchain.')
+ grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0)
+ grid.addWidget(self.height_label, 2, 1)
+ grid.addWidget(HelpButton(msg), 2, 4)
+
+ self.split_label = QLabel('')
+ grid.addWidget(self.split_label, 3, 0, 1, 3)
+
+ self.nodes_list_widget = NodesListWidget(self)
+ grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5)
+
+ vbox = QVBoxLayout()
+ vbox.addWidget(tabs)
+ self.layout_ = vbox
+ # tor detector
+ self.td = td = TorDetector()
+ td.found_proxy.connect(self.suggest_proxy)
+ td.start()
+
+ self.fill_in_proxy_settings()
+ self.update()
+
+ def check_disable_proxy(self, b):
+ if not self.config.is_modifiable('proxy'):
+ b = False
+ for w in [self.proxy_mode, self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password]:
+ w.setEnabled(b)
+
+ def enable_set_server(self):
+ if self.config.is_modifiable('server'):
+ enabled = not self.autoconnect_cb.isChecked()
+ self.server_host.setEnabled(enabled)
+ self.server_port.setEnabled(enabled)
+ self.servers_list.setEnabled(enabled)
+ else:
+ for w in [self.autoconnect_cb, self.server_host, self.server_port, self.servers_list]:
+ w.setEnabled(False)
+
+ def update(self):
+ host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()
+ self.server_host.setText(host)
+ self.server_port.setText(port)
+ self.autoconnect_cb.setChecked(auto_connect)
+
+ interface = self.network.interface
+ host = interface.host if interface else _('None')
+ self.server_label.setText(host)
+
+ self.set_protocol(protocol)
+ self.servers = self.network.get_servers()
+ self.servers_list.update(self.servers, self.protocol, self.tor_cb.isChecked())
+ self.enable_set_server()
+
+ height_str = "%d "%(self.network.get_local_height()) + _('blocks')
+ self.height_label.setText(height_str)
+ n = len(self.network.get_interfaces())
+ status = _("Connected to {0} nodes.").format(n) if n else _("Not connected")
+ self.status_label.setText(status)
+ chains = self.network.get_blockchains()
+ if len(chains)>1:
+ chain = self.network.blockchain()
+ forkpoint = chain.get_forkpoint()
+ name = chain.get_name()
+ msg = _('Chain split detected at block {0}').format(forkpoint) + '\n'
+ msg += (_('You are following branch') if auto_connect else _('Your server is on branch'))+ ' ' + name
+ msg += ' (%d %s)' % (chain.get_branch_size(), _('blocks'))
+ else:
+ msg = ''
+ self.split_label.setText(msg)
+ self.nodes_list_widget.update(self.network)
+
+ def fill_in_proxy_settings(self):
+ host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()
+ if not proxy_config:
+ proxy_config = {"mode": "none", "host": "localhost", "port": "9050"}
+
+ b = proxy_config.get('mode') != "none"
+ self.check_disable_proxy(b)
+ if b:
+ self.proxy_cb.setChecked(True)
+ self.proxy_mode.setCurrentIndex(
+ self.proxy_mode.findText(str(proxy_config.get("mode").upper())))
+
+ self.proxy_host.setText(proxy_config.get("host"))
+ self.proxy_port.setText(proxy_config.get("port"))
+ self.proxy_user.setText(proxy_config.get("user", ""))
+ self.proxy_password.setText(proxy_config.get("password", ""))
+
+ def layout(self):
+ return self.layout_
+
+ def set_protocol(self, protocol):
+ if protocol != self.protocol:
+ self.protocol = protocol
+
+ def change_protocol(self, use_ssl):
+ p = 's' if use_ssl else 't'
+ host = self.server_host.text()
+ pp = self.servers.get(host, constants.net.DEFAULT_PORTS)
+ if p not in pp.keys():
+ p = list(pp.keys())[0]
+ port = pp[p]
+ self.server_host.setText(host)
+ self.server_port.setText(port)
+ self.set_protocol(p)
+ self.set_server()
+
+ def follow_branch(self, index):
+ self.network.follow_chain(index)
+ self.update()
+
+ def follow_server(self, server):
+ self.network.switch_to_interface(server)
+ host, port, protocol, proxy, auto_connect = self.network.get_parameters()
+ host, port, protocol = deserialize_server(server)
+ self.network.set_parameters(host, port, protocol, proxy, auto_connect)
+ self.update()
+
+ def server_changed(self, x):
+ if x:
+ self.change_server(str(x.text(0)), self.protocol)
+
+ def change_server(self, host, protocol):
+ pp = self.servers.get(host, constants.net.DEFAULT_PORTS)
+ if protocol and protocol not in protocol_letters:
+ protocol = None
+ if protocol:
+ port = pp.get(protocol)
+ if port is None:
+ protocol = None
+ if not protocol:
+ if 's' in pp.keys():
+ protocol = 's'
+ port = pp.get(protocol)
+ else:
+ protocol = list(pp.keys())[0]
+ port = pp.get(protocol)
+ self.server_host.setText(host)
+ self.server_port.setText(port)
+
+ def accept(self):
+ pass
+
+ def set_server(self):
+ host, port, protocol, proxy, auto_connect = self.network.get_parameters()
+ host = str(self.server_host.text())
+ port = str(self.server_port.text())
+ auto_connect = self.autoconnect_cb.isChecked()
+ self.network.set_parameters(host, port, protocol, proxy, auto_connect)
+
+ def set_proxy(self):
+ host, port, protocol, proxy, auto_connect = self.network.get_parameters()
+ if self.proxy_cb.isChecked():
+ proxy = { 'mode':str(self.proxy_mode.currentText()).lower(),
+ 'host':str(self.proxy_host.text()),
+ 'port':str(self.proxy_port.text()),
+ 'user':str(self.proxy_user.text()),
+ 'password':str(self.proxy_password.text())}
+ else:
+ proxy = None
+ self.tor_cb.setChecked(False)
+ self.network.set_parameters(host, port, protocol, proxy, auto_connect)
+
+ def suggest_proxy(self, found_proxy):
+ self.tor_proxy = found_proxy
+ self.tor_cb.setText("Use Tor proxy at port " + str(found_proxy[1]))
+ if self.proxy_mode.currentIndex() == self.proxy_mode.findText('SOCKS5') \
+ and self.proxy_host.text() == "127.0.0.1" \
+ and self.proxy_port.text() == str(found_proxy[1]):
+ self.tor_cb.setChecked(True)
+ self.tor_cb.show()
+
+ def use_tor_proxy(self, use_it):
+ if not use_it:
+ self.proxy_cb.setChecked(False)
+ else:
+ socks5_mode_index = self.proxy_mode.findText('SOCKS5')
+ if socks5_mode_index == -1:
+ print_error("[network_dialog] can't find proxy_mode 'SOCKS5'")
+ return
+ self.proxy_mode.setCurrentIndex(socks5_mode_index)
+ self.proxy_host.setText("127.0.0.1")
+ self.proxy_port.setText(str(self.tor_proxy[1]))
+ self.proxy_user.setText("")
+ self.proxy_password.setText("")
+ self.tor_cb.setChecked(True)
+ self.proxy_cb.setChecked(True)
+ self.check_disable_proxy(use_it)
+ self.set_proxy()
+
+ def proxy_settings_changed(self):
+ self.tor_cb.setChecked(False)
+
+
+class TorDetector(QThread):
+ found_proxy = pyqtSignal(object)
+
+ def __init__(self):
+ QThread.__init__(self)
+
+ def run(self):
+ # Probable ports for Tor to listen at
+ ports = [9050, 9150]
+ for p in ports:
+ if TorDetector.is_tor_port(p):
+ self.found_proxy.emit(("127.0.0.1", p))
+ return
+
+ @staticmethod
+ def is_tor_port(port):
+ try:
+ s = (socket._socketobject if hasattr(socket, "_socketobject") else socket.socket)(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(0.1)
+ s.connect(("127.0.0.1", port))
+ # Tor responds uniquely to HTTP-like requests
+ s.send(b"GET\n")
+ if b"Tor is not an HTTP Proxy" in s.recv(1024):
+ return True
+ except socket.error:
+ pass
+ return False
diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py
new file mode 100644
index 000000000..43d2600d9
--- /dev/null
+++ b/electrum/gui/qt/password_dialog.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2013 ecdsa@github
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from electrum.i18n import _
+from .util import *
+import re
+import math
+
+from electrum.plugin import run_hook
+
+def check_password_strength(password):
+
+ '''
+ Check the strength of the password entered by the user and return back the same
+ :param password: password entered by user in New Password
+ :return: password strength Weak or Medium or Strong
+ '''
+ password = password
+ n = math.log(len(set(password)))
+ num = re.search("[0-9]", password) is not None and re.match("^[0-9]*$", password) is None
+ caps = password != password.upper() and password != password.lower()
+ extra = re.match("^[a-zA-Z0-9]*$", password) is None
+ score = len(password)*( n + caps + num + extra)/20
+ password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"}
+ return password_strength[min(3, int(score))]
+
+
+PW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)
+
+
+class PasswordLayout(object):
+
+ titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
+
+ def __init__(self, wallet, msg, kind, OK_button, force_disable_encrypt_cb=False):
+ self.wallet = wallet
+
+ self.pw = QLineEdit()
+ self.pw.setEchoMode(2)
+ self.new_pw = QLineEdit()
+ self.new_pw.setEchoMode(2)
+ self.conf_pw = QLineEdit()
+ self.conf_pw.setEchoMode(2)
+ self.kind = kind
+ self.OK_button = OK_button
+
+ vbox = QVBoxLayout()
+ label = QLabel(msg + "\n")
+ label.setWordWrap(True)
+
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnMinimumWidth(0, 150)
+ grid.setColumnMinimumWidth(1, 100)
+ grid.setColumnStretch(1,1)
+
+ if kind == PW_PASSPHRASE:
+ vbox.addWidget(label)
+ msgs = [_('Passphrase:'), _('Confirm Passphrase:')]
+ else:
+ logo_grid = QGridLayout()
+ logo_grid.setSpacing(8)
+ logo_grid.setColumnMinimumWidth(0, 70)
+ logo_grid.setColumnStretch(1,1)
+
+ logo = QLabel()
+ logo.setAlignment(Qt.AlignCenter)
+
+ logo_grid.addWidget(logo, 0, 0)
+ logo_grid.addWidget(label, 0, 1, 1, 2)
+ vbox.addLayout(logo_grid)
+
+ m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')
+ msgs = [m1, _('Confirm Password:')]
+ if wallet and wallet.has_password():
+ grid.addWidget(QLabel(_('Current Password:')), 0, 0)
+ grid.addWidget(self.pw, 0, 1)
+ lockfile = ":icons/lock.png"
+ else:
+ lockfile = ":icons/unlock.png"
+ logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
+
+ grid.addWidget(QLabel(msgs[0]), 1, 0)
+ grid.addWidget(self.new_pw, 1, 1)
+
+ grid.addWidget(QLabel(msgs[1]), 2, 0)
+ grid.addWidget(self.conf_pw, 2, 1)
+ vbox.addLayout(grid)
+
+ # Password Strength Label
+ if kind != PW_PASSPHRASE:
+ self.pw_strength = QLabel()
+ grid.addWidget(self.pw_strength, 3, 0, 1, 2)
+ self.new_pw.textChanged.connect(self.pw_changed)
+
+ self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
+ self.encrypt_cb.setEnabled(False)
+ grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)
+ self.encrypt_cb.setVisible(kind != PW_PASSPHRASE)
+
+ def enable_OK():
+ ok = self.new_pw.text() == self.conf_pw.text()
+ OK_button.setEnabled(ok)
+ self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text())
+ and not force_disable_encrypt_cb)
+ self.new_pw.textChanged.connect(enable_OK)
+ self.conf_pw.textChanged.connect(enable_OK)
+
+ self.vbox = vbox
+
+ def title(self):
+ return self.titles[self.kind]
+
+ def layout(self):
+ return self.vbox
+
+ def pw_changed(self):
+ password = self.new_pw.text()
+ if password:
+ colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green",
+ "Very Strong":"Green"}
+ strength = check_password_strength(password)
+ label = (_("Password Strength") + ": " + "" + strength + " ")
+ else:
+ label = ""
+ self.pw_strength.setText(label)
+
+ def old_password(self):
+ if self.kind == PW_CHANGE:
+ return self.pw.text() or None
+ return None
+
+ def new_password(self):
+ pw = self.new_pw.text()
+ # Empty passphrases are fine and returned empty.
+ if pw == "" and self.kind != PW_PASSPHRASE:
+ pw = None
+ return pw
+
+
+class PasswordLayoutForHW(object):
+
+ def __init__(self, wallet, msg, kind, OK_button):
+ self.wallet = wallet
+
+ self.kind = kind
+ self.OK_button = OK_button
+
+ vbox = QVBoxLayout()
+ label = QLabel(msg + "\n")
+ label.setWordWrap(True)
+
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.setColumnMinimumWidth(0, 150)
+ grid.setColumnMinimumWidth(1, 100)
+ grid.setColumnStretch(1,1)
+
+ logo_grid = QGridLayout()
+ logo_grid.setSpacing(8)
+ logo_grid.setColumnMinimumWidth(0, 70)
+ logo_grid.setColumnStretch(1,1)
+
+ logo = QLabel()
+ logo.setAlignment(Qt.AlignCenter)
+
+ logo_grid.addWidget(logo, 0, 0)
+ logo_grid.addWidget(label, 0, 1, 1, 2)
+ vbox.addLayout(logo_grid)
+
+ if wallet and wallet.has_storage_encryption():
+ lockfile = ":icons/lock.png"
+ else:
+ lockfile = ":icons/unlock.png"
+ logo.setPixmap(QPixmap(lockfile).scaledToWidth(36, mode=Qt.SmoothTransformation))
+
+ vbox.addLayout(grid)
+
+ self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
+ grid.addWidget(self.encrypt_cb, 1, 0, 1, 2)
+
+ self.vbox = vbox
+
+ def title(self):
+ return _("Toggle Encryption")
+
+ def layout(self):
+ return self.vbox
+
+
+class ChangePasswordDialogBase(WindowModalDialog):
+
+ def __init__(self, parent, wallet):
+ WindowModalDialog.__init__(self, parent)
+ is_encrypted = wallet.has_storage_encryption()
+ OK_button = OkButton(self)
+
+ self.create_password_layout(wallet, is_encrypted, OK_button)
+
+ self.setWindowTitle(self.playout.title())
+ vbox = QVBoxLayout(self)
+ vbox.addLayout(self.playout.layout())
+ vbox.addStretch(1)
+ vbox.addLayout(Buttons(CancelButton(self), OK_button))
+ self.playout.encrypt_cb.setChecked(is_encrypted)
+
+ def create_password_layout(self, wallet, is_encrypted, OK_button):
+ raise NotImplementedError()
+
+
+class ChangePasswordDialogForSW(ChangePasswordDialogBase):
+
+ def __init__(self, parent, wallet):
+ ChangePasswordDialogBase.__init__(self, parent, wallet)
+ if not wallet.has_password():
+ self.playout.encrypt_cb.setChecked(True)
+
+ def create_password_layout(self, wallet, is_encrypted, OK_button):
+ if not wallet.has_password():
+ msg = _('Your wallet is not protected.')
+ msg += ' ' + _('Use this dialog to add a password to your wallet.')
+ else:
+ if not is_encrypted:
+ msg = _('Your bitcores are password protected. However, your wallet file is not encrypted.')
+ else:
+ msg = _('Your wallet is password protected and encrypted.')
+ msg += ' ' + _('Use this dialog to change your password.')
+ self.playout = PasswordLayout(
+ wallet, msg, PW_CHANGE, OK_button,
+ force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
+
+ def run(self):
+ if not self.exec_():
+ return False, None, None, None
+ return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
+
+
+class ChangePasswordDialogForHW(ChangePasswordDialogBase):
+
+ def __init__(self, parent, wallet):
+ ChangePasswordDialogBase.__init__(self, parent, wallet)
+
+ def create_password_layout(self, wallet, is_encrypted, OK_button):
+ if not is_encrypted:
+ msg = _('Your wallet file is NOT encrypted.')
+ else:
+ msg = _('Your wallet file is encrypted.')
+ msg += '\n' + _('Note: If you enable this setting, you will need your hardware device to open your wallet.')
+ msg += '\n' + _('Use this dialog to toggle encryption.')
+ self.playout = PasswordLayoutForHW(wallet, msg, PW_CHANGE, OK_button)
+
+ def run(self):
+ if not self.exec_():
+ return False, None
+ return True, self.playout.encrypt_cb.isChecked()
+
+
+class PasswordDialog(WindowModalDialog):
+
+ def __init__(self, parent=None, msg=None):
+ msg = msg or _('Please enter your password')
+ WindowModalDialog.__init__(self, parent, _("Enter Password"))
+ self.pw = pw = QLineEdit()
+ pw.setEchoMode(2)
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(msg))
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.addWidget(QLabel(_('Password')), 1, 0)
+ grid.addWidget(pw, 1, 1)
+ vbox.addLayout(grid)
+ vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
+ self.setLayout(vbox)
+ run_hook('password_dialog', pw, grid, 1)
+
+ def run(self):
+ if not self.exec_():
+ return
+ return self.pw.text()
diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py
new file mode 100644
index 000000000..925507b83
--- /dev/null
+++ b/electrum/gui/qt/paytoedit.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from PyQt5.QtGui import *
+import re
+from decimal import Decimal
+
+from electrum import bitcoin
+from electrum.util import bfh
+from electrum.transaction import TxOutput
+
+from .qrtextedit import ScanQRTextEdit
+from .completion_text_edit import CompletionTextEdit
+from . import util
+
+RE_ALIAS = '(.*?)\s*\<([0-9A-Za-z]{1,})\>'
+
+frozen_style = "QWidget { background-color:none; border:none;}"
+normal_style = "QPlainTextEdit { }"
+
+class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
+
+ def __init__(self, win):
+ CompletionTextEdit.__init__(self)
+ ScanQRTextEdit.__init__(self)
+ self.win = win
+ self.amount_edit = win.amount_e
+ self.document().contentsChanged.connect(self.update_size)
+ self.heightMin = 0
+ self.heightMax = 150
+ self.c = None
+ self.textChanged.connect(self.check_text)
+ self.outputs = []
+ self.errors = []
+ self.is_pr = False
+ self.is_alias = False
+ self.scan_f = win.pay_to_URI
+ self.update_size()
+ self.payto_address = None
+
+ self.previous_payto = ''
+
+ def setFrozen(self, b):
+ self.setReadOnly(b)
+ self.setStyleSheet(frozen_style if b else normal_style)
+ for button in self.buttons:
+ button.setHidden(b)
+
+ def setGreen(self):
+ self.setStyleSheet(util.ColorScheme.GREEN.as_stylesheet(True))
+
+ def setExpired(self):
+ self.setStyleSheet(util.ColorScheme.RED.as_stylesheet(True))
+
+ def parse_address_and_amount(self, line):
+ x, y = line.split(',')
+ out_type, out = self.parse_output(x)
+ amount = self.parse_amount(y)
+ return TxOutput(out_type, out, amount)
+
+ def parse_output(self, x):
+ try:
+ address = self.parse_address(x)
+ return bitcoin.TYPE_ADDRESS, address
+ except:
+ script = self.parse_script(x)
+ return bitcoin.TYPE_SCRIPT, script
+
+ def parse_script(self, x):
+ from electrum.transaction import opcodes, push_script
+ script = ''
+ for word in x.split():
+ if word[0:3] == 'OP_':
+ assert word in opcodes.lookup
+ opcode_int = opcodes.lookup[word]
+ assert opcode_int < 256 # opcode is single-byte
+ script += bitcoin.int_to_hex(opcode_int)
+ else:
+ bfh(word) # to test it is hex data
+ script += push_script(word)
+ return script
+
+ def parse_amount(self, x):
+ if x.strip() == '!':
+ return '!'
+ p = pow(10, self.amount_edit.decimal_point())
+ return int(p * Decimal(x.strip()))
+
+ def parse_address(self, line):
+ r = line.strip()
+ m = re.match('^'+RE_ALIAS+'$', r)
+ address = str(m.group(2) if m else r)
+ assert bitcoin.is_address(address)
+ return address
+
+ def check_text(self):
+ self.errors = []
+ if self.is_pr:
+ return
+ # filter out empty lines
+ lines = [i for i in self.lines() if i]
+ outputs = []
+ total = 0
+ self.payto_address = None
+ if len(lines) == 1:
+ data = lines[0]
+ if data.startswith("bitcore:"):
+ self.scan_f(data)
+ return
+ try:
+ self.payto_address = self.parse_output(data)
+ except:
+ pass
+ if self.payto_address:
+ self.win.lock_amount(False)
+ return
+
+ is_max = False
+ for i, line in enumerate(lines):
+ try:
+ output = self.parse_address_and_amount(line)
+ except:
+ self.errors.append((i, line.strip()))
+ continue
+
+ outputs.append(output)
+ if output.value == '!':
+ is_max = True
+ else:
+ total += output.value
+
+ self.win.is_max = is_max
+ self.outputs = outputs
+ self.payto_address = None
+
+ if self.win.is_max:
+ self.win.do_update_fee()
+ else:
+ self.amount_edit.setAmount(total if outputs else None)
+ self.win.lock_amount(total or len(lines)>1)
+
+ def get_errors(self):
+ return self.errors
+
+ def get_recipient(self):
+ return self.payto_address
+
+ def get_outputs(self, is_max):
+ if self.payto_address:
+ if is_max:
+ amount = '!'
+ else:
+ amount = self.amount_edit.get_amount()
+
+ _type, addr = self.payto_address
+ self.outputs = [TxOutput(_type, addr, amount)]
+
+ return self.outputs[:]
+
+ def lines(self):
+ return self.toPlainText().split('\n')
+
+ def is_multiline(self):
+ return len(self.lines()) > 1
+
+ def paytomany(self):
+ self.setText("\n\n\n")
+ self.update_size()
+
+ def update_size(self):
+ lineHeight = QFontMetrics(self.document().defaultFont()).height()
+ docHeight = self.document().size().height()
+ h = docHeight * lineHeight + 11
+ if self.heightMin <= h <= self.heightMax:
+ self.setMinimumHeight(h)
+ self.setMaximumHeight(h)
+ self.verticalScrollBar().hide()
+
+ def qr_input(self):
+ data = super(PayToEdit,self).qr_input()
+ if data.startswith("bitcore:"):
+ self.scan_f(data)
+ # TODO: update fee
+
+ def resolve(self):
+ self.is_alias = False
+ if self.hasFocus():
+ return
+ if self.is_multiline(): # only supports single line entries atm
+ return
+ if self.is_pr:
+ return
+ key = str(self.toPlainText())
+ if key == self.previous_payto:
+ return
+ self.previous_payto = key
+ if not (('.' in key) and (not '<' in key) and (not ' ' in key)):
+ return
+ parts = key.split(sep=',') # assuming single line
+ if parts and len(parts) > 0 and bitcoin.is_address(parts[0]):
+ return
+ try:
+ data = self.win.contacts.resolve(key)
+ except:
+ return
+ if not data:
+ return
+ self.is_alias = True
+
+ address = data.get('address')
+ name = data.get('name')
+ new_url = key + ' <' + address + '>'
+ self.setText(new_url)
+ self.previous_payto = new_url
+
+ #if self.win.config.get('openalias_autoadd') == 'checked':
+ self.win.contacts[key] = ('openalias', name)
+ self.win.contact_list.on_update()
+
+ self.setFrozen(True)
+ if data.get('type') == 'openalias':
+ self.validated = data.get('validated')
+ if self.validated:
+ self.setGreen()
+ else:
+ self.setExpired()
+ else:
+ self.validated = None
diff --git a/electrum/gui/qt/qrcodewidget.py b/electrum/gui/qt/qrcodewidget.py
new file mode 100644
index 000000000..dc444d6a5
--- /dev/null
+++ b/electrum/gui/qt/qrcodewidget.py
@@ -0,0 +1,133 @@
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+import PyQt5.QtGui as QtGui
+from PyQt5.QtWidgets import (
+ QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget)
+
+import os
+import qrcode
+
+import electrum
+from electrum.i18n import _
+from .util import WindowModalDialog
+
+
+class QRCodeWidget(QWidget):
+
+ def __init__(self, data = None, fixedSize=False):
+ QWidget.__init__(self)
+ self.data = None
+ self.qr = None
+ self.fixedSize=fixedSize
+ if fixedSize:
+ self.setFixedSize(fixedSize, fixedSize)
+ self.setData(data)
+
+
+ def setData(self, data):
+ if self.data != data:
+ self.data = data
+ if self.data:
+ self.qr = qrcode.QRCode()
+ self.qr.add_data(self.data)
+ if not self.fixedSize:
+ k = len(self.qr.get_matrix())
+ self.setMinimumSize(k*5,k*5)
+ else:
+ self.qr = None
+
+ self.update()
+
+
+ def paintEvent(self, e):
+ if not self.data:
+ return
+
+ black = QColor(0, 0, 0, 255)
+ white = QColor(255, 255, 255, 255)
+
+ if not self.qr:
+ qp = QtGui.QPainter()
+ qp.begin(self)
+ qp.setBrush(white)
+ qp.setPen(white)
+ r = qp.viewport()
+ qp.drawRect(0, 0, r.width(), r.height())
+ qp.end()
+ return
+
+ matrix = self.qr.get_matrix()
+ k = len(matrix)
+ qp = QtGui.QPainter()
+ qp.begin(self)
+ r = qp.viewport()
+
+ margin = 10
+ framesize = min(r.width(), r.height())
+ boxsize = int( (framesize - 2*margin)/k )
+ size = k*boxsize
+ left = (r.width() - size)/2
+ top = (r.height() - size)/2
+
+ # Make a white margin around the QR in case of dark theme use
+ qp.setBrush(white)
+ qp.setPen(white)
+ qp.drawRect(left-margin, top-margin, size+(margin*2), size+(margin*2))
+ qp.setBrush(black)
+ qp.setPen(black)
+
+ for r in range(k):
+ for c in range(k):
+ if matrix[r][c]:
+ qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)
+ qp.end()
+
+
+
+class QRDialog(WindowModalDialog):
+
+ def __init__(self, data, parent=None, title = "", show_text=False):
+ WindowModalDialog.__init__(self, parent, title)
+
+ vbox = QVBoxLayout()
+ qrw = QRCodeWidget(data)
+ qscreen = QApplication.primaryScreen()
+ vbox.addWidget(qrw, 1)
+ if show_text:
+ text = QTextEdit()
+ text.setText(data)
+ text.setReadOnly(True)
+ vbox.addWidget(text)
+ hbox = QHBoxLayout()
+ hbox.addStretch(1)
+
+ config = electrum.get_config()
+ if config:
+ filename = os.path.join(config.path, "qrcode.png")
+
+ def print_qr():
+ p = qscreen.grabWindow(qrw.winId())
+ p.save(filename, 'png')
+ self.show_message(_("QR code saved to file") + " " + filename)
+
+ def copy_to_clipboard():
+ p = qscreen.grabWindow(qrw.winId())
+ QApplication.clipboard().setPixmap(p)
+ self.show_message(_("QR code copied to clipboard"))
+
+ b = QPushButton(_("Copy"))
+ hbox.addWidget(b)
+ b.clicked.connect(copy_to_clipboard)
+
+ b = QPushButton(_("Save"))
+ hbox.addWidget(b)
+ b.clicked.connect(print_qr)
+
+ b = QPushButton(_("Close"))
+ hbox.addWidget(b)
+ b.clicked.connect(self.accept)
+ b.setDefault(True)
+
+ vbox.addLayout(hbox)
+ self.setLayout(vbox)
diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py
new file mode 100644
index 000000000..6f3d1e057
--- /dev/null
+++ b/electrum/gui/qt/qrtextedit.py
@@ -0,0 +1,76 @@
+
+from electrum.i18n import _
+from electrum.plugin import run_hook
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import QFileDialog
+
+from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme
+
+
+class ShowQRTextEdit(ButtonsTextEdit):
+
+ def __init__(self, text=None):
+ ButtonsTextEdit.__init__(self, text)
+ self.setReadOnly(1)
+ self.addButton(":icons/qrcode.png", self.qr_show, _("Show as QR code"))
+
+ run_hook('show_text_edit', self)
+
+ def qr_show(self):
+ from .qrcodewidget import QRDialog
+ try:
+ s = str(self.toPlainText())
+ except:
+ s = self.toPlainText()
+ QRDialog(s).exec_()
+
+ def contextMenuEvent(self, e):
+ m = self.createStandardContextMenu()
+ m.addAction(_("Show as QR code"), self.qr_show)
+ m.exec_(e.globalPos())
+
+
+class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
+
+ def __init__(self, text="", allow_multi=False):
+ ButtonsTextEdit.__init__(self, text)
+ self.allow_multi = allow_multi
+ self.setReadOnly(0)
+ self.addButton(":icons/file.png", self.file_input, _("Read file"))
+ icon = ":icons/qrcode_white.png" if ColorScheme.dark_scheme else ":icons/qrcode.png"
+ self.addButton(icon, self.qr_input, _("Read QR code"))
+ run_hook('scan_text_edit', self)
+
+ def file_input(self):
+ fileName, __ = QFileDialog.getOpenFileName(self, 'select file')
+ if not fileName:
+ return
+ try:
+ with open(fileName, "r") as f:
+ data = f.read()
+ except BaseException as e:
+ self.show_error(_('Error opening file') + ':\n' + str(e))
+ else:
+ self.setText(data)
+
+ def qr_input(self):
+ from electrum import qrscanner, get_config
+ try:
+ data = qrscanner.scan_barcode(get_config().get_video_device())
+ except BaseException as e:
+ self.show_error(str(e))
+ data = ''
+ if not data:
+ data = ''
+ if self.allow_multi:
+ new_text = self.text() + data + '\n'
+ else:
+ new_text = data
+ self.setText(new_text)
+ return data
+
+ def contextMenuEvent(self, e):
+ m = self.createStandardContextMenu()
+ m.addAction(_("Read QR code"), self.qr_input)
+ m.exec_(e.globalPos())
diff --git a/electrum/gui/qt/qrwindow.py b/electrum/gui/qt/qrwindow.py
new file mode 100644
index 000000000..9abcc26f9
--- /dev/null
+++ b/electrum/gui/qt/qrwindow.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import platform
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget
+
+from .qrcodewidget import QRCodeWidget
+from electrum.i18n import _
+
+if platform.system() == 'Windows':
+ MONOSPACE_FONT = 'Lucida Console'
+elif platform.system() == 'Darwin':
+ MONOSPACE_FONT = 'Monaco'
+else:
+ MONOSPACE_FONT = 'monospace'
+
+column_index = 4
+
+class QR_Window(QWidget):
+
+ def __init__(self, win):
+ QWidget.__init__(self)
+ self.win = win
+ self.setWindowTitle('Electrum - '+_('Payment Request'))
+ self.setMinimumSize(800, 250)
+ self.address = ''
+ self.label = ''
+ self.amount = 0
+ self.setFocusPolicy(Qt.NoFocus)
+
+ main_box = QHBoxLayout()
+
+ self.qrw = QRCodeWidget()
+ main_box.addWidget(self.qrw, 1)
+
+ vbox = QVBoxLayout()
+ main_box.addLayout(vbox)
+
+ self.address_label = QLabel("")
+ #self.address_label.setFont(QFont(MONOSPACE_FONT))
+ vbox.addWidget(self.address_label)
+
+ self.label_label = QLabel("")
+ vbox.addWidget(self.label_label)
+
+ self.amount_label = QLabel("")
+ vbox.addWidget(self.amount_label)
+
+ vbox.addStretch(1)
+ self.setLayout(main_box)
+
+
+ def set_content(self, address, amount, message, url):
+ address_text = "%s " % address if address else ""
+ self.address_label.setText(address_text)
+ if amount:
+ amount = self.win.format_amount(amount)
+ amount_text = "%s %s " % (amount, self.win.base_unit())
+ else:
+ amount_text = ''
+ self.amount_label.setText(amount_text)
+ label_text = "%s " % message if message else ""
+ self.label_label.setText(label_text)
+ self.qrw.setData(url)
diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
new file mode 100644
index 000000000..7b55c5d8e
--- /dev/null
+++ b/electrum/gui/qt/request_list.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.i18n import _
+from electrum.util import format_time, age
+from electrum.plugin import run_hook
+from electrum.paymentrequest import PR_UNKNOWN
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import QTreeWidgetItem, QMenu
+from .util import MyTreeWidget, pr_tooltips, pr_icons
+
+
+class RequestList(MyTreeWidget):
+ filter_columns = [0, 1, 2, 3, 4] # Date, Account, Address, Description, Amount
+
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3)
+ self.currentItemChanged.connect(self.item_changed)
+ self.itemClicked.connect(self.item_changed)
+ self.setSortingEnabled(True)
+ self.setColumnWidth(0, 180)
+ self.hideColumn(1)
+
+ def item_changed(self, item):
+ if item is None:
+ return
+ if not item.isSelected():
+ return
+ addr = str(item.text(1))
+ req = self.wallet.receive_requests.get(addr)
+ if req is None:
+ self.update()
+ return
+ expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')
+ amount = req['amount']
+ message = self.wallet.labels.get(addr, '')
+ self.parent.receive_address_e.setText(addr)
+ self.parent.receive_message_e.setText(message)
+ self.parent.receive_amount_e.setAmount(amount)
+ self.parent.expires_combo.hide()
+ self.parent.expires_label.show()
+ self.parent.expires_label.setText(expires)
+ self.parent.new_request_button.setEnabled(True)
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ # hide receive tab if no receive requests available
+ b = len(self.wallet.receive_requests) > 0
+ self.setVisible(b)
+ self.parent.receive_requests_label.setVisible(b)
+ if not b:
+ self.parent.expires_label.hide()
+ self.parent.expires_combo.show()
+
+ # update the receive address if necessary
+ current_address = self.parent.receive_address_e.text()
+ domain = self.wallet.get_receiving_addresses()
+ addr = self.wallet.get_unused_address()
+ if not current_address in domain and addr:
+ self.parent.set_receive_address(addr)
+ self.parent.new_request_button.setEnabled(addr != current_address)
+
+ # clear the list and fill it again
+ self.clear()
+ for req in self.wallet.get_sorted_requests(self.config):
+ address = req['address']
+ if address not in domain:
+ continue
+ timestamp = req.get('time', 0)
+ amount = req.get('amount')
+ expiration = req.get('exp', None)
+ message = req.get('memo', '')
+ date = format_time(timestamp)
+ status = req.get('status')
+ signature = req.get('sig')
+ requestor = req.get('name', '')
+ amount_str = self.parent.format_amount(amount) if amount else ""
+ item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])
+ if signature is not None:
+ item.setIcon(2, self.icon_cache.get(":icons/seal.png"))
+ item.setToolTip(2, 'signed by '+ requestor)
+ if status is not PR_UNKNOWN:
+ item.setIcon(6, self.icon_cache.get(pr_icons.get(status)))
+ self.addTopLevelItem(item)
+
+
+ def create_menu(self, position):
+ item = self.itemAt(position)
+ if not item:
+ return
+ addr = str(item.text(1))
+ req = self.wallet.receive_requests.get(addr)
+ if req is None:
+ self.update()
+ return
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+ menu = QMenu(self)
+ menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.app.clipboard().setText(column_data))
+ menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
+ menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
+ run_hook('receive_list_menu', menu, addr)
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py
new file mode 100644
index 000000000..86fc15ec9
--- /dev/null
+++ b/electrum/gui/qt/seed_dialog.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2013 ecdsa@github
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.i18n import _
+from electrum.mnemonic import Mnemonic
+import electrum.old_mnemonic
+from electrum.plugin import run_hook
+
+
+from .util import *
+from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
+from .completion_text_edit import CompletionTextEdit
+
+
+def seed_warning_msg(seed):
+ return ''.join([
+ "",
+ _("Please save these {0} words on paper (order is important). "),
+ _("This seed will allow you to recover your wallet in case "
+ "of computer failure."),
+ "
",
+ "" + _("WARNING") + ": ",
+ "",
+ "" + _("Never disclose your seed.") + " ",
+ "" + _("Never type it on a website.") + " ",
+ "" + _("Do not store it electronically.") + " ",
+ " "
+ ]).format(len(seed.split()))
+
+
+class SeedLayout(QVBoxLayout):
+
+ def seed_options(self):
+ dialog = QDialog()
+ vbox = QVBoxLayout(dialog)
+ if 'ext' in self.options:
+ cb_ext = QCheckBox(_('Extend this seed with custom words'))
+ cb_ext.setChecked(self.is_ext)
+ vbox.addWidget(cb_ext)
+ if 'bip39' in self.options:
+ def f(b):
+ self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed
+ self.is_bip39 = b
+ self.on_edit()
+ if b:
+ msg = ' '.join([
+ '' + _('Warning') + ': ',
+ _('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
+ _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
+ _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
+ _('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
+ ])
+ else:
+ msg = ''
+ self.seed_warning.setText(msg)
+ cb_bip39 = QCheckBox(_('BIP39 seed'))
+ cb_bip39.toggled.connect(f)
+ cb_bip39.setChecked(self.is_bip39)
+ vbox.addWidget(cb_bip39)
+ vbox.addLayout(Buttons(OkButton(dialog)))
+ if not dialog.exec_():
+ return None
+ self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
+ self.is_bip39 = cb_bip39.isChecked() if 'bip39' in self.options else False
+
+ def __init__(self, seed=None, title=None, icon=True, msg=None, options=None,
+ is_seed=None, passphrase=None, parent=None, for_seed_words=True):
+ QVBoxLayout.__init__(self)
+ self.parent = parent
+ self.options = options
+ if title:
+ self.addWidget(WWLabel(title))
+ if seed: # "read only", we already have the text
+ if for_seed_words:
+ self.seed_e = ButtonsTextEdit()
+ else: # e.g. xpub
+ self.seed_e = ShowQRTextEdit()
+ self.seed_e.setReadOnly(True)
+ self.seed_e.setText(seed)
+ else: # we expect user to enter text
+ assert for_seed_words
+ self.seed_e = CompletionTextEdit()
+ self.seed_e.setTabChangesFocus(False) # so that tab auto-completes
+ self.is_seed = is_seed
+ self.saved_is_seed = self.is_seed
+ self.seed_e.textChanged.connect(self.on_edit)
+ self.initialize_completer()
+
+ self.seed_e.setMaximumHeight(75)
+ hbox = QHBoxLayout()
+ if icon:
+ logo = QLabel()
+ logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(64, mode=Qt.SmoothTransformation))
+ logo.setMaximumWidth(60)
+ hbox.addWidget(logo)
+ hbox.addWidget(self.seed_e)
+ self.addLayout(hbox)
+ hbox = QHBoxLayout()
+ hbox.addStretch(1)
+ self.seed_type_label = QLabel('')
+ hbox.addWidget(self.seed_type_label)
+
+ # options
+ self.is_bip39 = False
+ self.is_ext = False
+ if options:
+ opt_button = EnterButton(_('Options'), self.seed_options)
+ hbox.addWidget(opt_button)
+ self.addLayout(hbox)
+ if passphrase:
+ hbox = QHBoxLayout()
+ passphrase_e = QLineEdit()
+ passphrase_e.setText(passphrase)
+ passphrase_e.setReadOnly(True)
+ hbox.addWidget(QLabel(_("Your seed extension is") + ':'))
+ hbox.addWidget(passphrase_e)
+ self.addLayout(hbox)
+ self.addStretch(1)
+ self.seed_warning = WWLabel('')
+ if msg:
+ self.seed_warning.setText(seed_warning_msg(seed))
+ self.addWidget(self.seed_warning)
+
+ def initialize_completer(self):
+ english_list = Mnemonic('en').wordlist
+ old_list = electrum.old_mnemonic.words
+ self.wordlist = english_list + list(set(old_list) - set(english_list)) #concat both lists
+ self.wordlist.sort()
+ self.completer = QCompleter(self.wordlist)
+ self.seed_e.set_completer(self.completer)
+
+ def get_seed(self):
+ text = self.seed_e.text()
+ return ' '.join(text.split())
+
+ def on_edit(self):
+ from electrum.bitcoin import seed_type
+ s = self.get_seed()
+ b = self.is_seed(s)
+ if not self.is_bip39:
+ t = seed_type(s)
+ label = _('Seed Type') + ': ' + t if t else ''
+ else:
+ from electrum.keystore import bip39_is_checksum_valid
+ is_checksum, is_wordlist = bip39_is_checksum_valid(s)
+ status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'
+ label = 'BIP39' + ' (%s)'%status
+ self.seed_type_label.setText(label)
+ self.parent.next_button.setEnabled(b)
+
+ # to account for bip39 seeds
+ for word in self.get_seed().split(" ")[:-1]:
+ if word not in self.wordlist:
+ self.seed_e.disable_suggestions()
+ return
+ self.seed_e.enable_suggestions()
+
+class KeysLayout(QVBoxLayout):
+ def __init__(self, parent=None, header_layout=None, is_valid=None, allow_multi=False):
+ QVBoxLayout.__init__(self)
+ self.parent = parent
+ self.is_valid = is_valid
+ self.text_e = ScanQRTextEdit(allow_multi=allow_multi)
+ self.text_e.textChanged.connect(self.on_edit)
+ if isinstance(header_layout, str):
+ self.addWidget(WWLabel(header_layout))
+ else:
+ self.addLayout(header_layout)
+ self.addWidget(self.text_e)
+
+ def get_text(self):
+ return self.text_e.text()
+
+ def on_edit(self):
+ b = self.is_valid(self.get_text())
+ self.parent.next_button.setEnabled(b)
+
+
+class SeedDialog(WindowModalDialog):
+
+ def __init__(self, parent, seed, passphrase):
+ WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
+ self.setMinimumWidth(400)
+ vbox = QVBoxLayout(self)
+ title = _("Your wallet generation seed is:")
+ slayout = SeedLayout(title=title, seed=seed, msg=True, passphrase=passphrase)
+ vbox.addLayout(slayout)
+ has_extension = True if passphrase else False
+ run_hook('set_seed', seed, has_extension, slayout.seed_e)
+ vbox.addLayout(Buttons(CloseButton(self)))
diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py
new file mode 100644
index 000000000..4e8e6e678
--- /dev/null
+++ b/electrum/gui/qt/transaction_dialog.py
@@ -0,0 +1,333 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import copy
+import datetime
+import json
+import traceback
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+
+import qrcode
+from qrcode import exceptions
+
+from electrum.bitcoin import base_encode
+from electrum.i18n import _
+from electrum.plugin import run_hook
+from electrum import simple_config
+
+from electrum.util import bfh
+from electrum.transaction import SerializationError
+
+from .util import *
+
+
+SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline")
+SAVE_BUTTON_DISABLED_TOOLTIP = _("Please sign this transaction in order to save it")
+
+
+dialogs = [] # Otherwise python randomly garbage collects the dialogs...
+
+
+def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
+ try:
+ d = TxDialog(tx, parent, desc, prompt_if_unsaved)
+ except SerializationError as e:
+ traceback.print_exc(file=sys.stderr)
+ parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
+ else:
+ dialogs.append(d)
+ d.show()
+
+
+class TxDialog(QDialog, MessageBoxMixin):
+
+ def __init__(self, tx, parent, desc, prompt_if_unsaved):
+ '''Transactions in the wallet will show their description.
+ Pass desc to give a description for txs not yet in the wallet.
+ '''
+ # We want to be a top-level window
+ QDialog.__init__(self, parent=None)
+ # Take a copy; it might get updated in the main window by
+ # e.g. the FX plugin. If this happens during or after a long
+ # sign operation the signatures are lost.
+ self.tx = tx = copy.deepcopy(tx)
+ try:
+ self.tx.deserialize()
+ except BaseException as e:
+ raise SerializationError(e)
+ self.main_window = parent
+ self.wallet = parent.wallet
+ self.prompt_if_unsaved = prompt_if_unsaved
+ self.saved = False
+ self.desc = desc
+
+ # if the wallet can populate the inputs with more info, do it now.
+ # as a result, e.g. we might learn an imported address tx is segwit,
+ # in which case it's ok to display txid
+ self.wallet.add_input_info_to_all_inputs(tx)
+
+ self.setMinimumWidth(950)
+ self.setWindowTitle(_("Transaction"))
+
+ vbox = QVBoxLayout()
+ self.setLayout(vbox)
+
+ vbox.addWidget(QLabel(_("Transaction ID:")))
+ self.tx_hash_e = ButtonsLineEdit()
+ qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
+ self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
+ self.tx_hash_e.setReadOnly(True)
+ vbox.addWidget(self.tx_hash_e)
+ self.tx_desc = QLabel()
+ vbox.addWidget(self.tx_desc)
+ self.status_label = QLabel()
+ vbox.addWidget(self.status_label)
+ self.date_label = QLabel()
+ vbox.addWidget(self.date_label)
+ self.amount_label = QLabel()
+ vbox.addWidget(self.amount_label)
+ self.size_label = QLabel()
+ vbox.addWidget(self.size_label)
+ self.fee_label = QLabel()
+ vbox.addWidget(self.fee_label)
+
+ self.add_io(vbox)
+
+ vbox.addStretch(1)
+
+ self.sign_button = b = QPushButton(_("Sign"))
+ b.clicked.connect(self.sign)
+
+ self.broadcast_button = b = QPushButton(_("Broadcast"))
+ b.clicked.connect(self.do_broadcast)
+
+ self.save_button = b = QPushButton(_("Save"))
+ save_button_disabled = not tx.is_complete()
+ b.setDisabled(save_button_disabled)
+ if save_button_disabled:
+ b.setToolTip(SAVE_BUTTON_DISABLED_TOOLTIP)
+ else:
+ b.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP)
+ b.clicked.connect(self.save)
+
+ self.export_button = b = QPushButton(_("Export"))
+ b.clicked.connect(self.export)
+
+ self.cancel_button = b = QPushButton(_("Close"))
+ b.clicked.connect(self.close)
+ b.setDefault(True)
+
+ self.qr_button = b = QPushButton()
+ b.setIcon(QIcon(":icons/qrcode.png"))
+ b.clicked.connect(self.show_qr)
+
+ self.copy_button = CopyButton(lambda: str(self.tx), parent.app)
+
+ # Action buttons
+ self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]
+ # Transaction sharing buttons
+ self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button, self.save_button]
+
+ run_hook('transaction_dialog', self)
+
+ hbox = QHBoxLayout()
+ hbox.addLayout(Buttons(*self.sharing_buttons))
+ hbox.addStretch(1)
+ hbox.addLayout(Buttons(*self.buttons))
+ vbox.addLayout(hbox)
+ self.update()
+
+ def do_broadcast(self):
+ self.main_window.push_top_level_window(self)
+ try:
+ self.main_window.broadcast_transaction(self.tx, self.desc)
+ finally:
+ self.main_window.pop_top_level_window(self)
+ self.saved = True
+ self.update()
+
+ def closeEvent(self, event):
+ if (self.prompt_if_unsaved and not self.saved
+ and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
+ event.ignore()
+ else:
+ event.accept()
+ try:
+ dialogs.remove(self)
+ except ValueError:
+ pass # was not in list already
+
+ def show_qr(self):
+ text = bfh(str(self.tx))
+ text = base_encode(text, base=43)
+ try:
+ self.main_window.show_qrcode(text, 'Transaction', parent=self)
+ except qrcode.exceptions.DataOverflowError:
+ self.show_error(_('Failed to display QR code.') + '\n' +
+ _('Transaction is too large in size.'))
+ except Exception as e:
+ self.show_error(_('Failed to display QR code.') + '\n' + str(e))
+
+ def sign(self):
+ def sign_done(success):
+ # note: with segwit we could save partially signed tx, because they have a txid
+ if self.tx.is_complete():
+ self.prompt_if_unsaved = True
+ self.saved = False
+ self.save_button.setDisabled(False)
+ self.save_button.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP)
+ self.update()
+ self.main_window.pop_top_level_window(self)
+
+ self.sign_button.setDisabled(True)
+ self.main_window.push_top_level_window(self)
+ self.main_window.sign_tx(self.tx, sign_done)
+
+ def save(self):
+ self.main_window.push_top_level_window(self)
+ if self.main_window.save_transaction_into_wallet(self.tx):
+ self.save_button.setDisabled(True)
+ self.saved = True
+ self.main_window.pop_top_level_window(self)
+
+
+ def export(self):
+ name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
+ fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
+ if fileName:
+ with open(fileName, "w+") as f:
+ f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
+ self.show_message(_("Transaction exported successfully"))
+ self.saved = True
+
+ def update(self):
+ desc = self.desc
+ base_unit = self.main_window.base_unit()
+ format_amount = self.main_window.format_amount
+ tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
+ size = self.tx.estimated_size()
+ self.broadcast_button.setEnabled(can_broadcast)
+ can_sign = not self.tx.is_complete() and \
+ (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))
+ self.sign_button.setEnabled(can_sign)
+ self.tx_hash_e.setText(tx_hash or _('Unknown'))
+ if desc is None:
+ self.tx_desc.hide()
+ else:
+ self.tx_desc.setText(_("Description") + ': ' + desc)
+ self.tx_desc.show()
+ self.status_label.setText(_('Status:') + ' ' + status)
+
+ if timestamp:
+ time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+ self.date_label.setText(_("Date: {}").format(time_str))
+ self.date_label.show()
+ elif exp_n:
+ text = '%.2f MB'%(exp_n/1000000)
+ self.date_label.setText(_('Position in mempool: {} from tip').format(text))
+ self.date_label.show()
+ else:
+ self.date_label.hide()
+ if amount is None:
+ amount_str = _("Transaction unrelated to your wallet")
+ elif amount > 0:
+ amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit
+ else:
+ amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit
+ size_str = _("Size:") + ' %d bytes'% size
+ fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
+ if fee is not None:
+ fee_rate = fee/size*1000
+ fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
+ confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
+ if fee_rate > confirm_rate:
+ fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
+ self.amount_label.setText(amount_str)
+ self.fee_label.setText(fee_str)
+ self.size_label.setText(size_str)
+ run_hook('transaction_dialog_update', self)
+
+ def add_io(self, vbox):
+ if self.tx.locktime > 0:
+ vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))
+
+ vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
+ ext = QTextCharFormat()
+ rec = QTextCharFormat()
+ rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))
+ rec.setToolTip(_("Wallet receive address"))
+ chg = QTextCharFormat()
+ chg.setBackground(QBrush(ColorScheme.YELLOW.as_color(background=True)))
+ chg.setToolTip(_("Wallet change address"))
+ twofactor = QTextCharFormat()
+ twofactor.setBackground(QBrush(ColorScheme.BLUE.as_color(background=True)))
+ twofactor.setToolTip(_("TrustedCoin (2FA) fee for the next batch of transactions"))
+
+ def text_format(addr):
+ if self.wallet.is_mine(addr):
+ return chg if self.wallet.is_change(addr) else rec
+ elif self.wallet.is_billing_address(addr):
+ return twofactor
+ return ext
+
+ def format_amount(amt):
+ return self.main_window.format_amount(amt, whitespaces=True)
+
+ i_text = QTextEdit()
+ i_text.setFont(QFont(MONOSPACE_FONT))
+ i_text.setReadOnly(True)
+ i_text.setMaximumHeight(100)
+ cursor = i_text.textCursor()
+ for x in self.tx.inputs():
+ if x['type'] == 'coinbase':
+ cursor.insertText('coinbase')
+ else:
+ prevout_hash = x.get('prevout_hash')
+ prevout_n = x.get('prevout_n')
+ cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext)
+ addr = self.wallet.get_txin_address(x)
+ if addr is None:
+ addr = ''
+ cursor.insertText(addr, text_format(addr))
+ if x.get('value'):
+ cursor.insertText(format_amount(x['value']), ext)
+ cursor.insertBlock()
+
+ vbox.addWidget(i_text)
+ vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
+ o_text = QTextEdit()
+ o_text.setFont(QFont(MONOSPACE_FONT))
+ o_text.setReadOnly(True)
+ o_text.setMaximumHeight(100)
+ cursor = o_text.textCursor()
+ for addr, v in self.tx.get_outputs():
+ cursor.insertText(addr, text_format(addr))
+ if v is not None:
+ cursor.insertText('\t', ext)
+ cursor.insertText(format_amount(v), ext)
+ cursor.insertBlock()
+ vbox.addWidget(o_text)
diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
new file mode 100644
index 000000000..50ed0a5c3
--- /dev/null
+++ b/electrum/gui/qt/util.py
@@ -0,0 +1,819 @@
+import os.path
+import time
+import sys
+import platform
+import queue
+from collections import namedtuple
+from functools import partial
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+
+from electrum.i18n import _
+from electrum.util import FileImportFailed, FileExportFailed
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
+
+
+if platform.system() == 'Windows':
+ MONOSPACE_FONT = 'Lucida Console'
+elif platform.system() == 'Darwin':
+ MONOSPACE_FONT = 'Monaco'
+else:
+ MONOSPACE_FONT = 'monospace'
+
+
+dialogs = []
+
+pr_icons = {
+ PR_UNPAID:":icons/unpaid.png",
+ PR_PAID:":icons/confirmed.png",
+ PR_EXPIRED:":icons/expired.png"
+}
+
+pr_tooltips = {
+ PR_UNPAID:_('Pending'),
+ PR_PAID:_('Paid'),
+ PR_EXPIRED:_('Expired')
+}
+
+expiration_values = [
+ (_('1 hour'), 60*60),
+ (_('1 day'), 24*60*60),
+ (_('1 week'), 7*24*60*60),
+ (_('Never'), None)
+]
+
+
+class Timer(QThread):
+ stopped = False
+ timer_signal = pyqtSignal()
+
+ def run(self):
+ while not self.stopped:
+ self.timer_signal.emit()
+ time.sleep(0.5)
+
+ def stop(self):
+ self.stopped = True
+ self.wait()
+
+class EnterButton(QPushButton):
+ def __init__(self, text, func):
+ QPushButton.__init__(self, text)
+ self.func = func
+ self.clicked.connect(func)
+
+ def keyPressEvent(self, e):
+ if e.key() == Qt.Key_Return:
+ self.func()
+
+
+class ThreadedButton(QPushButton):
+ def __init__(self, text, task, on_success=None, on_error=None):
+ QPushButton.__init__(self, text)
+ self.task = task
+ self.on_success = on_success
+ self.on_error = on_error
+ self.clicked.connect(self.run_task)
+
+ def run_task(self):
+ self.setEnabled(False)
+ self.thread = TaskThread(self)
+ self.thread.add(self.task, self.on_success, self.done, self.on_error)
+
+ def done(self):
+ self.setEnabled(True)
+ self.thread.stop()
+
+
+class WWLabel(QLabel):
+ def __init__ (self, text="", parent=None):
+ QLabel.__init__(self, text, parent)
+ self.setWordWrap(True)
+
+
+class HelpLabel(QLabel):
+
+ def __init__(self, text, help_text):
+ QLabel.__init__(self, text)
+ self.help_text = help_text
+ self.app = QCoreApplication.instance()
+ self.font = QFont()
+
+ def mouseReleaseEvent(self, x):
+ QMessageBox.information(self, 'Help', self.help_text)
+
+ def enterEvent(self, event):
+ self.font.setUnderline(True)
+ self.setFont(self.font)
+ self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
+ return QLabel.enterEvent(self, event)
+
+ def leaveEvent(self, event):
+ self.font.setUnderline(False)
+ self.setFont(self.font)
+ self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
+ return QLabel.leaveEvent(self, event)
+
+
+class HelpButton(QPushButton):
+ def __init__(self, text):
+ QPushButton.__init__(self, '?')
+ self.help_text = text
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setFixedWidth(20)
+ self.clicked.connect(self.onclick)
+
+ def onclick(self):
+ QMessageBox.information(self, 'Help', self.help_text)
+
+
+class InfoButton(QPushButton):
+ def __init__(self, text):
+ QPushButton.__init__(self, 'Info')
+ self.help_text = text
+ self.setFocusPolicy(Qt.NoFocus)
+ self.setFixedWidth(60)
+ self.clicked.connect(self.onclick)
+
+ def onclick(self):
+ QMessageBox.information(self, 'Info', self.help_text)
+
+
+class Buttons(QHBoxLayout):
+ def __init__(self, *buttons):
+ QHBoxLayout.__init__(self)
+ self.addStretch(1)
+ for b in buttons:
+ self.addWidget(b)
+
+class CloseButton(QPushButton):
+ def __init__(self, dialog):
+ QPushButton.__init__(self, _("Close"))
+ self.clicked.connect(dialog.close)
+ self.setDefault(True)
+
+class CopyButton(QPushButton):
+ def __init__(self, text_getter, app):
+ QPushButton.__init__(self, _("Copy"))
+ self.clicked.connect(lambda: app.clipboard().setText(text_getter()))
+
+class CopyCloseButton(QPushButton):
+ def __init__(self, text_getter, app, dialog):
+ QPushButton.__init__(self, _("Copy and Close"))
+ self.clicked.connect(lambda: app.clipboard().setText(text_getter()))
+ self.clicked.connect(dialog.close)
+ self.setDefault(True)
+
+class OkButton(QPushButton):
+ def __init__(self, dialog, label=None):
+ QPushButton.__init__(self, label or _("OK"))
+ self.clicked.connect(dialog.accept)
+ self.setDefault(True)
+
+class CancelButton(QPushButton):
+ def __init__(self, dialog, label=None):
+ QPushButton.__init__(self, label or _("Cancel"))
+ self.clicked.connect(dialog.reject)
+
+class MessageBoxMixin(object):
+ def top_level_window_recurse(self, window=None, test_func=None):
+ window = window or self
+ classes = (WindowModalDialog, QMessageBox)
+ if test_func is None:
+ test_func = lambda x: True
+ for n, child in enumerate(window.children()):
+ # Test for visibility as old closed dialogs may not be GC-ed.
+ # Only accept children that confirm to test_func.
+ if isinstance(child, classes) and child.isVisible() \
+ and test_func(child):
+ return self.top_level_window_recurse(child, test_func=test_func)
+ return window
+
+ def top_level_window(self, test_func=None):
+ return self.top_level_window_recurse(test_func)
+
+ def question(self, msg, parent=None, title=None, icon=None):
+ Yes, No = QMessageBox.Yes, QMessageBox.No
+ return self.msg_box(icon or QMessageBox.Question,
+ parent, title or '',
+ msg, buttons=Yes|No, defaultButton=No) == Yes
+
+ def show_warning(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Warning, parent,
+ title or _('Warning'), msg)
+
+ def show_error(self, msg, parent=None):
+ return self.msg_box(QMessageBox.Warning, parent,
+ _('Error'), msg)
+
+ def show_critical(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Critical, parent,
+ title or _('Critical Error'), msg)
+
+ def show_message(self, msg, parent=None, title=None):
+ return self.msg_box(QMessageBox.Information, parent,
+ title or _('Information'), msg)
+
+ def msg_box(self, icon, parent, title, text, buttons=QMessageBox.Ok,
+ defaultButton=QMessageBox.NoButton):
+ parent = parent or self.top_level_window()
+ if type(icon) is QPixmap:
+ d = QMessageBox(QMessageBox.Information, title, str(text), buttons, parent)
+ d.setIconPixmap(icon)
+ else:
+ d = QMessageBox(icon, title, str(text), buttons, parent)
+ d.setWindowModality(Qt.WindowModal)
+ d.setDefaultButton(defaultButton)
+ d.setTextInteractionFlags(Qt.TextSelectableByMouse)
+ return d.exec_()
+
+class WindowModalDialog(QDialog, MessageBoxMixin):
+ '''Handy wrapper; window modal dialogs are better for our multi-window
+ daemon model as other wallet windows can still be accessed.'''
+ def __init__(self, parent, title=None):
+ QDialog.__init__(self, parent)
+ self.setWindowModality(Qt.WindowModal)
+ if title:
+ self.setWindowTitle(title)
+
+
+class WaitingDialog(WindowModalDialog):
+ '''Shows a please wait dialog whilst running a task. It is not
+ necessary to maintain a reference to this dialog.'''
+ def __init__(self, parent, message, task, on_success=None, on_error=None):
+ assert parent
+ if isinstance(parent, MessageBoxMixin):
+ parent = parent.top_level_window()
+ WindowModalDialog.__init__(self, parent, _("Please wait"))
+ vbox = QVBoxLayout(self)
+ vbox.addWidget(QLabel(message))
+ self.accepted.connect(self.on_accepted)
+ self.show()
+ self.thread = TaskThread(self)
+ self.thread.finished.connect(self.deleteLater) # see #3956
+ self.thread.add(task, on_success, self.accept, on_error)
+
+ def wait(self):
+ self.thread.wait()
+
+ def on_accepted(self):
+ self.thread.stop()
+
+
+def line_dialog(parent, title, label, ok_label, default=None):
+ dialog = WindowModalDialog(parent, title)
+ dialog.setMinimumWidth(500)
+ l = QVBoxLayout()
+ dialog.setLayout(l)
+ l.addWidget(QLabel(label))
+ txt = QLineEdit()
+ if default:
+ txt.setText(default)
+ l.addWidget(txt)
+ l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
+ if dialog.exec_():
+ return txt.text()
+
+def text_dialog(parent, title, header_layout, ok_label, default=None, allow_multi=False):
+ from .qrtextedit import ScanQRTextEdit
+ dialog = WindowModalDialog(parent, title)
+ dialog.setMinimumWidth(600)
+ l = QVBoxLayout()
+ dialog.setLayout(l)
+ if isinstance(header_layout, str):
+ l.addWidget(QLabel(header_layout))
+ else:
+ l.addLayout(header_layout)
+ txt = ScanQRTextEdit(allow_multi=allow_multi)
+ if default:
+ txt.setText(default)
+ l.addWidget(txt)
+ l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
+ if dialog.exec_():
+ return txt.toPlainText()
+
+class ChoicesLayout(object):
+ def __init__(self, msg, choices, on_clicked=None, checked_index=0):
+ vbox = QVBoxLayout()
+ if len(msg) > 50:
+ vbox.addWidget(WWLabel(msg))
+ msg = ""
+ gb2 = QGroupBox(msg)
+ vbox.addWidget(gb2)
+
+ vbox2 = QVBoxLayout()
+ gb2.setLayout(vbox2)
+
+ self.group = group = QButtonGroup()
+ for i,c in enumerate(choices):
+ button = QRadioButton(gb2)
+ button.setText(c)
+ vbox2.addWidget(button)
+ group.addButton(button)
+ group.setId(button, i)
+ if i==checked_index:
+ button.setChecked(True)
+
+ if on_clicked:
+ group.buttonClicked.connect(partial(on_clicked, self))
+
+ self.vbox = vbox
+
+ def layout(self):
+ return self.vbox
+
+ def selected_index(self):
+ return self.group.checkedId()
+
+def address_field(addresses):
+ hbox = QHBoxLayout()
+ address_e = QLineEdit()
+ if addresses and len(addresses) > 0:
+ address_e.setText(addresses[0])
+ else:
+ addresses = []
+ def func():
+ try:
+ i = addresses.index(str(address_e.text())) + 1
+ i = i % len(addresses)
+ address_e.setText(addresses[i])
+ except ValueError:
+ # the user might have changed address_e to an
+ # address not in the wallet (or to something that isn't an address)
+ if addresses and len(addresses) > 0:
+ address_e.setText(addresses[0])
+ button = QPushButton(_('Address'))
+ button.clicked.connect(func)
+ hbox.addWidget(button)
+ hbox.addWidget(address_e)
+ return hbox, address_e
+
+
+def filename_field(parent, config, defaultname, select_msg):
+
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(_("Format")))
+ gb = QGroupBox("format", parent)
+ b1 = QRadioButton(gb)
+ b1.setText(_("CSV"))
+ b1.setChecked(True)
+ b2 = QRadioButton(gb)
+ b2.setText(_("json"))
+ vbox.addWidget(b1)
+ vbox.addWidget(b2)
+
+ hbox = QHBoxLayout()
+
+ directory = config.get('io_dir', os.path.expanduser('~'))
+ path = os.path.join( directory, defaultname )
+ filename_e = QLineEdit()
+ filename_e.setText(path)
+
+ def func():
+ text = filename_e.text()
+ _filter = "*.csv" if text.endswith(".csv") else "*.json" if text.endswith(".json") else None
+ p, __ = QFileDialog.getSaveFileName(None, select_msg, text, _filter)
+ if p:
+ filename_e.setText(p)
+
+ button = QPushButton(_('File'))
+ button.clicked.connect(func)
+ hbox.addWidget(button)
+ hbox.addWidget(filename_e)
+ vbox.addLayout(hbox)
+
+ def set_csv(v):
+ text = filename_e.text()
+ text = text.replace(".json",".csv") if v else text.replace(".csv",".json")
+ filename_e.setText(text)
+
+ b1.clicked.connect(lambda: set_csv(True))
+ b2.clicked.connect(lambda: set_csv(False))
+
+ return vbox, filename_e, b1
+
+class ElectrumItemDelegate(QStyledItemDelegate):
+ def createEditor(self, parent, option, index):
+ return self.parent().createEditor(parent, option, index)
+
+class MyTreeWidget(QTreeWidget):
+
+ def __init__(self, parent, create_menu, headers, stretch_column=None,
+ editable_columns=None):
+ QTreeWidget.__init__(self, parent)
+ self.parent = parent
+ self.config = self.parent.config
+ self.stretch_column = stretch_column
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(create_menu)
+ self.setUniformRowHeights(True)
+ # extend the syntax for consistency
+ self.addChild = self.addTopLevelItem
+ self.insertChild = self.insertTopLevelItem
+
+ self.icon_cache = IconCache()
+
+ # Control which columns are editable
+ self.editor = None
+ self.pending_update = False
+ if editable_columns is None:
+ editable_columns = {stretch_column}
+ else:
+ editable_columns = set(editable_columns)
+ self.editable_columns = editable_columns
+ self.setItemDelegate(ElectrumItemDelegate(self))
+ self.itemDoubleClicked.connect(self.on_doubleclick)
+ self.update_headers(headers)
+ self.current_filter = ""
+
+ self.setRootIsDecorated(False) # remove left margin
+ self.toolbar_shown = False
+
+ def update_headers(self, headers):
+ self.setColumnCount(len(headers))
+ self.setHeaderLabels(headers)
+ self.header().setStretchLastSection(False)
+ for col in range(len(headers)):
+ sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
+ self.header().setSectionResizeMode(col, sm)
+
+ def editItem(self, item, column):
+ if column in self.editable_columns:
+ try:
+ self.editing_itemcol = (item, column, item.text(column))
+ # Calling setFlags causes on_changed events for some reason
+ item.setFlags(item.flags() | Qt.ItemIsEditable)
+ QTreeWidget.editItem(self, item, column)
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
+ except RuntimeError:
+ # (item) wrapped C/C++ object has been deleted
+ pass
+
+ def keyPressEvent(self, event):
+ if event.key() in [ Qt.Key_F2, Qt.Key_Return ] and self.editor is None:
+ self.on_activated(self.currentItem(), self.currentColumn())
+ else:
+ QTreeWidget.keyPressEvent(self, event)
+
+ def permit_edit(self, item, column):
+ return (column in self.editable_columns
+ and self.on_permit_edit(item, column))
+
+ def on_permit_edit(self, item, column):
+ return True
+
+ def on_doubleclick(self, item, column):
+ if self.permit_edit(item, column):
+ self.editItem(item, column)
+
+ def on_activated(self, item, column):
+ # on 'enter' we show the menu
+ pt = self.visualItemRect(item).bottomLeft()
+ pt.setX(50)
+ self.customContextMenuRequested.emit(pt)
+
+ def createEditor(self, parent, option, index):
+ self.editor = QStyledItemDelegate.createEditor(self.itemDelegate(),
+ parent, option, index)
+ self.editor.editingFinished.connect(self.editing_finished)
+ return self.editor
+
+ def editing_finished(self):
+ # Long-time QT bug - pressing Enter to finish editing signals
+ # editingFinished twice. If the item changed the sequence is
+ # Enter key: editingFinished, on_change, editingFinished
+ # Mouse: on_change, editingFinished
+ # This mess is the cleanest way to ensure we make the
+ # on_edited callback with the updated item
+ if self.editor:
+ (item, column, prior_text) = self.editing_itemcol
+ if self.editor.text() == prior_text:
+ self.editor = None # Unchanged - ignore any 2nd call
+ elif item.text(column) == prior_text:
+ pass # Buggy first call on Enter key, item not yet updated
+ else:
+ # What we want - the updated item
+ self.on_edited(*self.editing_itemcol)
+ self.editor = None
+
+ # Now do any pending updates
+ if self.editor is None and self.pending_update:
+ self.pending_update = False
+ self.on_update()
+
+ def on_edited(self, item, column, prior):
+ '''Called only when the text actually changes'''
+ key = item.data(0, Qt.UserRole)
+ text = item.text(column)
+ self.parent.wallet.set_label(key, text)
+ self.parent.history_list.update_labels()
+ self.parent.update_completions()
+
+ def update(self):
+ # Defer updates if editing
+ if self.editor:
+ self.pending_update = True
+ else:
+ self.setUpdatesEnabled(False)
+ scroll_pos = self.verticalScrollBar().value()
+ self.on_update()
+ self.setUpdatesEnabled(True)
+ # To paint the list before resetting the scroll position
+ self.parent.app.processEvents()
+ self.verticalScrollBar().setValue(scroll_pos)
+ if self.current_filter:
+ self.filter(self.current_filter)
+
+ def on_update(self):
+ pass
+
+ def get_leaves(self, root):
+ child_count = root.childCount()
+ if child_count == 0:
+ yield root
+ for i in range(child_count):
+ item = root.child(i)
+ for x in self.get_leaves(item):
+ yield x
+
+ def filter(self, p):
+ columns = self.__class__.filter_columns
+ p = p.lower()
+ self.current_filter = p
+ for item in self.get_leaves(self.invisibleRootItem()):
+ item.setHidden(all([item.text(column).lower().find(p) == -1
+ for column in columns]))
+
+ def create_toolbar(self, config=None):
+ hbox = QHBoxLayout()
+ buttons = self.get_toolbar_buttons()
+ for b in buttons:
+ b.setVisible(False)
+ hbox.addWidget(b)
+ hide_button = QPushButton('x')
+ hide_button.setVisible(False)
+ hide_button.pressed.connect(lambda: self.show_toolbar(False, config))
+ self.toolbar_buttons = buttons + (hide_button,)
+ hbox.addStretch()
+ hbox.addWidget(hide_button)
+ return hbox
+
+ def save_toolbar_state(self, state, config):
+ pass # implemented in subclasses
+
+ def show_toolbar(self, state, config=None):
+ if state == self.toolbar_shown:
+ return
+ self.toolbar_shown = state
+ if config:
+ self.save_toolbar_state(state, config)
+ for b in self.toolbar_buttons:
+ b.setVisible(state)
+ if not state:
+ self.on_hide_toolbar()
+
+ def toggle_toolbar(self, config=None):
+ self.show_toolbar(not self.toolbar_shown, config)
+
+
+class ButtonsWidget(QWidget):
+
+ def __init__(self):
+ super(QWidget, self).__init__()
+ self.buttons = []
+
+ def resizeButtons(self):
+ frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
+ x = self.rect().right() - frameWidth
+ y = self.rect().bottom() - frameWidth
+ for button in self.buttons:
+ sz = button.sizeHint()
+ x -= sz.width()
+ button.move(x, y - sz.height())
+
+ def addButton(self, icon_name, on_click, tooltip):
+ button = QToolButton(self)
+ button.setIcon(QIcon(icon_name))
+ button.setStyleSheet("QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }")
+ button.setVisible(True)
+ button.setToolTip(tooltip)
+ button.clicked.connect(on_click)
+ self.buttons.append(button)
+ return button
+
+ def addCopyButton(self, app):
+ self.app = app
+ self.addButton(":icons/copy.png", self.on_copy, _("Copy to clipboard"))
+
+ def on_copy(self):
+ self.app.clipboard().setText(self.text())
+ QToolTip.showText(QCursor.pos(), _("Text copied to clipboard"), self)
+
+class ButtonsLineEdit(QLineEdit, ButtonsWidget):
+ def __init__(self, text=None):
+ QLineEdit.__init__(self, text)
+ self.buttons = []
+
+ def resizeEvent(self, e):
+ o = QLineEdit.resizeEvent(self, e)
+ self.resizeButtons()
+ return o
+
+class ButtonsTextEdit(QPlainTextEdit, ButtonsWidget):
+ def __init__(self, text=None):
+ QPlainTextEdit.__init__(self, text)
+ self.setText = self.setPlainText
+ self.text = self.toPlainText
+ self.buttons = []
+
+ def resizeEvent(self, e):
+ o = QPlainTextEdit.resizeEvent(self, e)
+ self.resizeButtons()
+ return o
+
+
+class TaskThread(QThread):
+ '''Thread that runs background tasks. Callbacks are guaranteed
+ to happen in the context of its parent.'''
+
+ Task = namedtuple("Task", "task cb_success cb_done cb_error")
+ doneSig = pyqtSignal(object, object, object)
+
+ def __init__(self, parent, on_error=None):
+ super(TaskThread, self).__init__(parent)
+ self.on_error = on_error
+ self.tasks = queue.Queue()
+ self.doneSig.connect(self.on_done)
+ self.start()
+
+ def add(self, task, on_success=None, on_done=None, on_error=None):
+ on_error = on_error or self.on_error
+ self.tasks.put(TaskThread.Task(task, on_success, on_done, on_error))
+
+ def run(self):
+ while True:
+ task = self.tasks.get()
+ if not task:
+ break
+ try:
+ result = task.task()
+ self.doneSig.emit(result, task.cb_done, task.cb_success)
+ except BaseException:
+ self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)
+
+ def on_done(self, result, cb_done, cb_result):
+ # This runs in the parent's thread.
+ if cb_done:
+ cb_done()
+ if cb_result:
+ cb_result(result)
+
+ def stop(self):
+ self.tasks.put(None)
+
+
+class ColorSchemeItem:
+ def __init__(self, fg_color, bg_color):
+ self.colors = (fg_color, bg_color)
+
+ def _get_color(self, background):
+ return self.colors[(int(background) + int(ColorScheme.dark_scheme)) % 2]
+
+ def as_stylesheet(self, background=False):
+ css_prefix = "background-" if background else ""
+ color = self._get_color(background)
+ return "QWidget {{ {}color:{}; }}".format(css_prefix, color)
+
+ def as_color(self, background=False):
+ color = self._get_color(background)
+ return QColor(color)
+
+
+class ColorScheme:
+ dark_scheme = False
+
+ GREEN = ColorSchemeItem("#117c11", "#8af296")
+ YELLOW = ColorSchemeItem("#897b2a", "#ffff00")
+ RED = ColorSchemeItem("#7c1111", "#f18c8c")
+ BLUE = ColorSchemeItem("#123b7c", "#8cb3f2")
+ DEFAULT = ColorSchemeItem("black", "white")
+
+ @staticmethod
+ def has_dark_background(widget):
+ brightness = sum(widget.palette().color(QPalette.Background).getRgb()[0:3])
+ return brightness < (255*3/2)
+
+ @staticmethod
+ def update_from_widget(widget, force_dark=False):
+ if force_dark or ColorScheme.has_dark_background(widget):
+ ColorScheme.dark_scheme = True
+
+
+class AcceptFileDragDrop:
+ def __init__(self, file_type=""):
+ assert isinstance(self, QWidget)
+ self.setAcceptDrops(True)
+ self.file_type = file_type
+
+ def validateEvent(self, event):
+ if not event.mimeData().hasUrls():
+ event.ignore()
+ return False
+ for url in event.mimeData().urls():
+ if not url.toLocalFile().endswith(self.file_type):
+ event.ignore()
+ return False
+ event.accept()
+ return True
+
+ def dragEnterEvent(self, event):
+ self.validateEvent(event)
+
+ def dragMoveEvent(self, event):
+ if self.validateEvent(event):
+ event.setDropAction(Qt.CopyAction)
+
+ def dropEvent(self, event):
+ if self.validateEvent(event):
+ for url in event.mimeData().urls():
+ self.onFileAdded(url.toLocalFile())
+
+ def onFileAdded(self, fn):
+ raise NotImplementedError()
+
+
+def import_meta_gui(electrum_window, title, importer, on_success):
+ filter_ = "JSON (*.json);;All files (*)"
+ filename = electrum_window.getOpenFileName(_("Open {} file").format(title), filter_)
+ if not filename:
+ return
+ try:
+ importer(filename)
+ except FileImportFailed as e:
+ electrum_window.show_critical(str(e))
+ else:
+ electrum_window.show_message(_("Your {} were successfully imported").format(title))
+ on_success()
+
+
+def export_meta_gui(electrum_window, title, exporter):
+ filter_ = "JSON (*.json);;All files (*)"
+ filename = electrum_window.getSaveFileName(_("Select file to save your {}").format(title),
+ 'electrum_{}.json'.format(title), filter_)
+ if not filename:
+ return
+ try:
+ exporter(filename)
+ except FileExportFailed as e:
+ electrum_window.show_critical(str(e))
+ else:
+ electrum_window.show_message(_("Your {0} were exported to '{1}'")
+ .format(title, str(filename)))
+
+
+def get_parent_main_window(widget):
+ """Returns a reference to the ElectrumWindow this widget belongs to."""
+ from .main_window import ElectrumWindow
+ for _ in range(100):
+ if widget is None:
+ return None
+ if not isinstance(widget, ElectrumWindow):
+ widget = widget.parentWidget()
+ else:
+ return widget
+ return None
+
+class SortableTreeWidgetItem(QTreeWidgetItem):
+ DataRole = Qt.UserRole + 1
+
+ def __lt__(self, other):
+ column = self.treeWidget().sortColumn()
+ if None not in [x.data(column, self.DataRole) for x in [self, other]]:
+ # We have set custom data to sort by
+ return self.data(column, self.DataRole) < other.data(column, self.DataRole)
+ try:
+ # Is the value something numeric?
+ return float(self.text(column)) < float(other.text(column))
+ except ValueError:
+ # If not, we will just do string comparison
+ return self.text(column) < other.text(column)
+
+
+class IconCache:
+
+ def __init__(self):
+ self.__cache = {}
+
+ def get(self, file_name):
+ if file_name not in self.__cache:
+ self.__cache[file_name] = QIcon(file_name)
+ return self.__cache[file_name]
+
+
+if __name__ == "__main__":
+ app = QApplication([])
+ t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))
+ t.start()
+ app.exec_()
diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py
new file mode 100644
index 000000000..78e865360
--- /dev/null
+++ b/electrum/gui/qt/utxo_list.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from .util import *
+from electrum.i18n import _
+
+
+class UTXOList(MyTreeWidget):
+ filter_columns = [0, 2] # Address, Label
+
+ def __init__(self, parent=None):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [ _('Address'), _('Label'), _('Amount'), _('Height'), _('Output point')], 1)
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setSortingEnabled(True)
+
+ def get_name(self, x):
+ return x.get('prevout_hash') + ":%d"%x.get('prevout_n')
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ item = self.currentItem()
+ self.clear()
+ self.utxos = self.wallet.get_utxos()
+ for x in self.utxos:
+ address = x.get('address')
+ height = x.get('height')
+ name = self.get_name(x)
+ label = self.wallet.get_label(x.get('prevout_hash'))
+ amount = self.parent.format_amount(x['value'], whitespaces=True)
+ utxo_item = SortableTreeWidgetItem([address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]])
+ utxo_item.setFont(0, QFont(MONOSPACE_FONT))
+ utxo_item.setFont(2, QFont(MONOSPACE_FONT))
+ utxo_item.setFont(4, QFont(MONOSPACE_FONT))
+ utxo_item.setData(0, Qt.UserRole, name)
+ if self.wallet.is_frozen(address):
+ utxo_item.setBackground(0, ColorScheme.BLUE.as_color(True))
+ self.addChild(utxo_item)
+
+ def create_menu(self, position):
+ selected = [x.data(0, Qt.UserRole) for x in self.selectedItems()]
+ if not selected:
+ return
+ menu = QMenu()
+ coins = filter(lambda x: self.get_name(x) in selected, self.utxos)
+
+ menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins))
+ if len(selected) == 1:
+ txid = selected[0].split(':')[0]
+ tx = self.wallet.transactions.get(txid)
+ menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
+
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def on_permit_edit(self, item, column):
+ # disable editing fields in this tab (labels)
+ return False
diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py
new file mode 100644
index 000000000..4735141f0
--- /dev/null
+++ b/electrum/gui/stdio.py
@@ -0,0 +1,233 @@
+from decimal import Decimal
+_ = lambda x:x
+#from i18n import _
+from electrum import WalletStorage, Wallet
+from electrum.util import format_satoshis, set_verbosity
+from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
+from electrum.transaction import TxOutput
+import getpass, datetime
+
+# minimal fdisk like gui for console usage
+# written by rofl0r, with some bits stolen from the text gui (ncurses)
+
+class ElectrumGui:
+
+ def __init__(self, config, daemon, plugins):
+ self.config = config
+ self.network = daemon.network
+ storage = WalletStorage(config.get_wallet_path())
+ if not storage.file_exists:
+ print("Wallet not found. try 'electrum create'")
+ exit()
+ if storage.is_encrypted():
+ password = getpass.getpass('Password:', stream=None)
+ storage.decrypt(password)
+
+ self.done = 0
+ self.last_balance = ""
+
+ set_verbosity(False)
+
+ self.str_recipient = ""
+ self.str_description = ""
+ self.str_amount = ""
+ self.str_fee = ""
+
+ self.wallet = Wallet(storage)
+ self.wallet.start_threads(self.network)
+ self.contacts = self.wallet.contacts
+
+ self.network.register_callback(self.on_network, ['updated', 'banner'])
+ self.commands = [_("[h] - displays this help text"), \
+ _("[i] - display transaction history"), \
+ _("[o] - enter payment order"), \
+ _("[p] - print stored payment order"), \
+ _("[s] - send stored payment order"), \
+ _("[r] - show own receipt addresses"), \
+ _("[c] - display contacts"), \
+ _("[b] - print server banner"), \
+ _("[q] - quit") ]
+ self.num_commands = len(self.commands)
+
+ def on_network(self, event, *args):
+ if event == 'updated':
+ self.updated()
+ elif event == 'banner':
+ self.print_banner()
+
+ def main_command(self):
+ self.print_balance()
+ c = input("enter command: ")
+ if c == "h" : self.print_commands()
+ elif c == "i" : self.print_history()
+ elif c == "o" : self.enter_order()
+ elif c == "p" : self.print_order()
+ elif c == "s" : self.send_order()
+ elif c == "r" : self.print_addresses()
+ elif c == "c" : self.print_contacts()
+ elif c == "b" : self.print_banner()
+ elif c == "n" : self.network_dialog()
+ elif c == "e" : self.settings_dialog()
+ elif c == "q" : self.done = 1
+ else: self.print_commands()
+
+ def updated(self):
+ s = self.get_balance()
+ if s != self.last_balance:
+ print(s)
+ self.last_balance = s
+ return True
+
+ def print_commands(self):
+ self.print_list(self.commands, "Available commands")
+
+ def print_history(self):
+ width = [20, 40, 14, 14]
+ delta = (80 - sum(width) - 4)/3
+ format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \
+ + "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
+ messages = []
+
+ for tx_hash, tx_mined_status, delta, balance in self.wallet.get_history():
+ if tx_mined_status.conf:
+ timestamp = tx_mined_status.timestamp
+ try:
+ time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+ except Exception:
+ time_str = "unknown"
+ else:
+ time_str = 'unconfirmed'
+
+ label = self.wallet.get_label(tx_hash)
+ messages.append( format_str%( time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
+
+ self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
+
+
+ def print_balance(self):
+ print(self.get_balance())
+
+ def get_balance(self):
+ if self.wallet.network.is_connected():
+ if not self.wallet.up_to_date:
+ msg = _( "Synchronizing..." )
+ else:
+ c, u, x = self.wallet.get_balance()
+ msg = _("Balance")+": %f "%(Decimal(c) / COIN)
+ if u:
+ msg += " [%f unconfirmed]"%(Decimal(u) / COIN)
+ if x:
+ msg += " [%f unmatured]"%(Decimal(x) / COIN)
+ else:
+ msg = _( "Not connected" )
+
+ return(msg)
+
+
+ def print_contacts(self):
+ messages = map(lambda x: "%20s %45s "%(x[0], x[1][1]), self.contacts.items())
+ self.print_list(messages, "%19s %25s "%("Key", "Value"))
+
+ def print_addresses(self):
+ messages = map(lambda addr: "%30s %30s "%(addr, self.wallet.labels.get(addr,"")), self.wallet.get_addresses())
+ self.print_list(messages, "%19s %25s "%("Address", "Label"))
+
+ def print_order(self):
+ print("send order to " + self.str_recipient + ", amount: " + self.str_amount \
+ + "\nfee: " + self.str_fee + ", desc: " + self.str_description)
+
+ def enter_order(self):
+ self.str_recipient = input("Pay to: ")
+ self.str_description = input("Description : ")
+ self.str_amount = input("Amount: ")
+ self.str_fee = input("Fee: ")
+
+ def send_order(self):
+ self.do_send()
+
+ def print_banner(self):
+ for i, x in enumerate( self.wallet.network.banner.split('\n') ):
+ print( x )
+
+ def print_list(self, lst, firstline):
+ lst = list(lst)
+ self.maxpos = len(lst)
+ if not self.maxpos: return
+ print(firstline)
+ for i in range(self.maxpos):
+ msg = lst[i] if i < len(lst) else ""
+ print(msg)
+
+
+ def main(self):
+ while self.done == 0: self.main_command()
+
+ def do_send(self):
+ if not is_address(self.str_recipient):
+ print(_('Invalid Bitcore address'))
+ return
+ try:
+ amount = int(Decimal(self.str_amount) * COIN)
+ except Exception:
+ print(_('Invalid Amount'))
+ return
+ try:
+ fee = int(Decimal(self.str_fee) * COIN)
+ except Exception:
+ print(_('Invalid Fee'))
+ return
+
+ if self.wallet.has_password():
+ password = self.password_dialog()
+ if not password:
+ return
+ else:
+ password = None
+
+ c = ""
+ while c != "y":
+ c = input("ok to send (y/n)?")
+ if c == "n": return
+
+ try:
+ tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
+ password, self.config, fee)
+ except Exception as e:
+ print(str(e))
+ return
+
+ if self.str_description:
+ self.wallet.labels[tx.txid()] = self.str_description
+
+ print(_("Please wait..."))
+ status, msg = self.network.broadcast_transaction(tx)
+
+ if status:
+ print(_('Payment sent.'))
+ #self.do_clear()
+ #self.update_contacts_tab()
+ else:
+ print(_('Error'))
+
+ def network_dialog(self):
+ print("use 'electrum setconfig server/proxy' to change your network settings")
+ return True
+
+
+ def settings_dialog(self):
+ print("use 'electrum setconfig' to change your settings")
+ return True
+
+ def password_dialog(self):
+ return getpass.getpass()
+
+
+# XXX unused
+
+ def run_receive_tab(self, c):
+ #if c == 10:
+ # out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"])
+ return
+
+ def run_contacts_tab(self, c):
+ pass
diff --git a/electrum/gui/text.py b/electrum/gui/text.py
new file mode 100644
index 000000000..fd5ed29f5
--- /dev/null
+++ b/electrum/gui/text.py
@@ -0,0 +1,505 @@
+import tty, sys
+import curses, datetime, locale
+from decimal import Decimal
+import getpass
+
+import electrum
+from electrum.util import format_satoshis, set_verbosity
+from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
+from electrum.transaction import TxOutput
+from .. import Wallet, WalletStorage
+
+_ = lambda x:x
+
+
+
+class ElectrumGui:
+
+ def __init__(self, config, daemon, plugins):
+
+ self.config = config
+ self.network = daemon.network
+ storage = WalletStorage(config.get_wallet_path())
+ if not storage.file_exists():
+ print("Wallet not found. try 'electrum create'")
+ exit()
+ if storage.is_encrypted():
+ password = getpass.getpass('Password:', stream=None)
+ storage.decrypt(password)
+ self.wallet = Wallet(storage)
+ self.wallet.start_threads(self.network)
+ self.contacts = self.wallet.contacts
+
+ locale.setlocale(locale.LC_ALL, '')
+ self.encoding = locale.getpreferredencoding()
+
+ self.stdscr = curses.initscr()
+ curses.noecho()
+ curses.cbreak()
+ curses.start_color()
+ curses.use_default_colors()
+ curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
+ curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN)
+ curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
+ self.stdscr.keypad(1)
+ self.stdscr.border(0)
+ self.maxy, self.maxx = self.stdscr.getmaxyx()
+ self.set_cursor(0)
+ self.w = curses.newwin(10, 50, 5, 5)
+
+ set_verbosity(False)
+ self.tab = 0
+ self.pos = 0
+ self.popup_pos = 0
+
+ self.str_recipient = ""
+ self.str_description = ""
+ self.str_amount = ""
+ self.str_fee = ""
+ self.history = None
+
+ if self.network:
+ self.network.register_callback(self.update, ['updated'])
+
+ self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")]
+ self.num_tabs = len(self.tab_names)
+
+
+ def set_cursor(self, x):
+ try:
+ curses.curs_set(x)
+ except Exception:
+ pass
+
+ def restore_or_create(self):
+ pass
+
+ def verify_seed(self):
+ pass
+
+ def get_string(self, y, x):
+ self.set_cursor(1)
+ curses.echo()
+ self.stdscr.addstr( y, x, " "*20, curses.A_REVERSE)
+ s = self.stdscr.getstr(y,x)
+ curses.noecho()
+ self.set_cursor(0)
+ return s
+
+ def update(self, event):
+ self.update_history()
+ if self.tab == 0:
+ self.print_history()
+ self.refresh()
+
+ def print_history(self):
+
+ width = [20, 40, 14, 14]
+ delta = (self.maxx - sum(width) - 4)/3
+ format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%"+"%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
+
+ if self.history is None:
+ self.update_history()
+
+ self.print_list(self.history[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
+
+ def update_history(self):
+ width = [20, 40, 14, 14]
+ delta = (self.maxx - sum(width) - 4)/3
+ format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%"+"%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
+
+ b = 0
+ self.history = []
+ for tx_hash, tx_mined_status, value, balance in self.wallet.get_history():
+ if tx_mined_status.conf:
+ timestamp = tx_mined_status.timestamp
+ try:
+ time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+ except Exception:
+ time_str = "------"
+ else:
+ time_str = 'unconfirmed'
+
+ label = self.wallet.get_label(tx_hash)
+ if len(label) > 40:
+ label = label[0:37] + '...'
+ self.history.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
+
+
+ def print_balance(self):
+ if not self.network:
+ msg = _("Offline")
+ elif self.network.is_connected():
+ if not self.wallet.up_to_date:
+ msg = _("Synchronizing...")
+ else:
+ c, u, x = self.wallet.get_balance()
+ msg = _("Balance")+": %f "%(Decimal(c) / COIN)
+ if u:
+ msg += " [%f unconfirmed]"%(Decimal(u) / COIN)
+ if x:
+ msg += " [%f unmatured]"%(Decimal(x) / COIN)
+ else:
+ msg = _("Not connected")
+
+ self.stdscr.addstr( self.maxy -1, 3, msg)
+
+ for i in range(self.num_tabs):
+ 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)
+
+ self.stdscr.addstr(self.maxy -1, self.maxx-30, ' '.join([_("Settings"), _("Network"), _("Quit")]))
+
+ def print_receive(self):
+ addr = self.wallet.get_receiving_address()
+ self.stdscr.addstr(2, 1, "Address: "+addr)
+ self.print_qr(addr)
+
+ def print_contacts(self):
+ messages = map(lambda x: "%20s %45s "%(x[0], x[1][1]), self.contacts.items())
+ self.print_list(messages, "%19s %15s "%("Key", "Value"))
+
+ def print_addresses(self):
+ fmt = "%-35s %-30s"
+ messages = map(lambda addr: fmt % (addr, self.wallet.labels.get(addr,"")), self.wallet.get_addresses())
+ self.print_list(messages, fmt % ("Address", "Label"))
+
+ def print_edit_line(self, y, label, text, index, size):
+ text += " "*(size - len(text) )
+ self.stdscr.addstr( y, 2, label)
+ self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1))
+
+ def print_send_tab(self):
+ self.stdscr.clear()
+ self.print_edit_line(3, _("Pay to"), self.str_recipient, 0, 40)
+ self.print_edit_line(5, _("Description"), self.str_description, 1, 40)
+ self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15)
+ self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15)
+ self.stdscr.addstr( 12, 15, _("[Send]"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2))
+ self.stdscr.addstr( 12, 25, _("[Clear]"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2))
+ self.maxpos = 6
+
+ def print_banner(self):
+ if self.network:
+ self.print_list( self.network.banner.split('\n'))
+
+ def print_qr(self, data):
+ import qrcode
+ try:
+ from StringIO import StringIO
+ except ImportError:
+ from io import StringIO
+
+ s = StringIO()
+ self.qr = qrcode.QRCode()
+ self.qr.add_data(data)
+ self.qr.print_ascii(out=s, invert=False)
+ msg = s.getvalue()
+ lines = msg.split('\n')
+ for i, l in enumerate(lines):
+ l = l.encode("utf-8")
+ self.stdscr.addstr(i+5, 5, l, curses.color_pair(3))
+
+ def print_list(self, lst, firstline = None):
+ lst = list(lst)
+ self.maxpos = len(lst)
+ if not self.maxpos: return
+ if firstline:
+ firstline += " "*(self.maxx -2 - len(firstline))
+ self.stdscr.addstr( 1, 1, firstline )
+ for i in range(self.maxy-4):
+ msg = lst[i] if i < len(lst) else ""
+ msg += " "*(self.maxx - 2 - len(msg))
+ m = msg[0:self.maxx - 2]
+ m = m.encode(self.encoding)
+ self.stdscr.addstr( i+2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0)
+
+ def refresh(self):
+ if self.tab == -1: return
+ self.stdscr.border(0)
+ self.print_balance()
+ self.stdscr.refresh()
+
+ def main_command(self):
+ c = self.stdscr.getch()
+ print(c)
+ cc = curses.unctrl(c).decode()
+ if c == curses.KEY_RIGHT: self.tab = (self.tab + 1)%self.num_tabs
+ elif c == curses.KEY_LEFT: self.tab = (self.tab - 1)%self.num_tabs
+ elif c == curses.KEY_DOWN: self.pos +=1
+ elif c == curses.KEY_UP: self.pos -= 1
+ elif c == 9: self.pos +=1 # tab
+ elif cc in ['^W', '^C', '^X', '^Q']: self.tab = -1
+ elif cc in ['^N']: self.network_dialog()
+ elif cc == '^S': self.settings_dialog()
+ else: return c
+ if self.pos<0: self.pos=0
+ if self.pos>=self.maxpos: self.pos=self.maxpos - 1
+
+ def run_tab(self, i, print_func, exec_func):
+ while self.tab == i:
+ self.stdscr.clear()
+ print_func()
+ self.refresh()
+ c = self.main_command()
+ if c: exec_func(c)
+
+
+ def run_history_tab(self, c):
+ if c == 10:
+ out = self.run_popup('',["blah","foo"])
+
+
+ def edit_str(self, target, c, is_num=False):
+ # detect backspace
+ cc = curses.unctrl(c).decode()
+ if c in [8, 127, 263] and target:
+ target = target[:-1]
+ elif not is_num or cc in '0123456789.':
+ target += cc
+ return target
+
+
+ def run_send_tab(self, c):
+ if self.pos%6 == 0:
+ self.str_recipient = self.edit_str(self.str_recipient, c)
+ if self.pos%6 == 1:
+ self.str_description = self.edit_str(self.str_description, c)
+ if self.pos%6 == 2:
+ self.str_amount = self.edit_str(self.str_amount, c, True)
+ elif self.pos%6 == 3:
+ self.str_fee = self.edit_str(self.str_fee, c, True)
+ elif self.pos%6==4:
+ if c == 10: self.do_send()
+ elif self.pos%6==5:
+ if c == 10: self.do_clear()
+
+
+ def run_receive_tab(self, c):
+ if c == 10:
+ out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"])
+
+ def run_contacts_tab(self, c):
+ if c == 10 and self.contacts:
+ out = self.run_popup('Address', ["Copy", "Pay to", "Edit label", "Delete"]).get('button')
+ key = list(self.contacts.keys())[self.pos%len(self.contacts.keys())]
+ if out == "Pay to":
+ self.tab = 1
+ self.str_recipient = key
+ self.pos = 2
+ elif out == "Edit label":
+ s = self.get_string(6 + self.pos, 18)
+ if s:
+ self.wallet.labels[key] = s
+
+ def run_banner_tab(self, c):
+ self.show_message(repr(c))
+ pass
+
+ def main(self):
+
+ tty.setraw(sys.stdin)
+ while self.tab != -1:
+ self.run_tab(0, self.print_history, self.run_history_tab)
+ self.run_tab(1, self.print_send_tab, self.run_send_tab)
+ self.run_tab(2, self.print_receive, self.run_receive_tab)
+ self.run_tab(3, self.print_addresses, self.run_banner_tab)
+ self.run_tab(4, self.print_contacts, self.run_contacts_tab)
+ self.run_tab(5, self.print_banner, self.run_banner_tab)
+
+ tty.setcbreak(sys.stdin)
+ curses.nocbreak()
+ self.stdscr.keypad(0)
+ curses.echo()
+ curses.endwin()
+
+
+ def do_clear(self):
+ self.str_amount = ''
+ self.str_recipient = ''
+ self.str_fee = ''
+ self.str_description = ''
+
+ def do_send(self):
+ if not is_address(self.str_recipient):
+ self.show_message(_('Invalid Bitcore address'))
+ return
+ try:
+ amount = int(Decimal(self.str_amount) * COIN)
+ except Exception:
+ self.show_message(_('Invalid Amount'))
+ return
+ try:
+ fee = int(Decimal(self.str_fee) * COIN)
+ except Exception:
+ self.show_message(_('Invalid Fee'))
+ return
+
+ if self.wallet.has_password():
+ password = self.password_dialog()
+ if not password:
+ return
+ else:
+ password = None
+ try:
+ tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
+ password, self.config, fee)
+ except Exception as e:
+ self.show_message(str(e))
+ return
+
+ if self.str_description:
+ self.wallet.labels[tx.txid()] = self.str_description
+
+ self.show_message(_("Please wait..."), getchar=False)
+ status, msg = self.network.broadcast_transaction(tx)
+
+ if status:
+ self.show_message(_('Payment sent.'))
+ self.do_clear()
+ #self.update_contacts_tab()
+ else:
+ self.show_message(_('Error'))
+
+
+ def show_message(self, message, getchar = True):
+ w = self.w
+ w.clear()
+ w.border(0)
+ for i, line in enumerate(message.split('\n')):
+ w.addstr(2+i,2,line)
+ w.refresh()
+ if getchar: c = self.stdscr.getch()
+
+ def run_popup(self, title, items):
+ return self.run_dialog(title, list(map(lambda x: {'type':'button','label':x}, items)), interval=1, y_pos = self.pos+3)
+
+ def network_dialog(self):
+ if not self.network:
+ return
+ params = self.network.get_parameters()
+ host, port, protocol, proxy_config, auto_connect = params
+ srv = 'auto-connect' if auto_connect else self.network.default_server
+ out = self.run_dialog('Network', [
+ {'label':'server', 'type':'str', 'value':srv},
+ {'label':'proxy', 'type':'str', 'value':self.config.get('proxy', '')},
+ ], buttons = 1)
+ if out:
+ if out.get('server'):
+ server = out.get('server')
+ auto_connect = server == 'auto-connect'
+ if not auto_connect:
+ try:
+ host, port, protocol = server.split(':')
+ except Exception:
+ self.show_message("Error:" + server + "\nIn doubt, type \"auto-connect\"")
+ return False
+ if out.get('server') or out.get('proxy'):
+ proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config
+ self.network.set_parameters(host, port, protocol, proxy, auto_connect)
+
+ def settings_dialog(self):
+ fee = str(Decimal(self.config.fee_per_kb()) / COIN)
+ out = self.run_dialog('Settings', [
+ {'label':'Default fee', 'type':'satoshis', 'value': fee }
+ ], buttons = 1)
+ if out:
+ if out.get('Default fee'):
+ fee = int(Decimal(out['Default fee']) * COIN)
+ self.config.set_key('fee_per_kb', fee, True)
+
+
+ def password_dialog(self):
+ out = self.run_dialog('Password', [
+ {'label':'Password', 'type':'password', 'value':''}
+ ], buttons = 1)
+ return out.get('Password')
+
+
+ def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3):
+ self.popup_pos = 0
+
+ self.w = curses.newwin( 5 + len(list(items))*interval + (2 if buttons else 0), 50, y_pos, 5)
+ w = self.w
+ out = {}
+ while True:
+ w.clear()
+ w.border(0)
+ w.addstr( 0, 2, title)
+
+ num = len(list(items))
+
+ numpos = num
+ if buttons: numpos += 2
+
+ for i in range(num):
+ item = items[i]
+ label = item.get('label')
+ if item.get('type') == 'list':
+ value = item.get('value','')
+ elif item.get('type') == 'satoshis':
+ value = item.get('value','')
+ elif item.get('type') == 'str':
+ value = item.get('value','')
+ elif item.get('type') == 'password':
+ value = '*'*len(item.get('value',''))
+ else:
+ value = ''
+ if value is None:
+ value = ''
+ if len(value)<20:
+ value += ' '*(20-len(value))
+
+ if 'value' in item:
+ w.addstr( 2+interval*i, 2, label)
+ w.addstr( 2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1) )
+ else:
+ w.addstr( 2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0)
+
+ if buttons:
+ w.addstr( 5+interval*i, 10, "[ ok ]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2))
+ w.addstr( 5+interval*i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2))
+
+ w.refresh()
+
+ c = self.stdscr.getch()
+ if c in [ord('q'), 27]: break
+ elif c in [curses.KEY_LEFT, curses.KEY_UP]: self.popup_pos -= 1
+ elif c in [curses.KEY_RIGHT, curses.KEY_DOWN]: self.popup_pos +=1
+ else:
+ i = self.popup_pos%numpos
+ if buttons and c==10:
+ if i == numpos-2:
+ return out
+ elif i == numpos -1:
+ return {}
+
+ item = items[i]
+ _type = item.get('type')
+
+ if _type == 'str':
+ item['value'] = self.edit_str(item['value'], c)
+ out[item.get('label')] = item.get('value')
+
+ elif _type == 'password':
+ item['value'] = self.edit_str(item['value'], c)
+ out[item.get('label')] = item ['value']
+
+ elif _type == 'satoshis':
+ item['value'] = self.edit_str(item['value'], c, True)
+ out[item.get('label')] = item.get('value')
+
+ elif _type == 'list':
+ choices = item.get('choices')
+ try:
+ j = choices.index(item.get('value'))
+ except Exception:
+ j = 0
+ new_choice = choices[(j + 1)% len(choices)]
+ item['value'] = new_choice
+ out[item.get('label')] = item.get('value')
+
+ elif _type == 'button':
+ out['button'] = item.get('label')
+ break
+
+ return out
diff --git a/electrum/i18n.py b/electrum/i18n.py
new file mode 100644
index 000000000..9c6fad995
--- /dev/null
+++ b/electrum/i18n.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2012 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+
+import gettext
+
+LOCALE_DIR = os.path.join(os.path.dirname(__file__), 'locale')
+language = gettext.translation('electrum', LOCALE_DIR, fallback=True)
+
+
+def _(x):
+ global language
+ return language.gettext(x)
+
+
+def set_language(x):
+ global language
+ if x:
+ language = gettext.translation('electrum', LOCALE_DIR, fallback=True, languages=[x])
+
+
+languages = {
+ '': _('Default'),
+ 'ar_SA': _('Arabic'),
+ 'bg_BG': _('Bulgarian'),
+ 'cs_CZ': _('Czech'),
+ 'da_DK': _('Danish'),
+ 'de_DE': _('German'),
+ 'el_GR': _('Greek'),
+ 'eo_UY': _('Esperanto'),
+ 'en_UK': _('English'),
+ 'es_ES': _('Spanish'),
+ 'fa_IR': _('Persian'),
+ 'fr_FR': _('French'),
+ 'hu_HU': _('Hungarian'),
+ 'hy_AM': _('Armenian'),
+ 'id_ID': _('Indonesian'),
+ 'it_IT': _('Italian'),
+ 'ja_JP': _('Japanese'),
+ 'ky_KG': _('Kyrgyz'),
+ 'lv_LV': _('Latvian'),
+ 'nb_NO': _('Norwegian Bokmal'),
+ 'nl_NL': _('Dutch'),
+ 'pl_PL': _('Polish'),
+ 'pt_BR': _('Brasilian'),
+ 'pt_PT': _('Portuguese'),
+ 'ro_RO': _('Romanian'),
+ 'ru_RU': _('Russian'),
+ 'sk_SK': _('Slovak'),
+ 'sl_SI': _('Slovenian'),
+ 'sv_SE': _('Swedish'),
+ 'ta_IN': _('Tamil'),
+ 'th_TH': _('Thai'),
+ 'tr_TR': _('Turkish'),
+ 'uk_UA': _('Ukrainian'),
+ 'vi_VN': _('Vietnamese'),
+ 'zh_CN': _('Chinese Simplified'),
+ 'zh_TW': _('Chinese Traditional')
+}
diff --git a/electrum/interface.py b/electrum/interface.py
new file mode 100644
index 000000000..54fedd426
--- /dev/null
+++ b/electrum/interface.py
@@ -0,0 +1,410 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+import re
+import socket
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+
+import sys
+import threading
+import time
+import traceback
+
+import requests
+
+from .util import print_error
+
+ca_path = requests.certs.where()
+
+from . import util
+from . import x509
+from . import pem
+
+
+def Connection(server, queue, config_path):
+ """Makes asynchronous connections to a remote Electrum server.
+ Returns the running thread that is making the connection.
+
+ Once the thread has connected, it finishes, placing a tuple on the
+ queue of the form (server, socket), where socket is None if
+ connection failed.
+ """
+ host, port, protocol = server.rsplit(':', 2)
+ if not protocol in 'st':
+ raise Exception('Unknown protocol: %s' % protocol)
+ c = TcpConnection(server, queue, config_path)
+ c.start()
+ return c
+
+
+class TcpConnection(threading.Thread, util.PrintError):
+ verbosity_filter = 'i'
+
+ def __init__(self, server, queue, config_path):
+ threading.Thread.__init__(self)
+ self.config_path = config_path
+ self.queue = queue
+ self.server = server
+ self.host, self.port, self.protocol = self.server.rsplit(':', 2)
+ self.host = str(self.host)
+ self.port = int(self.port)
+ self.use_ssl = (self.protocol == 's')
+ self.daemon = True
+
+ def diagnostic_name(self):
+ return self.host
+
+ def check_host_name(self, peercert, name):
+ """Simple certificate/host name checker. Returns True if the
+ certificate matches, False otherwise. Does not support
+ wildcards."""
+ # Check that the peer has supplied a certificate.
+ # None/{} is not acceptable.
+ if not peercert:
+ return False
+ if 'subjectAltName' in peercert:
+ for typ, val in peercert["subjectAltName"]:
+ if typ == "DNS" and val == name:
+ return True
+ else:
+ # Only check the subject DN if there is no subject alternative
+ # name.
+ cn = None
+ for attr, val in peercert["subject"]:
+ # Use most-specific (last) commonName attribute.
+ if attr == "commonName":
+ cn = val
+ if cn is not None:
+ return cn == name
+ return False
+
+ def get_simple_socket(self):
+ try:
+ l = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+ except socket.gaierror:
+ self.print_error("cannot resolve hostname")
+ return
+ e = None
+ for res in l:
+ try:
+ s = socket.socket(res[0], socket.SOCK_STREAM)
+ s.settimeout(10)
+ s.connect(res[4])
+ s.settimeout(2)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ return s
+ except BaseException as _e:
+ e = _e
+ continue
+ else:
+ self.print_error("failed to connect", str(e))
+
+ @staticmethod
+ def get_ssl_context(cert_reqs, ca_certs):
+ context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_certs)
+ context.check_hostname = False
+ context.verify_mode = cert_reqs
+
+ context.options |= ssl.OP_NO_SSLv2
+ context.options |= ssl.OP_NO_SSLv3
+ context.options |= ssl.OP_NO_TLSv1
+
+ return context
+
+ def get_socket(self):
+ if self.use_ssl:
+ cert_path = os.path.join(self.config_path, 'certs', self.host)
+ if not os.path.exists(cert_path):
+ is_new = True
+ s = self.get_simple_socket()
+ if s is None:
+ return
+ # try with CA first
+ try:
+ context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path)
+ s = context.wrap_socket(s, do_handshake_on_connect=True)
+ except ssl.SSLError as e:
+ self.print_error(e)
+ except:
+ return
+ else:
+ try:
+ peer_cert = s.getpeercert()
+ except OSError:
+ return
+ if self.check_host_name(peer_cert, self.host):
+ self.print_error("SSL certificate signed by CA")
+ return s
+ # get server certificate.
+ # Do not use ssl.get_server_certificate because it does not work with proxy
+ s = self.get_simple_socket()
+ if s is None:
+ return
+ try:
+ context = self.get_ssl_context(cert_reqs=ssl.CERT_NONE, ca_certs=None)
+ s = context.wrap_socket(s)
+ except ssl.SSLError as e:
+ self.print_error("SSL error retrieving SSL certificate:", e)
+ return
+ except:
+ return
+
+ try:
+ dercert = s.getpeercert(True)
+ except OSError:
+ return
+ s.close()
+ cert = ssl.DER_cert_to_PEM_cert(dercert)
+ # workaround android bug
+ cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert)
+ temporary_path = cert_path + '.temp'
+ util.assert_datadir_available(self.config_path)
+ with open(temporary_path, "w", encoding='utf-8') as f:
+ f.write(cert)
+ f.flush()
+ os.fsync(f.fileno())
+ else:
+ is_new = False
+
+ s = self.get_simple_socket()
+ if s is None:
+ return
+
+ if self.use_ssl:
+ try:
+ context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED,
+ ca_certs=(temporary_path if is_new else cert_path))
+ s = context.wrap_socket(s, do_handshake_on_connect=True)
+ except socket.timeout:
+ self.print_error('timeout')
+ return
+ except ssl.SSLError as e:
+ self.print_error("SSL error:", e)
+ if e.errno != 1:
+ return
+ if is_new:
+ rej = cert_path + '.rej'
+ if os.path.exists(rej):
+ os.unlink(rej)
+ os.rename(temporary_path, rej)
+ else:
+ util.assert_datadir_available(self.config_path)
+ with open(cert_path, encoding='utf-8') as f:
+ cert = f.read()
+ try:
+ b = pem.dePem(cert, 'CERTIFICATE')
+ x = x509.X509(b)
+ except:
+ traceback.print_exc(file=sys.stderr)
+ self.print_error("wrong certificate")
+ return
+ try:
+ x.check_date()
+ except:
+ self.print_error("certificate has expired:", cert_path)
+ os.unlink(cert_path)
+ return
+ self.print_error("wrong certificate")
+ if e.errno == 104:
+ return
+ return
+ except BaseException as e:
+ self.print_error(e)
+ traceback.print_exc(file=sys.stderr)
+ return
+
+ if is_new:
+ self.print_error("saving certificate")
+ os.rename(temporary_path, cert_path)
+
+ return s
+
+ def run(self):
+ socket = self.get_socket()
+ if socket:
+ self.print_error("connected")
+ self.queue.put((self.server, socket))
+
+
+class Interface(util.PrintError):
+ """The Interface class handles a socket connected to a single remote
+ Electrum server. Its exposed API is:
+
+ - Member functions close(), fileno(), get_responses(), has_timed_out(),
+ ping_required(), queue_request(), send_requests()
+ - Member variable server.
+ """
+
+ def __init__(self, server, socket):
+ self.server = server
+ self.host, _, _ = server.rsplit(':', 2)
+ self.socket = socket
+
+ self.pipe = util.SocketPipe(socket)
+ self.pipe.set_timeout(0.0) # Don't wait for data
+ # Dump network messages. Set at runtime from the console.
+ self.debug = False
+ self.unsent_requests = []
+ self.unanswered_requests = {}
+ self.last_send = time.time()
+ self.closed_remotely = False
+
+ def diagnostic_name(self):
+ return self.host
+
+ def fileno(self):
+ # Needed for select
+ return self.socket.fileno()
+
+ def close(self):
+ if not self.closed_remotely:
+ try:
+ self.socket.shutdown(socket.SHUT_RDWR)
+ except socket.error:
+ pass
+ self.socket.close()
+
+ def queue_request(self, *args): # method, params, _id
+ '''Queue a request, later to be send with send_requests when the
+ socket is available for writing.
+ '''
+ self.request_time = time.time()
+ self.unsent_requests.append(args)
+
+ def num_requests(self):
+ '''Keep unanswered requests below 100'''
+ n = 100 - len(self.unanswered_requests)
+ return min(n, len(self.unsent_requests))
+
+ def send_requests(self):
+ '''Sends queued requests. Returns False on failure.'''
+ self.last_send = time.time()
+ make_dict = lambda m, p, i: {'method': m, 'params': p, 'id': i}
+ n = self.num_requests()
+ wire_requests = self.unsent_requests[0:n]
+ try:
+ self.pipe.send_all([make_dict(*r) for r in wire_requests])
+ except BaseException as e:
+ self.print_error("pipe send error:", e)
+ return False
+ self.unsent_requests = self.unsent_requests[n:]
+ for request in wire_requests:
+ if self.debug:
+ self.print_error("-->", request)
+ self.unanswered_requests[request[2]] = request
+ return True
+
+ def ping_required(self):
+ '''Returns True if a ping should be sent.'''
+ return time.time() - self.last_send > 300
+
+ def has_timed_out(self):
+ '''Returns True if the interface has timed out.'''
+ if (self.unanswered_requests and time.time() - self.request_time > 10
+ and self.pipe.idle_time() > 10):
+ self.print_error("timeout", len(self.unanswered_requests))
+ return True
+
+ return False
+
+ def get_responses(self):
+ '''Call if there is data available on the socket. Returns a list of
+ (request, response) pairs. Notifications are singleton
+ unsolicited responses presumably as a result of prior
+ subscriptions, so request is None and there is no 'id' member.
+ Otherwise it is a response, which has an 'id' member and a
+ corresponding request. If the connection was closed remotely
+ or the remote server is misbehaving, a (None, None) will appear.
+ '''
+ responses = []
+ while True:
+ try:
+ response = self.pipe.get()
+ except util.timeout:
+ break
+ if not type(response) is dict:
+ responses.append((None, None))
+ if response is None:
+ self.closed_remotely = True
+ self.print_error("connection closed remotely")
+ break
+ if self.debug:
+ self.print_error("<--", response)
+ wire_id = response.get('id', None)
+ if wire_id is None: # Notification
+ responses.append((None, response))
+ else:
+ request = self.unanswered_requests.pop(wire_id, None)
+ if request:
+ responses.append((request, response))
+ else:
+ self.print_error("unknown wire ID", wire_id)
+ responses.append((None, None)) # Signal
+ break
+
+ return responses
+
+
+def check_cert(host, cert):
+ try:
+ b = pem.dePem(cert, 'CERTIFICATE')
+ x = x509.X509(b)
+ except:
+ traceback.print_exc(file=sys.stdout)
+ return
+
+ try:
+ x.check_date()
+ expired = False
+ except:
+ expired = True
+
+ m = "host: %s\n"%host
+ m += "has_expired: %s\n"% expired
+ util.print_msg(m)
+
+
+# Used by tests
+def _match_hostname(name, val):
+ if val == name:
+ return True
+
+ return val.startswith('*.') and name.endswith(val[1:])
+
+
+def test_certificates():
+ from .simple_config import SimpleConfig
+ config = SimpleConfig()
+ mydir = os.path.join(config.path, "certs")
+ certs = os.listdir(mydir)
+ for c in certs:
+ p = os.path.join(mydir,c)
+ with open(p, encoding='utf-8') as f:
+ cert = f.read()
+ check_cert(c, cert)
+
+if __name__ == "__main__":
+ test_certificates()
diff --git a/electrum/jsonrpc.py b/electrum/jsonrpc.py
new file mode 100644
index 000000000..200b6e86e
--- /dev/null
+++ b/electrum/jsonrpc.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2018 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
+from base64 import b64decode
+import time
+
+from . import util
+
+
+class RPCAuthCredentialsInvalid(Exception):
+ def __str__(self):
+ return 'Authentication failed (bad credentials)'
+
+
+class RPCAuthCredentialsMissing(Exception):
+ def __str__(self):
+ return 'Authentication failed (missing credentials)'
+
+
+class RPCAuthUnsupportedType(Exception):
+ def __str__(self):
+ return 'Authentication failed (only basic auth is supported)'
+
+
+# based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke
+class VerifyingJSONRPCServer(SimpleJSONRPCServer):
+
+ def __init__(self, *args, rpc_user, rpc_password, **kargs):
+
+ self.rpc_user = rpc_user
+ self.rpc_password = rpc_password
+
+ class VerifyingRequestHandler(SimpleJSONRPCRequestHandler):
+ def parse_request(myself):
+ # first, call the original implementation which returns
+ # True if all OK so far
+ if SimpleJSONRPCRequestHandler.parse_request(myself):
+ # Do not authenticate OPTIONS-requests
+ if myself.command.strip() == 'OPTIONS':
+ return True
+ try:
+ self.authenticate(myself.headers)
+ return True
+ except (RPCAuthCredentialsInvalid, RPCAuthCredentialsMissing,
+ RPCAuthUnsupportedType) as e:
+ myself.send_error(401, str(e))
+ except BaseException as e:
+ import traceback, sys
+ traceback.print_exc(file=sys.stderr)
+ myself.send_error(500, str(e))
+ return False
+
+ SimpleJSONRPCServer.__init__(
+ self, requestHandler=VerifyingRequestHandler, *args, **kargs)
+
+ def authenticate(self, headers):
+ if self.rpc_password == '':
+ # RPC authentication is disabled
+ return
+
+ auth_string = headers.get('Authorization', None)
+ if auth_string is None:
+ raise RPCAuthCredentialsMissing()
+
+ (basic, _, encoded) = auth_string.partition(' ')
+ if basic != 'Basic':
+ raise RPCAuthUnsupportedType()
+
+ encoded = util.to_bytes(encoded, 'utf8')
+ credentials = util.to_string(b64decode(encoded), 'utf8')
+ (username, _, password) = credentials.partition(':')
+ if not (util.constant_time_compare(username, self.rpc_user)
+ and util.constant_time_compare(password, self.rpc_password)):
+ time.sleep(0.050)
+ raise RPCAuthCredentialsInvalid()
diff --git a/electrum/keystore.py b/electrum/keystore.py
new file mode 100644
index 000000000..c822be13e
--- /dev/null
+++ b/electrum/keystore.py
@@ -0,0 +1,797 @@
+#!/usr/bin/env python2
+# -*- mode: python -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2016 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from unicodedata import normalize
+
+from . import bitcoin, ecc, constants
+from .bitcoin import *
+from .ecc import string_to_number, number_to_string
+from .crypto import pw_decode, pw_encode
+from .util import (PrintError, InvalidPassword, hfu, WalletFileException,
+ BitcoinException)
+from .mnemonic import Mnemonic, load_wordlist
+from .plugin import run_hook
+
+
+class KeyStore(PrintError):
+
+ def has_seed(self):
+ return False
+
+ def is_watching_only(self):
+ return False
+
+ def can_import(self):
+ return False
+
+ def may_have_password(self):
+ """Returns whether the keystore can be encrypted with a password."""
+ raise NotImplementedError()
+
+ def get_tx_derivations(self, tx):
+ keypairs = {}
+ for txin in tx.inputs():
+ num_sig = txin.get('num_sig')
+ if num_sig is None:
+ continue
+ x_signatures = txin['signatures']
+ signatures = [sig for sig in x_signatures if sig]
+ if len(signatures) == num_sig:
+ # input is complete
+ continue
+ for k, x_pubkey in enumerate(txin['x_pubkeys']):
+ if x_signatures[k] is not None:
+ # this pubkey already signed
+ continue
+ derivation = self.get_pubkey_derivation(x_pubkey)
+ if not derivation:
+ continue
+ keypairs[x_pubkey] = derivation
+ return keypairs
+
+ def can_sign(self, tx):
+ if self.is_watching_only():
+ return False
+ return bool(self.get_tx_derivations(tx))
+
+ def ready_to_sign(self):
+ return not self.is_watching_only()
+
+
+class Software_KeyStore(KeyStore):
+
+ def __init__(self):
+ KeyStore.__init__(self)
+
+ def may_have_password(self):
+ return not self.is_watching_only()
+
+ def sign_message(self, sequence, message, password):
+ privkey, compressed = self.get_private_key(sequence, password)
+ key = ecc.ECPrivkey(privkey)
+ return key.sign_message(message, compressed)
+
+ def decrypt_message(self, sequence, message, password):
+ privkey, compressed = self.get_private_key(sequence, password)
+ ec = ecc.ECPrivkey(privkey)
+ decrypted = ec.decrypt_message(message)
+ return decrypted
+
+ def sign_transaction(self, tx, password):
+ if self.is_watching_only():
+ return
+ # Raise if password is not correct.
+ self.check_password(password)
+ # Add private keys
+ keypairs = self.get_tx_derivations(tx)
+ for k, v in keypairs.items():
+ keypairs[k] = self.get_private_key(v, password)
+ # Sign
+ if keypairs:
+ tx.sign(keypairs)
+
+
+class Imported_KeyStore(Software_KeyStore):
+ # keystore for imported private keys
+
+ def __init__(self, d):
+ Software_KeyStore.__init__(self)
+ self.keypairs = d.get('keypairs', {})
+
+ def is_deterministic(self):
+ return False
+
+ def get_master_public_key(self):
+ return None
+
+ def dump(self):
+ return {
+ 'type': 'imported',
+ 'keypairs': self.keypairs,
+ }
+
+ def can_import(self):
+ return True
+
+ def check_password(self, password):
+ pubkey = list(self.keypairs.keys())[0]
+ self.get_private_key(pubkey, password)
+
+ def import_privkey(self, sec, password):
+ txin_type, privkey, compressed = deserialize_privkey(sec)
+ pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
+ # re-serialize the key so the internal storage format is consistent
+ serialized_privkey = serialize_privkey(
+ privkey, compressed, txin_type, internal_use=True)
+ # NOTE: if the same pubkey is reused for multiple addresses (script types),
+ # there will only be one pubkey-privkey pair for it in self.keypairs,
+ # and the privkey will encode a txin_type but that txin_type cannot be trusted.
+ # Removing keys complicates this further.
+ self.keypairs[pubkey] = pw_encode(serialized_privkey, password)
+ return txin_type, pubkey
+
+ def delete_imported_key(self, key):
+ self.keypairs.pop(key)
+
+ def get_private_key(self, pubkey, password):
+ sec = pw_decode(self.keypairs[pubkey], password)
+ txin_type, privkey, compressed = deserialize_privkey(sec)
+ # this checks the password
+ if pubkey != ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed):
+ raise InvalidPassword()
+ return privkey, compressed
+
+ def get_pubkey_derivation(self, x_pubkey):
+ if x_pubkey[0:2] in ['02', '03', '04']:
+ if x_pubkey in self.keypairs.keys():
+ return x_pubkey
+ elif x_pubkey[0:2] == 'fd':
+ addr = bitcoin.script_to_address(x_pubkey[2:])
+ if addr in self.addresses:
+ return self.addresses[addr].get('pubkey')
+
+ def update_password(self, old_password, new_password):
+ self.check_password(old_password)
+ if new_password == '':
+ new_password = None
+ for k, v in self.keypairs.items():
+ b = pw_decode(v, old_password)
+ c = pw_encode(b, new_password)
+ self.keypairs[k] = c
+
+
+
+class Deterministic_KeyStore(Software_KeyStore):
+
+ def __init__(self, d):
+ Software_KeyStore.__init__(self)
+ self.seed = d.get('seed', '')
+ self.passphrase = d.get('passphrase', '')
+
+ def is_deterministic(self):
+ return True
+
+ def dump(self):
+ d = {}
+ if self.seed:
+ d['seed'] = self.seed
+ if self.passphrase:
+ d['passphrase'] = self.passphrase
+ return d
+
+ def has_seed(self):
+ return bool(self.seed)
+
+ def is_watching_only(self):
+ return not self.has_seed()
+
+ def add_seed(self, seed):
+ if self.seed:
+ raise Exception("a seed exists")
+ self.seed = self.format_seed(seed)
+
+ def get_seed(self, password):
+ return pw_decode(self.seed, password)
+
+ def get_passphrase(self, password):
+ return pw_decode(self.passphrase, password) if self.passphrase else ''
+
+
+class Xpub:
+
+ def __init__(self):
+ self.xpub = None
+ self.xpub_receive = None
+ self.xpub_change = None
+
+ def get_master_public_key(self):
+ return self.xpub
+
+ def derive_pubkey(self, for_change, n):
+ xpub = self.xpub_change if for_change else self.xpub_receive
+ if xpub is None:
+ xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
+ if for_change:
+ self.xpub_change = xpub
+ else:
+ self.xpub_receive = xpub
+ return self.get_pubkey_from_xpub(xpub, (n,))
+
+ @classmethod
+ def get_pubkey_from_xpub(self, xpub, sequence):
+ _, _, _, _, c, cK = deserialize_xpub(xpub)
+ for i in sequence:
+ cK, c = CKD_pub(cK, c, i)
+ return bh2u(cK)
+
+ def get_xpubkey(self, c, i):
+ s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
+ return 'ff' + bh2u(bitcoin.DecodeBase58Check(self.xpub)) + s
+
+ @classmethod
+ def parse_xpubkey(self, pubkey):
+ assert pubkey[0:2] == 'ff'
+ pk = bfh(pubkey)
+ pk = pk[1:]
+ xkey = bitcoin.EncodeBase58Check(pk[0:78])
+ dd = pk[78:]
+ s = []
+ while dd:
+ n = int(bitcoin.rev_hex(bh2u(dd[0:2])), 16)
+ dd = dd[2:]
+ s.append(n)
+ assert len(s) == 2
+ return xkey, s
+
+ def get_pubkey_derivation(self, x_pubkey):
+ if x_pubkey[0:2] != 'ff':
+ return
+ xpub, derivation = self.parse_xpubkey(x_pubkey)
+ if self.xpub != xpub:
+ return
+ return derivation
+
+
+class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
+
+ def __init__(self, d):
+ Xpub.__init__(self)
+ Deterministic_KeyStore.__init__(self, d)
+ self.xpub = d.get('xpub')
+ self.xprv = d.get('xprv')
+
+ def format_seed(self, seed):
+ return ' '.join(seed.split())
+
+ def dump(self):
+ d = Deterministic_KeyStore.dump(self)
+ d['type'] = 'bip32'
+ d['xpub'] = self.xpub
+ d['xprv'] = self.xprv
+ return d
+
+ def get_master_private_key(self, password):
+ return pw_decode(self.xprv, password)
+
+ def check_password(self, password):
+ xprv = pw_decode(self.xprv, password)
+ if deserialize_xprv(xprv)[4] != deserialize_xpub(self.xpub)[4]:
+ raise InvalidPassword()
+
+ def update_password(self, old_password, new_password):
+ self.check_password(old_password)
+ if new_password == '':
+ new_password = None
+ if self.has_seed():
+ decoded = self.get_seed(old_password)
+ self.seed = pw_encode(decoded, new_password)
+ if self.passphrase:
+ decoded = self.get_passphrase(old_password)
+ self.passphrase = pw_encode(decoded, new_password)
+ if self.xprv is not None:
+ b = pw_decode(self.xprv, old_password)
+ self.xprv = pw_encode(b, new_password)
+
+ def is_watching_only(self):
+ return self.xprv is None
+
+ def add_xprv(self, xprv):
+ self.xprv = xprv
+ self.xpub = bitcoin.xpub_from_xprv(xprv)
+
+ def add_xprv_from_seed(self, bip32_seed, xtype, derivation):
+ xprv, xpub = bip32_root(bip32_seed, xtype)
+ xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
+ self.add_xprv(xprv)
+
+ def get_private_key(self, sequence, password):
+ xprv = self.get_master_private_key(password)
+ _, _, _, _, c, k = deserialize_xprv(xprv)
+ pk = bip32_private_key(sequence, k, c)
+ return pk, True
+
+
+
+class Old_KeyStore(Deterministic_KeyStore):
+
+ def __init__(self, d):
+ Deterministic_KeyStore.__init__(self, d)
+ self.mpk = d.get('mpk')
+
+ def get_hex_seed(self, password):
+ return pw_decode(self.seed, password).encode('utf8')
+
+ def dump(self):
+ d = Deterministic_KeyStore.dump(self)
+ d['mpk'] = self.mpk
+ d['type'] = 'old'
+ return d
+
+ def add_seed(self, seedphrase):
+ Deterministic_KeyStore.add_seed(self, seedphrase)
+ s = self.get_hex_seed(None)
+ self.mpk = self.mpk_from_seed(s)
+
+ def add_master_public_key(self, mpk):
+ self.mpk = mpk
+
+ def format_seed(self, seed):
+ from . import old_mnemonic, mnemonic
+ seed = mnemonic.normalize_text(seed)
+ # see if seed was entered as hex
+ if seed:
+ try:
+ bfh(seed)
+ return str(seed)
+ except Exception:
+ pass
+ words = seed.split()
+ seed = old_mnemonic.mn_decode(words)
+ if not seed:
+ raise Exception("Invalid seed")
+ return seed
+
+ def get_seed(self, password):
+ from . import old_mnemonic
+ s = self.get_hex_seed(password)
+ return ' '.join(old_mnemonic.mn_encode(s))
+
+ @classmethod
+ def mpk_from_seed(klass, seed):
+ secexp = klass.stretch_key(seed)
+ privkey = ecc.ECPrivkey.from_secret_scalar(secexp)
+ return privkey.get_public_key_hex(compressed=False)[2:]
+
+ @classmethod
+ def stretch_key(self, seed):
+ x = seed
+ for i in range(100000):
+ x = hashlib.sha256(x + seed).digest()
+ return string_to_number(x)
+
+ @classmethod
+ def get_sequence(self, mpk, for_change, n):
+ return string_to_number(Hash(("%d:%d:"%(n, for_change)).encode('ascii') + bfh(mpk)))
+
+ @classmethod
+ def get_pubkey_from_mpk(self, mpk, for_change, n):
+ z = self.get_sequence(mpk, for_change, n)
+ master_public_key = ecc.ECPubkey(bfh('04'+mpk))
+ public_key = master_public_key + z*ecc.generator()
+ return public_key.get_public_key_hex(compressed=False)
+
+ def derive_pubkey(self, for_change, n):
+ return self.get_pubkey_from_mpk(self.mpk, for_change, n)
+
+ def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
+ secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % ecc.CURVE_ORDER
+ pk = number_to_string(secexp, ecc.CURVE_ORDER)
+ return pk
+
+ def get_private_key(self, sequence, password):
+ seed = self.get_hex_seed(password)
+ self.check_seed(seed)
+ for_change, n = sequence
+ secexp = self.stretch_key(seed)
+ pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
+ return pk, False
+
+ def check_seed(self, seed):
+ secexp = self.stretch_key(seed)
+ master_private_key = ecc.ECPrivkey.from_secret_scalar(secexp)
+ master_public_key = master_private_key.get_public_key_bytes(compressed=False)[1:]
+ if master_public_key != bfh(self.mpk):
+ print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key))
+ raise InvalidPassword()
+
+ def check_password(self, password):
+ seed = self.get_hex_seed(password)
+ self.check_seed(seed)
+
+ def get_master_public_key(self):
+ return self.mpk
+
+ def get_xpubkey(self, for_change, n):
+ s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
+ return 'fe' + self.mpk + s
+
+ @classmethod
+ def parse_xpubkey(self, x_pubkey):
+ assert x_pubkey[0:2] == 'fe'
+ pk = x_pubkey[2:]
+ mpk = pk[0:128]
+ dd = pk[128:]
+ s = []
+ while dd:
+ n = int(bitcoin.rev_hex(dd[0:4]), 16)
+ dd = dd[4:]
+ s.append(n)
+ assert len(s) == 2
+ return mpk, s
+
+ def get_pubkey_derivation(self, x_pubkey):
+ if x_pubkey[0:2] != 'fe':
+ return
+ mpk, derivation = self.parse_xpubkey(x_pubkey)
+ if self.mpk != mpk:
+ return
+ return derivation
+
+ def update_password(self, old_password, new_password):
+ self.check_password(old_password)
+ if new_password == '':
+ new_password = None
+ if self.has_seed():
+ decoded = pw_decode(self.seed, old_password)
+ self.seed = pw_encode(decoded, new_password)
+
+
+
+class Hardware_KeyStore(KeyStore, Xpub):
+ # Derived classes must set:
+ # - device
+ # - DEVICE_IDS
+ # - wallet_type
+
+ #restore_wallet_class = BIP32_RD_Wallet
+ max_change_outputs = 1
+
+ def __init__(self, d):
+ Xpub.__init__(self)
+ KeyStore.__init__(self)
+ # Errors and other user interaction is done through the wallet's
+ # handler. The handler is per-window and preserved across
+ # device reconnects
+ self.xpub = d.get('xpub')
+ self.label = d.get('label')
+ self.derivation = d.get('derivation')
+ self.handler = None
+ run_hook('init_keystore', self)
+
+ def set_label(self, label):
+ self.label = label
+
+ def may_have_password(self):
+ return False
+
+ def is_deterministic(self):
+ return True
+
+ def dump(self):
+ return {
+ 'type': 'hardware',
+ 'hw_type': self.hw_type,
+ 'xpub': self.xpub,
+ 'derivation':self.derivation,
+ 'label':self.label,
+ }
+
+ def unpaired(self):
+ '''A device paired with the wallet was disconnected. This can be
+ called in any thread context.'''
+ self.print_error("unpaired")
+
+ def paired(self):
+ '''A device paired with the wallet was (re-)connected. This can be
+ called in any thread context.'''
+ self.print_error("paired")
+
+ def can_export(self):
+ return False
+
+ def is_watching_only(self):
+ '''The wallet is not watching-only; the user will be prompted for
+ pin and passphrase as appropriate when needed.'''
+ assert not self.has_seed()
+ return False
+
+ def get_password_for_storage_encryption(self):
+ from .storage import get_derivation_used_for_hw_device_encryption
+ client = self.plugin.get_client(self)
+ derivation = get_derivation_used_for_hw_device_encryption()
+ xpub = client.get_xpub(derivation, "standard")
+ password = self.get_pubkey_from_xpub(xpub, ())
+ return password
+
+ def has_usable_connection_with_device(self):
+ if not hasattr(self, 'plugin'):
+ return False
+ client = self.plugin.get_client(self, force_pair=False)
+ if client is None:
+ return False
+ return client.has_usable_connection_with_device()
+
+ def ready_to_sign(self):
+ return super().ready_to_sign() and self.has_usable_connection_with_device()
+
+
+def bip39_normalize_passphrase(passphrase):
+ return normalize('NFKD', passphrase or '')
+
+def bip39_to_seed(mnemonic, passphrase):
+ import hashlib, hmac
+ PBKDF2_ROUNDS = 2048
+ mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))
+ passphrase = bip39_normalize_passphrase(passphrase)
+ return hashlib.pbkdf2_hmac('sha512', mnemonic.encode('utf-8'),
+ b'mnemonic' + passphrase.encode('utf-8'), iterations = PBKDF2_ROUNDS)
+
+# returns tuple (is_checksum_valid, is_wordlist_valid)
+def bip39_is_checksum_valid(mnemonic):
+ words = [ normalize('NFKD', word) for word in mnemonic.split() ]
+ words_len = len(words)
+ wordlist = load_wordlist("english.txt")
+ n = len(wordlist)
+ checksum_length = 11*words_len//33
+ entropy_length = 32*checksum_length
+ i = 0
+ words.reverse()
+ while words:
+ w = words.pop()
+ try:
+ k = wordlist.index(w)
+ except ValueError:
+ return False, False
+ i = i*n + k
+ if words_len not in [12, 15, 18, 21, 24]:
+ return False, True
+ entropy = i >> checksum_length
+ checksum = i % 2**checksum_length
+ h = '{:x}'.format(entropy)
+ while len(h) < entropy_length/4:
+ h = '0'+h
+ b = bytearray.fromhex(h)
+ hashed = int(hfu(hashlib.sha256(b).digest()), 16)
+ calculated_checksum = hashed >> (256 - checksum_length)
+ return checksum == calculated_checksum, True
+
+
+def from_bip39_seed(seed, passphrase, derivation, xtype=None):
+ k = BIP32_KeyStore({})
+ bip32_seed = bip39_to_seed(seed, passphrase)
+ if xtype is None:
+ xtype = xtype_from_derivation(derivation)
+ k.add_xprv_from_seed(bip32_seed, xtype, derivation)
+ return k
+
+
+def xtype_from_derivation(derivation: str) -> str:
+ """Returns the script type to be used for this derivation."""
+ if derivation.startswith("m/84'"):
+ return 'p2wpkh'
+ elif derivation.startswith("m/49'"):
+ return 'p2wpkh-p2sh'
+ elif derivation.startswith("m/44'"):
+ return 'standard'
+ elif derivation.startswith("m/45'"):
+ return 'standard'
+
+ bip32_indices = list(bip32_derivation(derivation))
+ if len(bip32_indices) >= 4:
+ if bip32_indices[0] == 48 + BIP32_PRIME:
+ # m / purpose' / coin_type' / account' / script_type' / change / address_index
+ script_type_int = bip32_indices[3] - BIP32_PRIME
+ script_type = PURPOSE48_SCRIPT_TYPES_INV.get(script_type_int)
+ if script_type is not None:
+ return script_type
+ return 'standard'
+
+
+# extended pubkeys
+
+def is_xpubkey(x_pubkey):
+ return x_pubkey[0:2] == 'ff'
+
+
+def parse_xpubkey(x_pubkey):
+ assert x_pubkey[0:2] == 'ff'
+ return BIP32_KeyStore.parse_xpubkey(x_pubkey)
+
+
+def xpubkey_to_address(x_pubkey):
+ if x_pubkey[0:2] == 'fd':
+ address = bitcoin.script_to_address(x_pubkey[2:])
+ return x_pubkey, address
+ if x_pubkey[0:2] in ['02', '03', '04']:
+ pubkey = x_pubkey
+ elif x_pubkey[0:2] == 'ff':
+ xpub, s = BIP32_KeyStore.parse_xpubkey(x_pubkey)
+ pubkey = BIP32_KeyStore.get_pubkey_from_xpub(xpub, s)
+ elif x_pubkey[0:2] == 'fe':
+ mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
+ pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
+ else:
+ raise BitcoinException("Cannot parse pubkey. prefix: {}"
+ .format(x_pubkey[0:2]))
+ if pubkey:
+ address = public_key_to_p2pkh(bfh(pubkey))
+ return pubkey, address
+
+def xpubkey_to_pubkey(x_pubkey):
+ pubkey, address = xpubkey_to_address(x_pubkey)
+ return pubkey
+
+hw_keystores = {}
+
+def register_keystore(hw_type, constructor):
+ hw_keystores[hw_type] = constructor
+
+def hardware_keystore(d):
+ hw_type = d['hw_type']
+ if hw_type in hw_keystores:
+ constructor = hw_keystores[hw_type]
+ return constructor(d)
+ raise WalletFileException('unknown hardware type: {}. hw_keystores: {}'.format(hw_type, list(hw_keystores.keys())))
+
+def load_keystore(storage, name):
+ d = storage.get(name, {})
+ t = d.get('type')
+ if not t:
+ raise WalletFileException(
+ 'Wallet format requires update.\n'
+ 'Cannot find keystore for name {}'.format(name))
+ if t == 'old':
+ k = Old_KeyStore(d)
+ elif t == 'imported':
+ k = Imported_KeyStore(d)
+ elif t == 'bip32':
+ k = BIP32_KeyStore(d)
+ elif t == 'hardware':
+ k = hardware_keystore(d)
+ else:
+ raise WalletFileException(
+ 'Unknown type {} for keystore named {}'.format(t, name))
+ return k
+
+
+def is_old_mpk(mpk: str) -> bool:
+ try:
+ int(mpk, 16)
+ except:
+ return False
+ if len(mpk) != 128:
+ return False
+ try:
+ ecc.ECPubkey(bfh('04' + mpk))
+ except:
+ return False
+ return True
+
+
+def is_address_list(text):
+ parts = text.split()
+ return bool(parts) and all(bitcoin.is_address(x) for x in parts)
+
+
+def get_private_keys(text):
+ parts = text.split('\n')
+ parts = map(lambda x: ''.join(x.split()), parts)
+ parts = list(filter(bool, parts))
+ if bool(parts) and all(bitcoin.is_private_key(x) for x in parts):
+ return parts
+
+
+def is_private_key_list(text):
+ return bool(get_private_keys(text))
+
+
+is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
+is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
+is_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x)
+is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
+is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
+
+
+def bip44_derivation(account_id, bip43_purpose=44):
+ coin = constants.net.BIP44_COIN_TYPE
+ return "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id))
+
+
+def purpose48_derivation(account_id: int, xtype: str) -> str:
+ # m / purpose' / coin_type' / account' / script_type' / change / address_index
+ bip43_purpose = 48
+ coin = constants.net.BIP44_COIN_TYPE
+ account_id = int(account_id)
+ script_type_int = PURPOSE48_SCRIPT_TYPES.get(xtype)
+ if script_type_int is None:
+ raise Exception('unknown xtype: {}'.format(xtype))
+ return "m/%d'/%d'/%d'/%d'" % (bip43_purpose, coin, account_id, script_type_int)
+
+
+def from_seed(seed, passphrase, is_p2sh):
+ t = seed_type(seed)
+ if t == 'old':
+ keystore = Old_KeyStore({})
+ keystore.add_seed(seed)
+ elif t in ['standard', 'segwit']:
+ keystore = BIP32_KeyStore({})
+ keystore.add_seed(seed)
+ keystore.passphrase = passphrase
+ bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
+ if t == 'standard':
+ der = "m/"
+ xtype = 'standard'
+ else:
+ der = "m/1'/" if is_p2sh else "m/0'/"
+ xtype = 'p2wsh' if is_p2sh else 'p2wpkh'
+ keystore.add_xprv_from_seed(bip32_seed, xtype, der)
+ else:
+ raise BitcoinException('Unexpected seed type {}'.format(t))
+ return keystore
+
+def from_private_key_list(text):
+ keystore = Imported_KeyStore({})
+ for x in get_private_keys(text):
+ keystore.import_key(x, None)
+ return keystore
+
+def from_old_mpk(mpk):
+ keystore = Old_KeyStore({})
+ keystore.add_master_public_key(mpk)
+ return keystore
+
+def from_xpub(xpub):
+ k = BIP32_KeyStore({})
+ k.xpub = xpub
+ return k
+
+def from_xprv(xprv):
+ xpub = bitcoin.xpub_from_xprv(xprv)
+ k = BIP32_KeyStore({})
+ k.xprv = xprv
+ k.xpub = xpub
+ return k
+
+def from_master_key(text):
+ if is_xprv(text):
+ k = from_xprv(text)
+ elif is_old_mpk(text):
+ k = from_old_mpk(text)
+ elif is_xpub(text):
+ k = from_xpub(text)
+ else:
+ raise BitcoinException('Invalid master key')
+ return k
diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py
new file mode 100644
index 000000000..a22913ae3
--- /dev/null
+++ b/electrum/mnemonic.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+import hmac
+import math
+import hashlib
+import unicodedata
+import string
+
+import ecdsa
+
+from .util import print_error
+from .bitcoin import is_old_seed, is_new_seed
+from . import version
+
+# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html
+CJK_INTERVALS = [
+ (0x4E00, 0x9FFF, 'CJK Unified Ideographs'),
+ (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'),
+ (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'),
+ (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'),
+ (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'),
+ (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'),
+ (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'),
+ (0x3190, 0x319F , 'Kanbun'),
+ (0x2E80, 0x2EFF, 'CJK Radicals Supplement'),
+ (0x2F00, 0x2FDF, 'CJK Radicals'),
+ (0x31C0, 0x31EF, 'CJK Strokes'),
+ (0x2FF0, 0x2FFF, 'Ideographic Description Characters'),
+ (0xE0100, 0xE01EF, 'Variation Selectors Supplement'),
+ (0x3100, 0x312F, 'Bopomofo'),
+ (0x31A0, 0x31BF, 'Bopomofo Extended'),
+ (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'),
+ (0x3040, 0x309F, 'Hiragana'),
+ (0x30A0, 0x30FF, 'Katakana'),
+ (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'),
+ (0x1B000, 0x1B0FF, 'Kana Supplement'),
+ (0xAC00, 0xD7AF, 'Hangul Syllables'),
+ (0x1100, 0x11FF, 'Hangul Jamo'),
+ (0xA960, 0xA97F, 'Hangul Jamo Extended A'),
+ (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'),
+ (0x3130, 0x318F, 'Hangul Compatibility Jamo'),
+ (0xA4D0, 0xA4FF, 'Lisu'),
+ (0x16F00, 0x16F9F, 'Miao'),
+ (0xA000, 0xA48F, 'Yi Syllables'),
+ (0xA490, 0xA4CF, 'Yi Radicals'),
+]
+
+def is_CJK(c):
+ n = ord(c)
+ for imin,imax,name in CJK_INTERVALS:
+ if n>=imin and n<=imax: return True
+ return False
+
+
+def normalize_text(seed):
+ # normalize
+ seed = unicodedata.normalize('NFKD', seed)
+ # lower
+ seed = seed.lower()
+ # remove accents
+ seed = u''.join([c for c in seed if not unicodedata.combining(c)])
+ # normalize whitespaces
+ seed = u' '.join(seed.split())
+ # remove whitespaces between CJK
+ 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]))])
+ return seed
+
+def load_wordlist(filename):
+ path = os.path.join(os.path.dirname(__file__), 'wordlist', filename)
+ with open(path, 'r', encoding='utf-8') as f:
+ s = f.read().strip()
+ s = unicodedata.normalize('NFKD', s)
+ lines = s.split('\n')
+ wordlist = []
+ for line in lines:
+ line = line.split('#')[0]
+ line = line.strip(' \r')
+ assert ' ' not in line
+ if line:
+ wordlist.append(line)
+ return wordlist
+
+
+filenames = {
+ 'en':'english.txt',
+ 'es':'spanish.txt',
+ 'ja':'japanese.txt',
+ 'pt':'portuguese.txt',
+ 'zh':'chinese_simplified.txt'
+}
+
+
+
+class Mnemonic(object):
+ # Seed derivation no longer follows BIP39
+ # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum
+
+ def __init__(self, lang=None):
+ lang = lang or 'en'
+ print_error('language', lang)
+ filename = filenames.get(lang[0:2], 'english.txt')
+ self.wordlist = load_wordlist(filename)
+ print_error("wordlist has %d words"%len(self.wordlist))
+
+ @classmethod
+ def mnemonic_to_seed(self, mnemonic, passphrase):
+ PBKDF2_ROUNDS = 2048
+ mnemonic = normalize_text(mnemonic)
+ passphrase = normalize_text(passphrase)
+ return hashlib.pbkdf2_hmac('sha512', mnemonic.encode('utf-8'), b'electrum' + passphrase.encode('utf-8'), iterations = PBKDF2_ROUNDS)
+
+ def mnemonic_encode(self, i):
+ n = len(self.wordlist)
+ words = []
+ while i:
+ x = i%n
+ i = i//n
+ words.append(self.wordlist[x])
+ return ' '.join(words)
+
+ def get_suggestions(self, prefix):
+ for w in self.wordlist:
+ if w.startswith(prefix):
+ yield w
+
+ def mnemonic_decode(self, seed):
+ n = len(self.wordlist)
+ words = seed.split()
+ i = 0
+ while words:
+ w = words.pop()
+ k = self.wordlist.index(w)
+ i = i*n + k
+ return i
+
+ def make_seed(self, seed_type='standard', num_bits=132):
+ prefix = version.seed_prefix(seed_type)
+ # increase num_bits in order to obtain a uniform distribution for the last word
+ bpw = math.log(len(self.wordlist), 2)
+ # rounding
+ n = int(math.ceil(num_bits/bpw) * bpw)
+ print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n)
+ entropy = 1
+ while entropy < pow(2, n - bpw):
+ # try again if seed would not contain enough words
+ entropy = ecdsa.util.randrange(pow(2, n))
+ nonce = 0
+ while True:
+ nonce += 1
+ i = entropy + nonce
+ seed = self.mnemonic_encode(i)
+ if i != self.mnemonic_decode(seed):
+ raise Exception('Cannot extract same entropy from mnemonic!')
+ if is_old_seed(seed):
+ continue
+ if is_new_seed(seed, prefix):
+ break
+ print_error('%d words'%len(seed.split()))
+ return seed
diff --git a/electrum/msqr.py b/electrum/msqr.py
new file mode 100644
index 000000000..76629fc68
--- /dev/null
+++ b/electrum/msqr.py
@@ -0,0 +1,94 @@
+# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
+
+def modular_sqrt(a, p):
+ """ Find a quadratic residue (mod p) of 'a'. p
+ must be an odd prime.
+
+ Solve the congruence of the form:
+ x^2 = a (mod p)
+ And returns x. Note that p - x is also a root.
+
+ 0 is returned is no square root exists for
+ these a and p.
+
+ The Tonelli-Shanks algorithm is used (except
+ for some simple cases in which the solution
+ is known from an identity). This algorithm
+ runs in polynomial time (unless the
+ generalized Riemann hypothesis is false).
+ """
+ # Simple cases
+ #
+ if legendre_symbol(a, p) != 1:
+ return 0
+ elif a == 0:
+ return 0
+ elif p == 2:
+ return p
+ elif p % 4 == 3:
+ return pow(a, (p + 1) // 4, p)
+
+ # Partition p-1 to s * 2^e for an odd s (i.e.
+ # reduce all the powers of 2 from p-1)
+ #
+ s = p - 1
+ e = 0
+ while s % 2 == 0:
+ s //= 2
+ e += 1
+
+ # Find some 'n' with a legendre symbol n|p = -1.
+ # Shouldn't take long.
+ #
+ n = 2
+ while legendre_symbol(n, p) != -1:
+ n += 1
+
+ # Here be dragons!
+ # Read the paper "Square roots from 1; 24, 51,
+ # 10 to Dan Shanks" by Ezra Brown for more
+ # information
+ #
+
+ # x is a guess of the square root that gets better
+ # with each iteration.
+ # b is the "fudge factor" - by how much we're off
+ # with the guess. The invariant x^2 = ab (mod p)
+ # is maintained throughout the loop.
+ # g is used for successive powers of n to update
+ # both a and b
+ # r is the exponent - decreases with each update
+ #
+ x = pow(a, (s + 1) // 2, p)
+ b = pow(a, s, p)
+ g = pow(n, s, p)
+ r = e
+
+ while True:
+ t = b
+ m = 0
+ for m in range(r):
+ if t == 1:
+ break
+ t = pow(t, 2, p)
+
+ if m == 0:
+ return x
+
+ gs = pow(g, 2 ** (r - m - 1), p)
+ g = (gs * gs) % p
+ x = (x * gs) % p
+ b = (b * g) % p
+ r = m
+
+def legendre_symbol(a, p):
+ """ Compute the Legendre symbol a|p using
+ Euler's criterion. p is a prime, a is
+ relatively prime to p (if p divides
+ a, then a|p = 0)
+
+ Returns 1 if a has a square root modulo
+ p, -1 otherwise.
+ """
+ ls = pow(a, (p - 1) // 2, p)
+ return -1 if ls == p - 1 else ls
diff --git a/electrum/network.py b/electrum/network.py
new file mode 100644
index 000000000..6eb1ae165
--- /dev/null
+++ b/electrum/network.py
@@ -0,0 +1,1323 @@
+# Electrum - Lightweight Bitcoin Client
+# Copyright (c) 2011-2016 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import time
+import queue
+import os
+import errno
+import random
+import re
+import select
+from collections import defaultdict
+import threading
+import socket
+import json
+import sys
+import ipaddress
+
+import dns
+import dns.resolver
+import socks
+
+from . import util
+from .util import print_error
+from . import bitcoin
+from .bitcoin import COIN
+from . import constants
+from .interface import Connection, Interface
+from . import blockchain
+from .version import ELECTRUM_VERSION, PROTOCOL_VERSION
+from .i18n import _
+from .blockchain import InvalidHeader
+
+
+NODES_RETRY_INTERVAL = 60
+SERVER_RETRY_INTERVAL = 10
+
+
+def parse_servers(result):
+ """ parse servers list into dict format"""
+ servers = {}
+ for item in result:
+ host = item[1]
+ out = {}
+ version = None
+ pruning_level = '-'
+ if len(item) > 2:
+ for v in item[2]:
+ if re.match("[st]\d*", v):
+ protocol, port = v[0], v[1:]
+ if port == '': port = constants.net.DEFAULT_PORTS[protocol]
+ out[protocol] = port
+ elif re.match("v(.?)+", v):
+ version = v[1:]
+ elif re.match("p\d*", v):
+ pruning_level = v[1:]
+ if pruning_level == '': pruning_level = '0'
+ if out:
+ out['pruning'] = pruning_level
+ out['version'] = version
+ servers[host] = out
+ return servers
+
+
+def filter_version(servers):
+ def is_recent(version):
+ try:
+ return util.normalize_version(version) >= util.normalize_version(PROTOCOL_VERSION)
+ except Exception as e:
+ return False
+ return {k: v for k, v in servers.items() if is_recent(v.get('version'))}
+
+
+def filter_noonion(servers):
+ return {k: v for k, v in servers.items() if not k.endswith('.onion')}
+
+
+def filter_protocol(hostmap, protocol='s'):
+ '''Filters the hostmap for those implementing protocol.
+ The result is a list in serialized form.'''
+ eligible = []
+ for host, portmap in hostmap.items():
+ port = portmap.get(protocol)
+ if port:
+ eligible.append(serialize_server(host, port, protocol))
+ return eligible
+
+
+def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()):
+ if hostmap is None:
+ hostmap = constants.net.DEFAULT_SERVERS
+ eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set)
+ return random.choice(eligible) if eligible else None
+
+
+from .simple_config import SimpleConfig
+
+proxy_modes = ['socks4', 'socks5', 'http']
+
+
+def serialize_proxy(p):
+ if not isinstance(p, dict):
+ return None
+ return ':'.join([p.get('mode'), p.get('host'), p.get('port'),
+ p.get('user', ''), p.get('password', '')])
+
+
+def deserialize_proxy(s):
+ if not isinstance(s, str):
+ return None
+ if s.lower() == 'none':
+ return None
+ proxy = { "mode":"socks5", "host":"localhost" }
+ args = s.split(':')
+ n = 0
+ if proxy_modes.count(args[n]) == 1:
+ proxy["mode"] = args[n]
+ n += 1
+ if len(args) > n:
+ proxy["host"] = args[n]
+ n += 1
+ if len(args) > n:
+ proxy["port"] = args[n]
+ n += 1
+ else:
+ proxy["port"] = "8080" if proxy["mode"] == "http" else "1080"
+ if len(args) > n:
+ proxy["user"] = args[n]
+ n += 1
+ if len(args) > n:
+ proxy["password"] = args[n]
+ return proxy
+
+
+def deserialize_server(server_str):
+ host, port, protocol = str(server_str).rsplit(':', 2)
+ if protocol not in 'st':
+ raise ValueError('invalid network protocol: {}'.format(protocol))
+ int(port) # Throw if cannot be converted to int
+ return host, port, protocol
+
+
+def serialize_server(host, port, protocol):
+ return str(':'.join([host, port, protocol]))
+
+
+class Network(util.DaemonThread):
+ """The Network class manages a set of connections to remote electrum
+ servers, each connected socket is handled by an Interface() object.
+ Connections are initiated by a Connection() thread which stops once
+ the connection succeeds or fails.
+
+ Our external API:
+
+ - Member functions get_header(), get_interfaces(), get_local_height(),
+ get_parameters(), get_server_height(), get_status_value(),
+ is_connected(), set_parameters(), stop()
+ """
+ verbosity_filter = 'n'
+
+ def __init__(self, config=None):
+ if config is None:
+ config = {} # Do not use mutables as default values!
+ util.DaemonThread.__init__(self)
+ self.config = SimpleConfig(config) if isinstance(config, dict) else config
+ self.num_server = 10 if not self.config.get('oneserver') else 0
+ self.blockchains = blockchain.read_blockchains(self.config) # note: needs self.blockchains_lock
+ self.print_error("blockchains", self.blockchains.keys())
+ self.blockchain_index = config.get('blockchain_index', 0)
+ if self.blockchain_index not in self.blockchains.keys():
+ self.blockchain_index = 0
+ # Server for addresses and transactions
+ self.default_server = self.config.get('server', None)
+ # Sanitize default server
+ if self.default_server:
+ try:
+ deserialize_server(self.default_server)
+ except:
+ self.print_error('Warning: failed to parse server-string; falling back to random.')
+ self.default_server = None
+ if not self.default_server:
+ self.default_server = pick_random_server()
+
+ # locks: if you need to take multiple ones, acquire them in the order they are defined here!
+ self.interface_lock = threading.RLock() # <- re-entrant
+ self.callback_lock = threading.Lock()
+ self.pending_sends_lock = threading.Lock()
+ self.recent_servers_lock = threading.RLock() # <- re-entrant
+ self.subscribed_addresses_lock = threading.Lock()
+ self.blockchains_lock = threading.Lock()
+
+ self.pending_sends = []
+ self.message_id = 0
+ self.debug = False
+ self.irc_servers = {} # returned by interface (list from irc)
+ self.recent_servers = self.read_recent_servers() # note: needs self.recent_servers_lock
+
+ self.banner = ''
+ self.donation_address = ''
+ self.relay_fee = None
+ # callbacks passed with subscriptions
+ self.subscriptions = defaultdict(list) # note: needs self.callback_lock
+ self.sub_cache = {} # note: needs self.interface_lock
+ # callbacks set by the GUI
+ self.callbacks = defaultdict(list) # note: needs self.callback_lock
+
+ dir_path = os.path.join(self.config.path, 'certs')
+ util.make_dir(dir_path)
+
+ # subscriptions and requests
+ self.subscribed_addresses = set() # note: needs self.subscribed_addresses_lock
+ self.h2addr = {}
+ # Requests from client we've not seen a response to
+ self.unanswered_requests = {}
+ # retry times
+ self.server_retry_time = time.time()
+ self.nodes_retry_time = time.time()
+ # kick off the network. interface is the main server we are currently
+ # communicating with. interfaces is the set of servers we are connecting
+ # to or have an ongoing connection with
+ self.interface = None # note: needs self.interface_lock
+ self.interfaces = {} # note: needs self.interface_lock
+ self.auto_connect = self.config.get('auto_connect', True)
+ self.connecting = set()
+ self.requested_chunks = set()
+ self.socket_queue = queue.Queue()
+ self.start_network(deserialize_server(self.default_server)[2],
+ deserialize_proxy(self.config.get('proxy')))
+
+ def with_interface_lock(func):
+ def func_wrapper(self, *args, **kwargs):
+ with self.interface_lock:
+ return func(self, *args, **kwargs)
+ return func_wrapper
+
+ def with_recent_servers_lock(func):
+ def func_wrapper(self, *args, **kwargs):
+ with self.recent_servers_lock:
+ return func(self, *args, **kwargs)
+ return func_wrapper
+
+ def register_callback(self, callback, events):
+ with self.callback_lock:
+ for event in events:
+ self.callbacks[event].append(callback)
+
+ def unregister_callback(self, callback):
+ with self.callback_lock:
+ for callbacks in self.callbacks.values():
+ if callback in callbacks:
+ callbacks.remove(callback)
+
+ def trigger_callback(self, event, *args):
+ with self.callback_lock:
+ callbacks = self.callbacks[event][:]
+ [callback(event, *args) for callback in callbacks]
+
+ def read_recent_servers(self):
+ if not self.config.path:
+ return []
+ path = os.path.join(self.config.path, "recent_servers")
+ try:
+ with open(path, "r", encoding='utf-8') as f:
+ data = f.read()
+ return json.loads(data)
+ except:
+ return []
+
+ @with_recent_servers_lock
+ def save_recent_servers(self):
+ if not self.config.path:
+ return
+ path = os.path.join(self.config.path, "recent_servers")
+ s = json.dumps(self.recent_servers, indent=4, sort_keys=True)
+ try:
+ with open(path, "w", encoding='utf-8') as f:
+ f.write(s)
+ except:
+ pass
+
+ @with_interface_lock
+ def get_server_height(self):
+ return self.interface.tip if self.interface else 0
+
+ def server_is_lagging(self):
+ sh = self.get_server_height()
+ if not sh:
+ self.print_error('no height for main interface')
+ return True
+ lh = self.get_local_height()
+ result = (lh - sh) > 1
+ if result:
+ self.print_error('%s is lagging (%d vs %d)' % (self.default_server, sh, lh))
+ return result
+
+ def set_status(self, status):
+ self.connection_status = status
+ self.notify('status')
+
+ def is_connected(self):
+ return self.interface is not None
+
+ def is_connecting(self):
+ return self.connection_status == 'connecting'
+
+ @with_interface_lock
+ def queue_request(self, method, params, interface=None):
+ # If you want to queue a request on any interface it must go
+ # through this function so message ids are properly tracked
+ if interface is None:
+ interface = self.interface
+ if interface is None:
+ self.print_error('warning: dropping request', method, params)
+ return
+ message_id = self.message_id
+ self.message_id += 1
+ if self.debug:
+ self.print_error(interface.host, "-->", method, params, message_id)
+ interface.queue_request(method, params, message_id)
+ return message_id
+
+ @with_interface_lock
+ def send_subscriptions(self):
+ assert self.interface
+ self.print_error('sending subscriptions to', self.interface.server, len(self.unanswered_requests), len(self.subscribed_addresses))
+ self.sub_cache.clear()
+ # Resend unanswered requests
+ requests = self.unanswered_requests.values()
+ self.unanswered_requests = {}
+ for request in requests:
+ message_id = self.queue_request(request[0], request[1])
+ self.unanswered_requests[message_id] = request
+ self.queue_request('server.banner', [])
+ self.queue_request('server.donation_address', [])
+ self.queue_request('server.peers.subscribe', [])
+ self.request_fee_estimates()
+ self.queue_request('blockchain.relayfee', [])
+ with self.subscribed_addresses_lock:
+ for h in self.subscribed_addresses:
+ self.queue_request('blockchain.scripthash.subscribe', [h])
+
+ def request_fee_estimates(self):
+ from .simple_config import FEE_ETA_TARGETS
+ self.config.requested_fee_estimates()
+ self.queue_request('mempool.get_fee_histogram', [])
+ for i in FEE_ETA_TARGETS:
+ self.queue_request('blockchain.estimatefee', [i])
+
+ def get_status_value(self, key):
+ if key == 'status':
+ value = self.connection_status
+ elif key == 'banner':
+ value = self.banner
+ elif key == 'fee':
+ value = self.config.fee_estimates
+ elif key == 'fee_histogram':
+ value = self.config.mempool_fees
+ elif key == 'updated':
+ value = (self.get_local_height(), self.get_server_height())
+ elif key == 'servers':
+ value = self.get_servers()
+ elif key == 'interfaces':
+ value = self.get_interfaces()
+ return value
+
+ def notify(self, key):
+ if key in ['status', 'updated']:
+ self.trigger_callback(key)
+ else:
+ self.trigger_callback(key, self.get_status_value(key))
+
+ def get_parameters(self):
+ host, port, protocol = deserialize_server(self.default_server)
+ return host, port, protocol, self.proxy, self.auto_connect
+
+ def get_donation_address(self):
+ if self.is_connected():
+ return self.donation_address
+
+ @with_interface_lock
+ def get_interfaces(self):
+ '''The interfaces that are in connected state'''
+ return list(self.interfaces.keys())
+
+ @with_recent_servers_lock
+ def get_servers(self):
+ out = constants.net.DEFAULT_SERVERS
+ if self.irc_servers:
+ out.update(filter_version(self.irc_servers.copy()))
+ else:
+ for s in self.recent_servers:
+ try:
+ host, port, protocol = deserialize_server(s)
+ except:
+ continue
+ if host not in out:
+ out[host] = {protocol: port}
+ if self.config.get('noonion'):
+ out = filter_noonion(out)
+ return out
+
+ @with_interface_lock
+ def start_interface(self, server):
+ if (not server in self.interfaces and not server in self.connecting):
+ if server == self.default_server:
+ self.print_error("connecting to %s as new interface" % server)
+ self.set_status('connecting')
+ self.connecting.add(server)
+ Connection(server, self.socket_queue, self.config.path)
+
+ def start_random_interface(self):
+ with self.interface_lock:
+ exclude_set = self.disconnected_servers.union(set(self.interfaces))
+ server = pick_random_server(self.get_servers(), self.protocol, exclude_set)
+ if server:
+ self.start_interface(server)
+
+ def start_interfaces(self):
+ self.start_interface(self.default_server)
+ for i in range(self.num_server - 1):
+ self.start_random_interface()
+
+ def set_proxy(self, proxy):
+ self.proxy = proxy
+ # Store these somewhere so we can un-monkey-patch
+ if not hasattr(socket, "_socketobject"):
+ socket._socketobject = socket.socket
+ socket._getaddrinfo = socket.getaddrinfo
+ if proxy:
+ self.print_error('setting proxy', proxy)
+ proxy_mode = proxy_modes.index(proxy["mode"]) + 1
+ socks.setdefaultproxy(proxy_mode,
+ proxy["host"],
+ int(proxy["port"]),
+ # socks.py seems to want either None or a non-empty string
+ username=(proxy.get("user", "") or None),
+ password=(proxy.get("password", "") or None))
+ socket.socket = socks.socksocket
+ # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy
+ socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
+ else:
+ socket.socket = socket._socketobject
+ if sys.platform == 'win32':
+ # On Windows, socket.getaddrinfo takes a mutex, and might hold it for up to 10 seconds
+ # when dns-resolving. To speed it up drastically, we resolve dns ourselves, outside that lock.
+ # see #4421
+ socket.getaddrinfo = self._fast_getaddrinfo
+ else:
+ socket.getaddrinfo = socket._getaddrinfo
+
+ @staticmethod
+ def _fast_getaddrinfo(host, *args, **kwargs):
+ def needs_dns_resolving(host2):
+ try:
+ ipaddress.ip_address(host2)
+ return False # already valid IP
+ except ValueError:
+ pass # not an IP
+ if str(host) in ('localhost', 'localhost.',):
+ return False
+ return True
+ try:
+ if needs_dns_resolving(host):
+ answers = dns.resolver.query(host)
+ addr = str(answers[0])
+ else:
+ addr = host
+ except dns.exception.DNSException:
+ # dns failed for some reason, e.g. dns.resolver.NXDOMAIN
+ # this is normal. Simply report back failure:
+ raise socket.gaierror(11001, 'getaddrinfo failed')
+ except BaseException as e:
+ # Possibly internal error in dnspython :( see #4483
+ # Fall back to original socket.getaddrinfo to resolve dns.
+ print_error('dnspython failed to resolve dns with error:', e)
+ addr = host
+ return socket._getaddrinfo(addr, *args, **kwargs)
+
+ @with_interface_lock
+ def start_network(self, protocol, proxy):
+ assert not self.interface and not self.interfaces
+ assert not self.connecting and self.socket_queue.empty()
+ self.print_error('starting network')
+ self.disconnected_servers = set([]) # note: needs self.interface_lock
+ self.protocol = protocol
+ self.set_proxy(proxy)
+ self.start_interfaces()
+
+ @with_interface_lock
+ def stop_network(self):
+ self.print_error("stopping network")
+ for interface in list(self.interfaces.values()):
+ self.close_interface(interface)
+ if self.interface:
+ self.close_interface(self.interface)
+ assert self.interface is None
+ assert not self.interfaces
+ self.connecting = set()
+ # Get a new queue - no old pending connections thanks!
+ self.socket_queue = queue.Queue()
+
+ def set_parameters(self, host, port, protocol, proxy, auto_connect):
+ proxy_str = serialize_proxy(proxy)
+ server = serialize_server(host, port, protocol)
+ # sanitize parameters
+ try:
+ deserialize_server(serialize_server(host, port, protocol))
+ if proxy:
+ proxy_modes.index(proxy["mode"]) + 1
+ int(proxy['port'])
+ except:
+ return
+ self.config.set_key('auto_connect', auto_connect, False)
+ self.config.set_key("proxy", proxy_str, False)
+ self.config.set_key("server", server, True)
+ # abort if changes were not allowed by config
+ if self.config.get('server') != server or self.config.get('proxy') != proxy_str:
+ return
+ self.auto_connect = auto_connect
+ if self.proxy != proxy or self.protocol != protocol:
+ # Restart the network defaulting to the given server
+ with self.interface_lock:
+ self.stop_network()
+ self.default_server = server
+ self.start_network(protocol, proxy)
+ elif self.default_server != server:
+ self.switch_to_interface(server)
+ else:
+ self.switch_lagging_interface()
+ self.notify('updated')
+
+ def switch_to_random_interface(self):
+ '''Switch to a random connected server other than the current one'''
+ servers = self.get_interfaces() # Those in connected state
+ if self.default_server in servers:
+ servers.remove(self.default_server)
+ if servers:
+ self.switch_to_interface(random.choice(servers))
+
+ @with_interface_lock
+ def switch_lagging_interface(self):
+ '''If auto_connect and lagging, switch interface'''
+ if self.server_is_lagging() and self.auto_connect:
+ # switch to one that has the correct header (not height)
+ header = self.blockchain().read_header(self.get_local_height())
+ filtered = list(map(lambda x: x[0], filter(lambda x: x[1].tip_header == header, self.interfaces.items())))
+ if filtered:
+ choice = random.choice(filtered)
+ self.switch_to_interface(choice)
+
+ @with_interface_lock
+ def switch_to_interface(self, server):
+ '''Switch to server as our interface. If no connection exists nor
+ being opened, start a thread to connect. The actual switch will
+ happen on receipt of the connection notification. Do nothing
+ if server already is our interface.'''
+ self.default_server = server
+ if server not in self.interfaces:
+ self.interface = None
+ self.start_interface(server)
+ return
+
+ i = self.interfaces[server]
+ if self.interface != i:
+ self.print_error("switching to", server)
+ # stop any current interface in order to terminate subscriptions
+ # fixme: we don't want to close headers sub
+ #self.close_interface(self.interface)
+ self.interface = i
+ self.send_subscriptions()
+ self.set_status('connected')
+ self.notify('updated')
+ self.notify('interfaces')
+
+ @with_interface_lock
+ def close_interface(self, interface):
+ if interface:
+ if interface.server in self.interfaces:
+ self.interfaces.pop(interface.server)
+ if interface.server == self.default_server:
+ self.interface = None
+ interface.close()
+
+ @with_recent_servers_lock
+ def add_recent_server(self, server):
+ # list is ordered
+ if server in self.recent_servers:
+ self.recent_servers.remove(server)
+ self.recent_servers.insert(0, server)
+ self.recent_servers = self.recent_servers[0:20]
+ self.save_recent_servers()
+
+ def process_response(self, interface, response, callbacks):
+ if self.debug:
+ self.print_error(interface.host, "<--", response)
+ error = response.get('error')
+ result = response.get('result')
+ method = response.get('method')
+ params = response.get('params')
+
+ # We handle some responses; return the rest to the client.
+ if method == 'server.version':
+ interface.server_version = result
+ elif method == 'blockchain.headers.subscribe':
+ if error is None:
+ self.on_notify_header(interface, result)
+ else:
+ # no point in keeping this connection without headers sub
+ self.connection_down(interface.server)
+ return
+ elif method == 'server.peers.subscribe':
+ if error is None:
+ self.irc_servers = parse_servers(result)
+ self.notify('servers')
+ elif method == 'server.banner':
+ if error is None:
+ self.banner = result
+ self.notify('banner')
+ elif method == 'server.donation_address':
+ if error is None:
+ self.donation_address = result
+ elif method == 'mempool.get_fee_histogram':
+ if error is None:
+ self.print_error('fee_histogram', result)
+ self.config.mempool_fees = result
+ self.notify('fee_histogram')
+ elif method == 'blockchain.estimatefee':
+ if error is None and result > 0:
+ i = params[0]
+ fee = int(result*COIN)
+ self.config.update_fee_estimates(i, fee)
+ self.print_error("fee_estimates[%d]" % i, fee)
+ self.notify('fee')
+ elif method == 'blockchain.relayfee':
+ if error is None:
+ self.relay_fee = int(result * COIN) if result is not None else None
+ self.print_error("relayfee", self.relay_fee)
+ elif method == 'blockchain.block.headers':
+ self.on_block_headers(interface, response)
+ elif method == 'blockchain.block.get_header':
+ self.on_get_header(interface, response)
+
+ for callback in callbacks:
+ callback(response)
+
+ @classmethod
+ def get_index(cls, method, params):
+ """ hashable index for subscriptions and cache"""
+ return str(method) + (':' + str(params[0]) if params else '')
+
+ def process_responses(self, interface):
+ responses = interface.get_responses()
+ for request, response in responses:
+ if request:
+ method, params, message_id = request
+ k = self.get_index(method, params)
+ # client requests go through self.send() with a
+ # callback, are only sent to the current interface,
+ # and are placed in the unanswered_requests dictionary
+ client_req = self.unanswered_requests.pop(message_id, None)
+ if client_req:
+ if interface != self.interface:
+ # we probably changed the current interface
+ # in the meantime; drop this.
+ return
+ callbacks = [client_req[2]]
+ else:
+ # fixme: will only work for subscriptions
+ k = self.get_index(method, params)
+ callbacks = list(self.subscriptions.get(k, []))
+
+ # Copy the request method and params to the response
+ response['method'] = method
+ response['params'] = params
+ # Only once we've received a response to an addr subscription
+ # add it to the list; avoids double-sends on reconnection
+ if method == 'blockchain.scripthash.subscribe':
+ with self.subscribed_addresses_lock:
+ self.subscribed_addresses.add(params[0])
+ else:
+ if not response: # Closed remotely / misbehaving
+ self.connection_down(interface.server)
+ break
+ # Rewrite response shape to match subscription request response
+ method = response.get('method')
+ params = response.get('params')
+ k = self.get_index(method, params)
+ if method == 'blockchain.headers.subscribe':
+ response['result'] = params[0]
+ response['params'] = []
+ elif method == 'blockchain.scripthash.subscribe':
+ response['params'] = [params[0]] # addr
+ response['result'] = params[1]
+ callbacks = list(self.subscriptions.get(k, []))
+
+ # update cache if it's a subscription
+ if method.endswith('.subscribe'):
+ with self.interface_lock:
+ self.sub_cache[k] = response
+ # Response is now in canonical form
+ self.process_response(interface, response, callbacks)
+
+ def send(self, messages, callback):
+ '''Messages is a list of (method, params) tuples'''
+ messages = list(messages)
+ with self.pending_sends_lock:
+ self.pending_sends.append((messages, callback))
+
+ @with_interface_lock
+ def process_pending_sends(self):
+ # Requests needs connectivity. If we don't have an interface,
+ # we cannot process them.
+ if not self.interface:
+ return
+
+ with self.pending_sends_lock:
+ sends = self.pending_sends
+ self.pending_sends = []
+
+ for messages, callback in sends:
+ for method, params in messages:
+ r = None
+ if method.endswith('.subscribe'):
+ k = self.get_index(method, params)
+ # add callback to list
+ l = list(self.subscriptions.get(k, []))
+ if callback not in l:
+ l.append(callback)
+ with self.callback_lock:
+ self.subscriptions[k] = l
+ # check cached response for subscriptions
+ r = self.sub_cache.get(k)
+
+ if r is not None:
+ self.print_error("cache hit", k)
+ callback(r)
+ else:
+ message_id = self.queue_request(method, params)
+ self.unanswered_requests[message_id] = method, params, callback
+
+ def unsubscribe(self, callback):
+ '''Unsubscribe a callback to free object references to enable GC.'''
+ # Note: we can't unsubscribe from the server, so if we receive
+ # subsequent notifications process_response() will emit a harmless
+ # "received unexpected notification" warning
+ with self.callback_lock:
+ for v in self.subscriptions.values():
+ if callback in v:
+ v.remove(callback)
+
+ @with_interface_lock
+ def connection_down(self, server):
+ '''A connection to server either went down, or was never made.
+ We distinguish by whether it is in self.interfaces.'''
+ self.disconnected_servers.add(server)
+ if server == self.default_server:
+ self.set_status('disconnected')
+ if server in self.interfaces:
+ self.close_interface(self.interfaces[server])
+ self.notify('interfaces')
+ with self.blockchains_lock:
+ for b in self.blockchains.values():
+ if b.catch_up == server:
+ b.catch_up = None
+
+ def new_interface(self, server, socket):
+ # todo: get tip first, then decide which checkpoint to use.
+ self.add_recent_server(server)
+ interface = Interface(server, socket)
+ interface.blockchain = None
+ interface.tip_header = None
+ interface.tip = 0
+ interface.mode = 'default'
+ interface.request = None
+ with self.interface_lock:
+ self.interfaces[server] = interface
+ # server.version should be the first message
+ params = [ELECTRUM_VERSION, PROTOCOL_VERSION]
+ self.queue_request('server.version', params, interface)
+ self.queue_request('blockchain.headers.subscribe', [True], interface)
+ if server == self.default_server:
+ self.switch_to_interface(server)
+ #self.notify('interfaces')
+
+ def maintain_sockets(self):
+ '''Socket maintenance.'''
+ # Responses to connection attempts?
+ while not self.socket_queue.empty():
+ server, socket = self.socket_queue.get()
+ if server in self.connecting:
+ self.connecting.remove(server)
+
+ if socket:
+ self.new_interface(server, socket)
+ else:
+ self.connection_down(server)
+
+ # Send pings and shut down stale interfaces
+ # must use copy of values
+ with self.interface_lock:
+ interfaces = list(self.interfaces.values())
+ for interface in interfaces:
+ if interface.has_timed_out():
+ self.connection_down(interface.server)
+ elif interface.ping_required():
+ self.queue_request('server.ping', [], interface)
+
+ now = time.time()
+ # nodes
+ with self.interface_lock:
+ if len(self.interfaces) + len(self.connecting) < self.num_server:
+ self.start_random_interface()
+ if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:
+ self.print_error('network: retrying connections')
+ self.disconnected_servers = set([])
+ self.nodes_retry_time = now
+
+ # main interface
+ with self.interface_lock:
+ if not self.is_connected():
+ if self.auto_connect:
+ if not self.is_connecting():
+ self.switch_to_random_interface()
+ else:
+ if self.default_server in self.disconnected_servers:
+ if now - self.server_retry_time > SERVER_RETRY_INTERVAL:
+ self.disconnected_servers.remove(self.default_server)
+ self.server_retry_time = now
+ else:
+ self.switch_to_interface(self.default_server)
+ else:
+ if self.config.is_fee_estimates_update_required():
+ self.request_fee_estimates()
+
+ def request_chunk(self, interface, index):
+ if index in self.requested_chunks:
+ return
+ interface.print_error("requesting chunk %d" % index)
+ self.requested_chunks.add(index)
+ height = index * 2016
+ self.queue_request('blockchain.block.headers', [height, 2016],
+ interface)
+
+ def on_block_headers(self, interface, response):
+ '''Handle receiving a chunk of block headers'''
+ error = response.get('error')
+ result = response.get('result')
+ params = response.get('params')
+ blockchain = interface.blockchain
+ if result is None or params is None or error is not None:
+ interface.print_error(error or 'bad response')
+ return
+ # Ignore unsolicited chunks
+ height = params[0]
+ index = height // 2016
+ if index * 2016 != height or index not in self.requested_chunks:
+ interface.print_error("received chunk %d (unsolicited)" % index)
+ return
+ else:
+ interface.print_error("received chunk %d" % index)
+ self.requested_chunks.remove(index)
+ hexdata = result['hex']
+ connect = blockchain.connect_chunk(index, hexdata)
+ if not connect:
+ self.connection_down(interface.server)
+ return
+ if index >= len(blockchain.checkpoints):
+ # If not finished, get the next chunk
+ if blockchain.height() < interface.tip:
+ self.request_chunk(interface, index+1)
+ else:
+ interface.mode = 'default'
+ interface.print_error('catch up done', blockchain.height())
+ blockchain.catch_up = None
+ else:
+ # the verifier must have asked for this chunk
+ pass
+ self.notify('updated')
+
+ def on_get_header(self, interface, response):
+ '''Handle receiving a single block header'''
+ header = response.get('result')
+ if not header:
+ interface.print_error(response)
+ self.connection_down(interface.server)
+ return
+ height = header.get('block_height')
+ #interface.print_error('got header', height, blockchain.hash_header(header))
+ if interface.request != height:
+ interface.print_error("unsolicited header",interface.request, height)
+ self.connection_down(interface.server)
+ return
+ chain = blockchain.check_header(header)
+ if interface.mode == 'backward':
+ can_connect = blockchain.can_connect(header)
+ if can_connect and can_connect.catch_up is None:
+ interface.mode = 'catch_up'
+ interface.blockchain = can_connect
+ interface.blockchain.save_header(header)
+ next_height = height + 1
+ interface.blockchain.catch_up = interface.server
+ elif chain:
+ # FIXME should await "initial chunk download".
+ # binary search will NOT do the correct thing if we don't yet
+ # have all headers up to the fork height
+ interface.print_error("binary search")
+ interface.mode = 'binary'
+ interface.blockchain = chain
+ interface.good = height
+ next_height = (interface.bad + interface.good) // 2
+ assert next_height >= self.max_checkpoint(), (interface.bad, interface.good)
+ else:
+ if height == 0:
+ self.connection_down(interface.server)
+ next_height = None
+ else:
+ interface.bad = height
+ interface.bad_header = header
+ delta = interface.tip - height
+ next_height = max(self.max_checkpoint(), interface.tip - 2 * delta)
+ if height == next_height:
+ self.connection_down(interface.server)
+ next_height = None
+
+ elif interface.mode == 'binary':
+ if chain:
+ interface.good = height
+ interface.blockchain = chain
+ else:
+ interface.bad = height
+ interface.bad_header = header
+ if interface.bad != interface.good + 1:
+ next_height = (interface.bad + interface.good) // 2
+ assert next_height >= self.max_checkpoint()
+ elif not interface.blockchain.can_connect(interface.bad_header, check_height=False):
+ self.connection_down(interface.server)
+ next_height = None
+ else:
+ branch = self.blockchains.get(interface.bad)
+ if branch is not None:
+ if branch.check_header(interface.bad_header):
+ interface.print_error('joining chain', interface.bad)
+ next_height = None
+ elif branch.parent().check_header(header):
+ interface.print_error('reorg', interface.bad, interface.tip)
+ interface.blockchain = branch.parent()
+ next_height = interface.bad
+ else:
+ interface.print_error('forkpoint conflicts with existing fork', branch.path())
+ branch.write(b'', 0)
+ branch.save_header(interface.bad_header)
+ interface.mode = 'catch_up'
+ interface.blockchain = branch
+ next_height = interface.bad + 1
+ interface.blockchain.catch_up = interface.server
+ else:
+ bh = interface.blockchain.height()
+ next_height = None
+ if bh > interface.good:
+ if not interface.blockchain.check_header(interface.bad_header):
+ b = interface.blockchain.fork(interface.bad_header)
+ with self.blockchains_lock:
+ self.blockchains[interface.bad] = b
+ interface.blockchain = b
+ interface.print_error("new chain", b.forkpoint)
+ interface.mode = 'catch_up'
+ maybe_next_height = interface.bad + 1
+ if maybe_next_height <= interface.tip:
+ next_height = maybe_next_height
+ interface.blockchain.catch_up = interface.server
+ else:
+ assert bh == interface.good
+ if interface.blockchain.catch_up is None and bh < interface.tip:
+ interface.print_error("catching up from %d"% (bh + 1))
+ interface.mode = 'catch_up'
+ next_height = bh + 1
+ interface.blockchain.catch_up = interface.server
+
+ self.notify('updated')
+
+ elif interface.mode == 'catch_up':
+ can_connect = interface.blockchain.can_connect(header)
+ if can_connect:
+ interface.blockchain.save_header(header)
+ next_height = height + 1 if height < interface.tip else None
+ else:
+ # go back
+ interface.print_error("cannot connect", height)
+ interface.mode = 'backward'
+ interface.bad = height
+ interface.bad_header = header
+ next_height = height - 1
+
+ if next_height is None:
+ # exit catch_up state
+ interface.print_error('catch up done', interface.blockchain.height())
+ interface.blockchain.catch_up = None
+ self.switch_lagging_interface()
+ self.notify('updated')
+
+ else:
+ raise Exception(interface.mode)
+ # If not finished, get the next header
+ if next_height is not None:
+ if next_height < 0:
+ self.connection_down(interface.server)
+ next_height = None
+ elif interface.mode == 'catch_up' and interface.tip > next_height + 50:
+ self.request_chunk(interface, next_height // 2016)
+ else:
+ self.request_header(interface, next_height)
+ if next_height is None:
+ interface.mode = 'default'
+ interface.request = None
+ self.notify('updated')
+
+ # refresh network dialog
+ self.notify('interfaces')
+
+ def maintain_requests(self):
+ with self.interface_lock:
+ interfaces = list(self.interfaces.values())
+ for interface in interfaces:
+ if interface.request and time.time() - interface.request_time > 20:
+ interface.print_error("blockchain request timed out")
+ self.connection_down(interface.server)
+ continue
+
+ def wait_on_sockets(self):
+ # Python docs say Windows doesn't like empty selects.
+ # Sleep to prevent busy looping
+ if not self.interfaces:
+ time.sleep(0.1)
+ return
+ with self.interface_lock:
+ interfaces = list(self.interfaces.values())
+ rin = [i for i in interfaces]
+ win = [i for i in interfaces if i.num_requests()]
+ try:
+ rout, wout, xout = select.select(rin, win, [], 0.1)
+ except socket.error as e:
+ if e.errno == errno.EINTR:
+ return
+ raise
+ assert not xout
+ for interface in wout:
+ interface.send_requests()
+ for interface in rout:
+ self.process_responses(interface)
+
+ def init_headers_file(self):
+ b = self.blockchains[0]
+ filename = b.path()
+ length = 80 * len(constants.net.CHECKPOINTS) * 2016
+ if not os.path.exists(filename) or os.path.getsize(filename) < length:
+ with open(filename, 'wb') as f:
+ if length>0:
+ f.seek(length-1)
+ f.write(b'\x00')
+ with b.lock:
+ b.update_size()
+
+ def run(self):
+ self.init_headers_file()
+ while self.is_running():
+ self.maintain_sockets()
+ self.wait_on_sockets()
+ self.maintain_requests()
+ self.run_jobs() # Synchronizer and Verifier
+ self.process_pending_sends()
+ self.stop_network()
+ self.on_stop()
+
+ def on_notify_header(self, interface, header_dict):
+ try:
+ header_hex, height = header_dict['hex'], header_dict['height']
+ except KeyError:
+ # no point in keeping this connection without headers sub
+ self.connection_down(interface.server)
+ return
+ try:
+ header = blockchain.deserialize_header(util.bfh(header_hex), height)
+ except InvalidHeader:
+ self.connection_down(interface.server)
+ return
+ #interface.print_error('notified of header', height, blockchain.hash_header(header))
+ if height < self.max_checkpoint():
+ self.connection_down(interface.server)
+ return
+ interface.tip_header = header
+ interface.tip = height
+ if interface.mode != 'default':
+ return
+ b = blockchain.check_header(header)
+ if b:
+ interface.blockchain = b
+ self.switch_lagging_interface()
+ self.notify('updated')
+ self.notify('interfaces')
+ return
+ b = blockchain.can_connect(header)
+ if b:
+ interface.blockchain = b
+ b.save_header(header)
+ self.switch_lagging_interface()
+ self.notify('updated')
+ self.notify('interfaces')
+ return
+ with self.blockchains_lock:
+ tip = max([x.height() for x in self.blockchains.values()])
+ if tip >=0:
+ interface.mode = 'backward'
+ interface.bad = height
+ interface.bad_header = header
+ self.request_header(interface, min(tip +1, height - 1))
+ else:
+ chain = self.blockchains[0]
+ if chain.catch_up is None:
+ chain.catch_up = interface
+ interface.mode = 'catch_up'
+ interface.blockchain = chain
+ with self.blockchains_lock:
+ self.print_error("switching to catchup mode", tip, self.blockchains)
+ self.request_header(interface, 0)
+ else:
+ self.print_error("chain already catching up with", chain.catch_up.server)
+
+ @with_interface_lock
+ def blockchain(self):
+ if self.interface and self.interface.blockchain is not None:
+ self.blockchain_index = self.interface.blockchain.forkpoint
+ return self.blockchains[self.blockchain_index]
+
+ @with_interface_lock
+ def get_blockchains(self):
+ out = {}
+ with self.blockchains_lock:
+ blockchain_items = list(self.blockchains.items())
+ for k, b in blockchain_items:
+ r = list(filter(lambda i: i.blockchain==b, list(self.interfaces.values())))
+ if r:
+ out[k] = r
+ return out
+
+ def follow_chain(self, index):
+ blockchain = self.blockchains.get(index)
+ if blockchain:
+ self.blockchain_index = index
+ self.config.set_key('blockchain_index', index)
+ with self.interface_lock:
+ interfaces = list(self.interfaces.values())
+ for i in interfaces:
+ if i.blockchain == blockchain:
+ self.switch_to_interface(i.server)
+ break
+ else:
+ raise Exception('blockchain not found', index)
+
+ with self.interface_lock:
+ if self.interface:
+ server = self.interface.server
+ host, port, protocol, proxy, auto_connect = self.get_parameters()
+ host, port, protocol = server.split(':')
+ self.set_parameters(host, port, protocol, proxy, auto_connect)
+
+ def get_local_height(self):
+ return self.blockchain().height()
+
+ @staticmethod
+ def __wait_for(it):
+ """Wait for the result of calling lambda `it`."""
+ q = queue.Queue()
+ it(q.put)
+ try:
+ result = q.get(block=True, timeout=30)
+ except queue.Empty:
+ raise util.TimeoutException(_('Server did not answer'))
+
+ if result.get('error'):
+ raise Exception(result.get('error'))
+
+ return result.get('result')
+
+ @staticmethod
+ def __with_default_synchronous_callback(invocation, callback):
+ """ Use this method if you want to make the network request
+ synchronous. """
+ if not callback:
+ return Network.__wait_for(invocation)
+
+ invocation(callback)
+
+ def request_header(self, interface, height):
+ self.queue_request('blockchain.block.get_header', [height], interface)
+ interface.request = height
+ interface.req_time = time.time()
+
+ def map_scripthash_to_address(self, callback):
+ def cb2(x):
+ x2 = x.copy()
+ p = x2.pop('params')
+ addr = self.h2addr[p[0]]
+ x2['params'] = [addr]
+ callback(x2)
+ return cb2
+
+ def subscribe_to_addresses(self, addresses, callback):
+ hash2address = {
+ bitcoin.address_to_scripthash(address): address
+ for address in addresses}
+ self.h2addr.update(hash2address)
+ msgs = [
+ ('blockchain.scripthash.subscribe', [x])
+ for x in hash2address.keys()]
+ self.send(msgs, self.map_scripthash_to_address(callback))
+
+ def request_address_history(self, address, callback):
+ h = bitcoin.address_to_scripthash(address)
+ self.h2addr.update({h: address})
+ self.send([('blockchain.scripthash.get_history', [h])], self.map_scripthash_to_address(callback))
+
+ # NOTE this method handles exceptions and a special edge case, counter to
+ # what the other ElectrumX methods do. This is unexpected.
+ def broadcast_transaction(self, transaction, callback=None):
+ command = 'blockchain.transaction.broadcast'
+ invocation = lambda c: self.send([(command, [str(transaction)])], c)
+
+ if callback:
+ invocation(callback)
+ return
+
+ try:
+ out = Network.__wait_for(invocation)
+ except BaseException as e:
+ return False, "error: " + str(e)
+
+ if out != transaction.txid():
+ return False, "error: " + out
+
+ return True, out
+
+ def get_history_for_scripthash(self, hash, callback=None):
+ command = 'blockchain.scripthash.get_history'
+ invocation = lambda c: self.send([(command, [hash])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def subscribe_to_headers(self, callback=None):
+ command = 'blockchain.headers.subscribe'
+ invocation = lambda c: self.send([(command, [True])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def subscribe_to_address(self, address, callback=None):
+ command = 'blockchain.address.subscribe'
+ invocation = lambda c: self.send([(command, [address])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def get_merkle_for_transaction(self, tx_hash, tx_height, callback=None):
+ command = 'blockchain.transaction.get_merkle'
+ invocation = lambda c: self.send([(command, [tx_hash, tx_height])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def subscribe_to_scripthash(self, scripthash, callback=None):
+ command = 'blockchain.scripthash.subscribe'
+ invocation = lambda c: self.send([(command, [scripthash])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def get_transaction(self, transaction_hash, callback=None):
+ command = 'blockchain.transaction.get'
+ invocation = lambda c: self.send([(command, [transaction_hash])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def get_transactions(self, transaction_hashes, callback=None):
+ command = 'blockchain.transaction.get'
+ messages = [(command, [tx_hash]) for tx_hash in transaction_hashes]
+ invocation = lambda c: self.send(messages, c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def listunspent_for_scripthash(self, scripthash, callback=None):
+ command = 'blockchain.scripthash.listunspent'
+ invocation = lambda c: self.send([(command, [scripthash])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def get_balance_for_scripthash(self, scripthash, callback=None):
+ command = 'blockchain.scripthash.get_balance'
+ invocation = lambda c: self.send([(command, [scripthash])], c)
+
+ return Network.__with_default_synchronous_callback(invocation, callback)
+
+ def export_checkpoints(self, path):
+ # run manually from the console to generate checkpoints
+ cp = self.blockchain().get_checkpoints()
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(json.dumps(cp, indent=4))
+
+ @classmethod
+ def max_checkpoint(cls):
+ return max(0, len(constants.net.CHECKPOINTS) * 2016 - 1)
diff --git a/electrum/old_mnemonic.py b/electrum/old_mnemonic.py
new file mode 100644
index 000000000..7af669950
--- /dev/null
+++ b/electrum/old_mnemonic.py
@@ -0,0 +1,1697 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+# list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
+
+words = [
+"like",
+"just",
+"love",
+"know",
+"never",
+"want",
+"time",
+"out",
+"there",
+"make",
+"look",
+"eye",
+"down",
+"only",
+"think",
+"heart",
+"back",
+"then",
+"into",
+"about",
+"more",
+"away",
+"still",
+"them",
+"take",
+"thing",
+"even",
+"through",
+"long",
+"always",
+"world",
+"too",
+"friend",
+"tell",
+"try",
+"hand",
+"thought",
+"over",
+"here",
+"other",
+"need",
+"smile",
+"again",
+"much",
+"cry",
+"been",
+"night",
+"ever",
+"little",
+"said",
+"end",
+"some",
+"those",
+"around",
+"mind",
+"people",
+"girl",
+"leave",
+"dream",
+"left",
+"turn",
+"myself",
+"give",
+"nothing",
+"really",
+"off",
+"before",
+"something",
+"find",
+"walk",
+"wish",
+"good",
+"once",
+"place",
+"ask",
+"stop",
+"keep",
+"watch",
+"seem",
+"everything",
+"wait",
+"got",
+"yet",
+"made",
+"remember",
+"start",
+"alone",
+"run",
+"hope",
+"maybe",
+"believe",
+"body",
+"hate",
+"after",
+"close",
+"talk",
+"stand",
+"own",
+"each",
+"hurt",
+"help",
+"home",
+"god",
+"soul",
+"new",
+"many",
+"two",
+"inside",
+"should",
+"true",
+"first",
+"fear",
+"mean",
+"better",
+"play",
+"another",
+"gone",
+"change",
+"use",
+"wonder",
+"someone",
+"hair",
+"cold",
+"open",
+"best",
+"any",
+"behind",
+"happen",
+"water",
+"dark",
+"laugh",
+"stay",
+"forever",
+"name",
+"work",
+"show",
+"sky",
+"break",
+"came",
+"deep",
+"door",
+"put",
+"black",
+"together",
+"upon",
+"happy",
+"such",
+"great",
+"white",
+"matter",
+"fill",
+"past",
+"please",
+"burn",
+"cause",
+"enough",
+"touch",
+"moment",
+"soon",
+"voice",
+"scream",
+"anything",
+"stare",
+"sound",
+"red",
+"everyone",
+"hide",
+"kiss",
+"truth",
+"death",
+"beautiful",
+"mine",
+"blood",
+"broken",
+"very",
+"pass",
+"next",
+"forget",
+"tree",
+"wrong",
+"air",
+"mother",
+"understand",
+"lip",
+"hit",
+"wall",
+"memory",
+"sleep",
+"free",
+"high",
+"realize",
+"school",
+"might",
+"skin",
+"sweet",
+"perfect",
+"blue",
+"kill",
+"breath",
+"dance",
+"against",
+"fly",
+"between",
+"grow",
+"strong",
+"under",
+"listen",
+"bring",
+"sometimes",
+"speak",
+"pull",
+"person",
+"become",
+"family",
+"begin",
+"ground",
+"real",
+"small",
+"father",
+"sure",
+"feet",
+"rest",
+"young",
+"finally",
+"land",
+"across",
+"today",
+"different",
+"guy",
+"line",
+"fire",
+"reason",
+"reach",
+"second",
+"slowly",
+"write",
+"eat",
+"smell",
+"mouth",
+"step",
+"learn",
+"three",
+"floor",
+"promise",
+"breathe",
+"darkness",
+"push",
+"earth",
+"guess",
+"save",
+"song",
+"above",
+"along",
+"both",
+"color",
+"house",
+"almost",
+"sorry",
+"anymore",
+"brother",
+"okay",
+"dear",
+"game",
+"fade",
+"already",
+"apart",
+"warm",
+"beauty",
+"heard",
+"notice",
+"question",
+"shine",
+"began",
+"piece",
+"whole",
+"shadow",
+"secret",
+"street",
+"within",
+"finger",
+"point",
+"morning",
+"whisper",
+"child",
+"moon",
+"green",
+"story",
+"glass",
+"kid",
+"silence",
+"since",
+"soft",
+"yourself",
+"empty",
+"shall",
+"angel",
+"answer",
+"baby",
+"bright",
+"dad",
+"path",
+"worry",
+"hour",
+"drop",
+"follow",
+"power",
+"war",
+"half",
+"flow",
+"heaven",
+"act",
+"chance",
+"fact",
+"least",
+"tired",
+"children",
+"near",
+"quite",
+"afraid",
+"rise",
+"sea",
+"taste",
+"window",
+"cover",
+"nice",
+"trust",
+"lot",
+"sad",
+"cool",
+"force",
+"peace",
+"return",
+"blind",
+"easy",
+"ready",
+"roll",
+"rose",
+"drive",
+"held",
+"music",
+"beneath",
+"hang",
+"mom",
+"paint",
+"emotion",
+"quiet",
+"clear",
+"cloud",
+"few",
+"pretty",
+"bird",
+"outside",
+"paper",
+"picture",
+"front",
+"rock",
+"simple",
+"anyone",
+"meant",
+"reality",
+"road",
+"sense",
+"waste",
+"bit",
+"leaf",
+"thank",
+"happiness",
+"meet",
+"men",
+"smoke",
+"truly",
+"decide",
+"self",
+"age",
+"book",
+"form",
+"alive",
+"carry",
+"escape",
+"damn",
+"instead",
+"able",
+"ice",
+"minute",
+"throw",
+"catch",
+"leg",
+"ring",
+"course",
+"goodbye",
+"lead",
+"poem",
+"sick",
+"corner",
+"desire",
+"known",
+"problem",
+"remind",
+"shoulder",
+"suppose",
+"toward",
+"wave",
+"drink",
+"jump",
+"woman",
+"pretend",
+"sister",
+"week",
+"human",
+"joy",
+"crack",
+"grey",
+"pray",
+"surprise",
+"dry",
+"knee",
+"less",
+"search",
+"bleed",
+"caught",
+"clean",
+"embrace",
+"future",
+"king",
+"son",
+"sorrow",
+"chest",
+"hug",
+"remain",
+"sat",
+"worth",
+"blow",
+"daddy",
+"final",
+"parent",
+"tight",
+"also",
+"create",
+"lonely",
+"safe",
+"cross",
+"dress",
+"evil",
+"silent",
+"bone",
+"fate",
+"perhaps",
+"anger",
+"class",
+"scar",
+"snow",
+"tiny",
+"tonight",
+"continue",
+"control",
+"dog",
+"edge",
+"mirror",
+"month",
+"suddenly",
+"comfort",
+"given",
+"loud",
+"quickly",
+"gaze",
+"plan",
+"rush",
+"stone",
+"town",
+"battle",
+"ignore",
+"spirit",
+"stood",
+"stupid",
+"yours",
+"brown",
+"build",
+"dust",
+"hey",
+"kept",
+"pay",
+"phone",
+"twist",
+"although",
+"ball",
+"beyond",
+"hidden",
+"nose",
+"taken",
+"fail",
+"float",
+"pure",
+"somehow",
+"wash",
+"wrap",
+"angry",
+"cheek",
+"creature",
+"forgotten",
+"heat",
+"rip",
+"single",
+"space",
+"special",
+"weak",
+"whatever",
+"yell",
+"anyway",
+"blame",
+"job",
+"choose",
+"country",
+"curse",
+"drift",
+"echo",
+"figure",
+"grew",
+"laughter",
+"neck",
+"suffer",
+"worse",
+"yeah",
+"disappear",
+"foot",
+"forward",
+"knife",
+"mess",
+"somewhere",
+"stomach",
+"storm",
+"beg",
+"idea",
+"lift",
+"offer",
+"breeze",
+"field",
+"five",
+"often",
+"simply",
+"stuck",
+"win",
+"allow",
+"confuse",
+"enjoy",
+"except",
+"flower",
+"seek",
+"strength",
+"calm",
+"grin",
+"gun",
+"heavy",
+"hill",
+"large",
+"ocean",
+"shoe",
+"sigh",
+"straight",
+"summer",
+"tongue",
+"accept",
+"crazy",
+"everyday",
+"exist",
+"grass",
+"mistake",
+"sent",
+"shut",
+"surround",
+"table",
+"ache",
+"brain",
+"destroy",
+"heal",
+"nature",
+"shout",
+"sign",
+"stain",
+"choice",
+"doubt",
+"glance",
+"glow",
+"mountain",
+"queen",
+"stranger",
+"throat",
+"tomorrow",
+"city",
+"either",
+"fish",
+"flame",
+"rather",
+"shape",
+"spin",
+"spread",
+"ash",
+"distance",
+"finish",
+"image",
+"imagine",
+"important",
+"nobody",
+"shatter",
+"warmth",
+"became",
+"feed",
+"flesh",
+"funny",
+"lust",
+"shirt",
+"trouble",
+"yellow",
+"attention",
+"bare",
+"bite",
+"money",
+"protect",
+"amaze",
+"appear",
+"born",
+"choke",
+"completely",
+"daughter",
+"fresh",
+"friendship",
+"gentle",
+"probably",
+"six",
+"deserve",
+"expect",
+"grab",
+"middle",
+"nightmare",
+"river",
+"thousand",
+"weight",
+"worst",
+"wound",
+"barely",
+"bottle",
+"cream",
+"regret",
+"relationship",
+"stick",
+"test",
+"crush",
+"endless",
+"fault",
+"itself",
+"rule",
+"spill",
+"art",
+"circle",
+"join",
+"kick",
+"mask",
+"master",
+"passion",
+"quick",
+"raise",
+"smooth",
+"unless",
+"wander",
+"actually",
+"broke",
+"chair",
+"deal",
+"favorite",
+"gift",
+"note",
+"number",
+"sweat",
+"box",
+"chill",
+"clothes",
+"lady",
+"mark",
+"park",
+"poor",
+"sadness",
+"tie",
+"animal",
+"belong",
+"brush",
+"consume",
+"dawn",
+"forest",
+"innocent",
+"pen",
+"pride",
+"stream",
+"thick",
+"clay",
+"complete",
+"count",
+"draw",
+"faith",
+"press",
+"silver",
+"struggle",
+"surface",
+"taught",
+"teach",
+"wet",
+"bless",
+"chase",
+"climb",
+"enter",
+"letter",
+"melt",
+"metal",
+"movie",
+"stretch",
+"swing",
+"vision",
+"wife",
+"beside",
+"crash",
+"forgot",
+"guide",
+"haunt",
+"joke",
+"knock",
+"plant",
+"pour",
+"prove",
+"reveal",
+"steal",
+"stuff",
+"trip",
+"wood",
+"wrist",
+"bother",
+"bottom",
+"crawl",
+"crowd",
+"fix",
+"forgive",
+"frown",
+"grace",
+"loose",
+"lucky",
+"party",
+"release",
+"surely",
+"survive",
+"teacher",
+"gently",
+"grip",
+"speed",
+"suicide",
+"travel",
+"treat",
+"vein",
+"written",
+"cage",
+"chain",
+"conversation",
+"date",
+"enemy",
+"however",
+"interest",
+"million",
+"page",
+"pink",
+"proud",
+"sway",
+"themselves",
+"winter",
+"church",
+"cruel",
+"cup",
+"demon",
+"experience",
+"freedom",
+"pair",
+"pop",
+"purpose",
+"respect",
+"shoot",
+"softly",
+"state",
+"strange",
+"bar",
+"birth",
+"curl",
+"dirt",
+"excuse",
+"lord",
+"lovely",
+"monster",
+"order",
+"pack",
+"pants",
+"pool",
+"scene",
+"seven",
+"shame",
+"slide",
+"ugly",
+"among",
+"blade",
+"blonde",
+"closet",
+"creek",
+"deny",
+"drug",
+"eternity",
+"gain",
+"grade",
+"handle",
+"key",
+"linger",
+"pale",
+"prepare",
+"swallow",
+"swim",
+"tremble",
+"wheel",
+"won",
+"cast",
+"cigarette",
+"claim",
+"college",
+"direction",
+"dirty",
+"gather",
+"ghost",
+"hundred",
+"loss",
+"lung",
+"orange",
+"present",
+"swear",
+"swirl",
+"twice",
+"wild",
+"bitter",
+"blanket",
+"doctor",
+"everywhere",
+"flash",
+"grown",
+"knowledge",
+"numb",
+"pressure",
+"radio",
+"repeat",
+"ruin",
+"spend",
+"unknown",
+"buy",
+"clock",
+"devil",
+"early",
+"false",
+"fantasy",
+"pound",
+"precious",
+"refuse",
+"sheet",
+"teeth",
+"welcome",
+"add",
+"ahead",
+"block",
+"bury",
+"caress",
+"content",
+"depth",
+"despite",
+"distant",
+"marry",
+"purple",
+"threw",
+"whenever",
+"bomb",
+"dull",
+"easily",
+"grasp",
+"hospital",
+"innocence",
+"normal",
+"receive",
+"reply",
+"rhyme",
+"shade",
+"someday",
+"sword",
+"toe",
+"visit",
+"asleep",
+"bought",
+"center",
+"consider",
+"flat",
+"hero",
+"history",
+"ink",
+"insane",
+"muscle",
+"mystery",
+"pocket",
+"reflection",
+"shove",
+"silently",
+"smart",
+"soldier",
+"spot",
+"stress",
+"train",
+"type",
+"view",
+"whether",
+"bus",
+"energy",
+"explain",
+"holy",
+"hunger",
+"inch",
+"magic",
+"mix",
+"noise",
+"nowhere",
+"prayer",
+"presence",
+"shock",
+"snap",
+"spider",
+"study",
+"thunder",
+"trail",
+"admit",
+"agree",
+"bag",
+"bang",
+"bound",
+"butterfly",
+"cute",
+"exactly",
+"explode",
+"familiar",
+"fold",
+"further",
+"pierce",
+"reflect",
+"scent",
+"selfish",
+"sharp",
+"sink",
+"spring",
+"stumble",
+"universe",
+"weep",
+"women",
+"wonderful",
+"action",
+"ancient",
+"attempt",
+"avoid",
+"birthday",
+"branch",
+"chocolate",
+"core",
+"depress",
+"drunk",
+"especially",
+"focus",
+"fruit",
+"honest",
+"match",
+"palm",
+"perfectly",
+"pillow",
+"pity",
+"poison",
+"roar",
+"shift",
+"slightly",
+"thump",
+"truck",
+"tune",
+"twenty",
+"unable",
+"wipe",
+"wrote",
+"coat",
+"constant",
+"dinner",
+"drove",
+"egg",
+"eternal",
+"flight",
+"flood",
+"frame",
+"freak",
+"gasp",
+"glad",
+"hollow",
+"motion",
+"peer",
+"plastic",
+"root",
+"screen",
+"season",
+"sting",
+"strike",
+"team",
+"unlike",
+"victim",
+"volume",
+"warn",
+"weird",
+"attack",
+"await",
+"awake",
+"built",
+"charm",
+"crave",
+"despair",
+"fought",
+"grant",
+"grief",
+"horse",
+"limit",
+"message",
+"ripple",
+"sanity",
+"scatter",
+"serve",
+"split",
+"string",
+"trick",
+"annoy",
+"blur",
+"boat",
+"brave",
+"clearly",
+"cling",
+"connect",
+"fist",
+"forth",
+"imagination",
+"iron",
+"jock",
+"judge",
+"lesson",
+"milk",
+"misery",
+"nail",
+"naked",
+"ourselves",
+"poet",
+"possible",
+"princess",
+"sail",
+"size",
+"snake",
+"society",
+"stroke",
+"torture",
+"toss",
+"trace",
+"wise",
+"bloom",
+"bullet",
+"cell",
+"check",
+"cost",
+"darling",
+"during",
+"footstep",
+"fragile",
+"hallway",
+"hardly",
+"horizon",
+"invisible",
+"journey",
+"midnight",
+"mud",
+"nod",
+"pause",
+"relax",
+"shiver",
+"sudden",
+"value",
+"youth",
+"abuse",
+"admire",
+"blink",
+"breast",
+"bruise",
+"constantly",
+"couple",
+"creep",
+"curve",
+"difference",
+"dumb",
+"emptiness",
+"gotta",
+"honor",
+"plain",
+"planet",
+"recall",
+"rub",
+"ship",
+"slam",
+"soar",
+"somebody",
+"tightly",
+"weather",
+"adore",
+"approach",
+"bond",
+"bread",
+"burst",
+"candle",
+"coffee",
+"cousin",
+"crime",
+"desert",
+"flutter",
+"frozen",
+"grand",
+"heel",
+"hello",
+"language",
+"level",
+"movement",
+"pleasure",
+"powerful",
+"random",
+"rhythm",
+"settle",
+"silly",
+"slap",
+"sort",
+"spoken",
+"steel",
+"threaten",
+"tumble",
+"upset",
+"aside",
+"awkward",
+"bee",
+"blank",
+"board",
+"button",
+"card",
+"carefully",
+"complain",
+"crap",
+"deeply",
+"discover",
+"drag",
+"dread",
+"effort",
+"entire",
+"fairy",
+"giant",
+"gotten",
+"greet",
+"illusion",
+"jeans",
+"leap",
+"liquid",
+"march",
+"mend",
+"nervous",
+"nine",
+"replace",
+"rope",
+"spine",
+"stole",
+"terror",
+"accident",
+"apple",
+"balance",
+"boom",
+"childhood",
+"collect",
+"demand",
+"depression",
+"eventually",
+"faint",
+"glare",
+"goal",
+"group",
+"honey",
+"kitchen",
+"laid",
+"limb",
+"machine",
+"mere",
+"mold",
+"murder",
+"nerve",
+"painful",
+"poetry",
+"prince",
+"rabbit",
+"shelter",
+"shore",
+"shower",
+"soothe",
+"stair",
+"steady",
+"sunlight",
+"tangle",
+"tease",
+"treasure",
+"uncle",
+"begun",
+"bliss",
+"canvas",
+"cheer",
+"claw",
+"clutch",
+"commit",
+"crimson",
+"crystal",
+"delight",
+"doll",
+"existence",
+"express",
+"fog",
+"football",
+"gay",
+"goose",
+"guard",
+"hatred",
+"illuminate",
+"mass",
+"math",
+"mourn",
+"rich",
+"rough",
+"skip",
+"stir",
+"student",
+"style",
+"support",
+"thorn",
+"tough",
+"yard",
+"yearn",
+"yesterday",
+"advice",
+"appreciate",
+"autumn",
+"bank",
+"beam",
+"bowl",
+"capture",
+"carve",
+"collapse",
+"confusion",
+"creation",
+"dove",
+"feather",
+"girlfriend",
+"glory",
+"government",
+"harsh",
+"hop",
+"inner",
+"loser",
+"moonlight",
+"neighbor",
+"neither",
+"peach",
+"pig",
+"praise",
+"screw",
+"shield",
+"shimmer",
+"sneak",
+"stab",
+"subject",
+"throughout",
+"thrown",
+"tower",
+"twirl",
+"wow",
+"army",
+"arrive",
+"bathroom",
+"bump",
+"cease",
+"cookie",
+"couch",
+"courage",
+"dim",
+"guilt",
+"howl",
+"hum",
+"husband",
+"insult",
+"led",
+"lunch",
+"mock",
+"mostly",
+"natural",
+"nearly",
+"needle",
+"nerd",
+"peaceful",
+"perfection",
+"pile",
+"price",
+"remove",
+"roam",
+"sanctuary",
+"serious",
+"shiny",
+"shook",
+"sob",
+"stolen",
+"tap",
+"vain",
+"void",
+"warrior",
+"wrinkle",
+"affection",
+"apologize",
+"blossom",
+"bounce",
+"bridge",
+"cheap",
+"crumble",
+"decision",
+"descend",
+"desperately",
+"dig",
+"dot",
+"flip",
+"frighten",
+"heartbeat",
+"huge",
+"lazy",
+"lick",
+"odd",
+"opinion",
+"process",
+"puzzle",
+"quietly",
+"retreat",
+"score",
+"sentence",
+"separate",
+"situation",
+"skill",
+"soak",
+"square",
+"stray",
+"taint",
+"task",
+"tide",
+"underneath",
+"veil",
+"whistle",
+"anywhere",
+"bedroom",
+"bid",
+"bloody",
+"burden",
+"careful",
+"compare",
+"concern",
+"curtain",
+"decay",
+"defeat",
+"describe",
+"double",
+"dreamer",
+"driver",
+"dwell",
+"evening",
+"flare",
+"flicker",
+"grandma",
+"guitar",
+"harm",
+"horrible",
+"hungry",
+"indeed",
+"lace",
+"melody",
+"monkey",
+"nation",
+"object",
+"obviously",
+"rainbow",
+"salt",
+"scratch",
+"shown",
+"shy",
+"stage",
+"stun",
+"third",
+"tickle",
+"useless",
+"weakness",
+"worship",
+"worthless",
+"afternoon",
+"beard",
+"boyfriend",
+"bubble",
+"busy",
+"certain",
+"chin",
+"concrete",
+"desk",
+"diamond",
+"doom",
+"drawn",
+"due",
+"felicity",
+"freeze",
+"frost",
+"garden",
+"glide",
+"harmony",
+"hopefully",
+"hunt",
+"jealous",
+"lightning",
+"mama",
+"mercy",
+"peel",
+"physical",
+"position",
+"pulse",
+"punch",
+"quit",
+"rant",
+"respond",
+"salty",
+"sane",
+"satisfy",
+"savior",
+"sheep",
+"slept",
+"social",
+"sport",
+"tuck",
+"utter",
+"valley",
+"wolf",
+"aim",
+"alas",
+"alter",
+"arrow",
+"awaken",
+"beaten",
+"belief",
+"brand",
+"ceiling",
+"cheese",
+"clue",
+"confidence",
+"connection",
+"daily",
+"disguise",
+"eager",
+"erase",
+"essence",
+"everytime",
+"expression",
+"fan",
+"flag",
+"flirt",
+"foul",
+"fur",
+"giggle",
+"glorious",
+"ignorance",
+"law",
+"lifeless",
+"measure",
+"mighty",
+"muse",
+"north",
+"opposite",
+"paradise",
+"patience",
+"patient",
+"pencil",
+"petal",
+"plate",
+"ponder",
+"possibly",
+"practice",
+"slice",
+"spell",
+"stock",
+"strife",
+"strip",
+"suffocate",
+"suit",
+"tender",
+"tool",
+"trade",
+"velvet",
+"verse",
+"waist",
+"witch",
+"aunt",
+"bench",
+"bold",
+"cap",
+"certainly",
+"click",
+"companion",
+"creator",
+"dart",
+"delicate",
+"determine",
+"dish",
+"dragon",
+"drama",
+"drum",
+"dude",
+"everybody",
+"feast",
+"forehead",
+"former",
+"fright",
+"fully",
+"gas",
+"hook",
+"hurl",
+"invite",
+"juice",
+"manage",
+"moral",
+"possess",
+"raw",
+"rebel",
+"royal",
+"scale",
+"scary",
+"several",
+"slight",
+"stubborn",
+"swell",
+"talent",
+"tea",
+"terrible",
+"thread",
+"torment",
+"trickle",
+"usually",
+"vast",
+"violence",
+"weave",
+"acid",
+"agony",
+"ashamed",
+"awe",
+"belly",
+"blend",
+"blush",
+"character",
+"cheat",
+"common",
+"company",
+"coward",
+"creak",
+"danger",
+"deadly",
+"defense",
+"define",
+"depend",
+"desperate",
+"destination",
+"dew",
+"duck",
+"dusty",
+"embarrass",
+"engine",
+"example",
+"explore",
+"foe",
+"freely",
+"frustrate",
+"generation",
+"glove",
+"guilty",
+"health",
+"hurry",
+"idiot",
+"impossible",
+"inhale",
+"jaw",
+"kingdom",
+"mention",
+"mist",
+"moan",
+"mumble",
+"mutter",
+"observe",
+"ode",
+"pathetic",
+"pattern",
+"pie",
+"prefer",
+"puff",
+"rape",
+"rare",
+"revenge",
+"rude",
+"scrape",
+"spiral",
+"squeeze",
+"strain",
+"sunset",
+"suspend",
+"sympathy",
+"thigh",
+"throne",
+"total",
+"unseen",
+"weapon",
+"weary"
+]
+
+
+
+n = 1626
+
+# Note about US patent no 5892470: Here each word does not represent a given digit.
+# Instead, the digit represented by a word is variable, it depends on the previous word.
+
+def mn_encode( message ):
+ assert len(message) % 8 == 0
+ out = []
+ for i in range(len(message)//8):
+ word = message[8*i:8*i+8]
+ x = int(word, 16)
+ w1 = (x%n)
+ w2 = ((x//n) + w1)%n
+ w3 = ((x//n//n) + w2)%n
+ out += [ words[w1], words[w2], words[w3] ]
+ return out
+
+
+def mn_decode( wlist ):
+ out = ''
+ for i in range(len(wlist)//3):
+ word1, word2, word3 = wlist[3*i:3*i+3]
+ w1 = words.index(word1)
+ w2 = (words.index(word2))%n
+ w3 = (words.index(word3))%n
+ x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n)
+ out += '%08x'%x
+ return out
+
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) == 1:
+ print('I need arguments: a hex string to encode, or a list of words to decode')
+ elif len(sys.argv) == 2:
+ print(' '.join(mn_encode(sys.argv[1])))
+ else:
+ print(mn_decode(sys.argv[1:]))
diff --git a/electrum/paymentrequest.proto b/electrum/paymentrequest.proto
new file mode 100644
index 000000000..32f9b56a1
--- /dev/null
+++ b/electrum/paymentrequest.proto
@@ -0,0 +1,47 @@
+//
+// Simple Bitcoin Payment Protocol messages
+//
+// Use fields 1000+ for extensions;
+// to avoid conflicts, register extensions via pull-req at
+// https://github.com/bitcoin/bips/bip-0070/extensions.mediawiki
+//
+
+syntax = "proto2";
+package payments;
+option java_package = "org.bitcoin.protocols.payments";
+option java_outer_classname = "Protos";
+
+// Generalized form of "send payment to this/these bitcoin addresses"
+message Output {
+ optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
+ required bytes script = 2; // usually one of the standard Script forms
+}
+message PaymentDetails {
+ optional string network = 1 [default = "main"]; // "main" or "test"
+ repeated Output outputs = 2; // Where payment should be sent
+ required uint64 time = 3; // Timestamp; when payment request created
+ optional uint64 expires = 4; // Timestamp; when this request should be considered invalid
+ optional string memo = 5; // Human-readable description of request for the customer
+ optional string payment_url = 6; // URL to send Payment and get PaymentACK
+ optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message
+}
+message PaymentRequest {
+ optional uint32 payment_details_version = 1 [default = 1];
+ optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1
+ optional bytes pki_data = 3; // depends on pki_type
+ required bytes serialized_payment_details = 4; // PaymentDetails
+ optional bytes signature = 5; // pki-dependent signature
+}
+message X509Certificates {
+ repeated bytes certificate = 1; // DER-encoded X.509 certificate chain
+}
+message Payment {
+ optional bytes merchant_data = 1; // From PaymentDetails.merchant_data
+ repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs
+ repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary
+ optional string memo = 4; // Human-readable message for the merchant
+}
+message PaymentACK {
+ required Payment payment = 1; // Payment message that triggered this ACK
+ optional string memo = 2; // human-readable message for customer
+}
diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
new file mode 100644
index 000000000..78df75666
--- /dev/null
+++ b/electrum/paymentrequest.py
@@ -0,0 +1,525 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import hashlib
+import sys
+import time
+import traceback
+import json
+import requests
+
+import urllib.parse
+
+
+try:
+ from . import paymentrequest_pb2 as pb2
+except ImportError:
+ sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
+
+from . import bitcoin, ecc, util, transaction, x509, rsakey
+from .util import print_error, bh2u, bfh
+from .util import export_meta, import_meta
+
+from .bitcoin import TYPE_ADDRESS
+from .transaction import TxOutput
+
+REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
+ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
+
+ca_path = requests.certs.where()
+ca_list = None
+ca_keyID = None
+
+def load_ca_list():
+ global ca_list, ca_keyID
+ if ca_list is None:
+ ca_list, ca_keyID = x509.load_certificates(ca_path)
+
+
+
+# status of payment requests
+PR_UNPAID = 0
+PR_EXPIRED = 1
+PR_UNKNOWN = 2 # sent but not propagated
+PR_PAID = 3 # send and propagated
+
+
+
+def get_payment_request(url):
+ u = urllib.parse.urlparse(url)
+ error = None
+ if u.scheme in ['http', 'https']:
+ try:
+ response = requests.request('GET', url, headers=REQUEST_HEADERS)
+ response.raise_for_status()
+ # Guard against `bitcoin:`-URIs with invalid payment request URLs
+ if "Content-Type" not in response.headers \
+ or response.headers["Content-Type"] != "application/bitcoin-paymentrequest":
+ data = None
+ error = "payment URL not pointing to a payment request handling server"
+ else:
+ data = response.content
+ print_error('fetched payment request', url, len(response.content))
+ except requests.exceptions.RequestException:
+ data = None
+ error = "payment URL not pointing to a valid server"
+ elif u.scheme == 'file':
+ try:
+ with open(u.path, 'r', encoding='utf-8') as f:
+ data = f.read()
+ except IOError:
+ data = None
+ error = "payment URL not pointing to a valid file"
+ else:
+ data = None
+ error = "Unknown scheme for payment request. URL: {}".format(url)
+ pr = PaymentRequest(data, error)
+ return pr
+
+
+class PaymentRequest:
+
+ def __init__(self, data, error=None):
+ self.raw = data
+ self.error = error
+ self.parse(data)
+ self.requestor = None # known after verify
+ self.tx = None
+
+ def __str__(self):
+ return str(self.raw)
+
+ def parse(self, r):
+ if self.error:
+ return
+ self.id = bh2u(bitcoin.sha256(r)[0:16])
+ try:
+ self.data = pb2.PaymentRequest()
+ self.data.ParseFromString(r)
+ except:
+ self.error = "cannot parse payment request"
+ return
+ self.details = pb2.PaymentDetails()
+ self.details.ParseFromString(self.data.serialized_payment_details)
+ self.outputs = []
+ for o in self.details.outputs:
+ addr = transaction.get_address_from_output_script(o.script)[1]
+ self.outputs.append(TxOutput(TYPE_ADDRESS, addr, o.amount))
+ self.memo = self.details.memo
+ self.payment_url = self.details.payment_url
+
+ def is_pr(self):
+ return self.get_amount() != 0
+ #return self.get_outputs() != [(TYPE_ADDRESS, self.get_requestor(), self.get_amount())]
+
+ def verify(self, contacts):
+ if self.error:
+ return False
+ if not self.raw:
+ self.error = "Empty request"
+ return False
+ pr = pb2.PaymentRequest()
+ try:
+ pr.ParseFromString(self.raw)
+ except:
+ self.error = "Error: Cannot parse payment request"
+ return False
+ if not pr.signature:
+ # the address will be displayed as requestor
+ self.requestor = None
+ return True
+ if pr.pki_type in ["x509+sha256", "x509+sha1"]:
+ return self.verify_x509(pr)
+ elif pr.pki_type in ["dnssec+btc", "dnssec+ecdsa"]:
+ return self.verify_dnssec(pr, contacts)
+ else:
+ self.error = "ERROR: Unsupported PKI Type for Message Signature"
+ return False
+
+ def verify_x509(self, paymntreq):
+ load_ca_list()
+ if not ca_list:
+ self.error = "Trusted certificate authorities list not found"
+ return False
+ cert = pb2.X509Certificates()
+ cert.ParseFromString(paymntreq.pki_data)
+ # verify the chain of certificates
+ try:
+ x, ca = verify_cert_chain(cert.certificate)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.error = str(e)
+ return False
+ # get requestor name
+ self.requestor = x.get_common_name()
+ if self.requestor.startswith('*.'):
+ self.requestor = self.requestor[2:]
+ # verify the BIP70 signature
+ pubkey0 = rsakey.RSAKey(x.modulus, x.exponent)
+ sig = paymntreq.signature
+ paymntreq.signature = b''
+ s = paymntreq.SerializeToString()
+ sigBytes = bytearray(sig)
+ msgBytes = bytearray(s)
+ if paymntreq.pki_type == "x509+sha256":
+ hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
+ verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
+ elif paymntreq.pki_type == "x509+sha1":
+ verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
+ if not verify:
+ self.error = "ERROR: Invalid Signature for Payment Request Data"
+ return False
+ ### SIG Verified
+ self.error = 'Signed by Trusted CA: ' + ca.get_common_name()
+ return True
+
+ def verify_dnssec(self, pr, contacts):
+ sig = pr.signature
+ alias = pr.pki_data
+ info = contacts.resolve(alias)
+ if info.get('validated') is not True:
+ self.error = "Alias verification failed (DNSSEC)"
+ return False
+ if pr.pki_type == "dnssec+btc":
+ self.requestor = alias
+ address = info.get('address')
+ pr.signature = b''
+ message = pr.SerializeToString()
+ if ecc.verify_message_with_address(address, sig, message):
+ self.error = 'Verified with DNSSEC'
+ return True
+ else:
+ self.error = "verify failed"
+ return False
+ else:
+ self.error = "unknown algo"
+ return False
+
+ def has_expired(self):
+ return self.details.expires and self.details.expires < int(time.time())
+
+ def get_expiration_date(self):
+ return self.details.expires
+
+ def get_amount(self):
+ return sum(map(lambda x:x[2], self.outputs))
+
+ def get_address(self):
+ o = self.outputs[0]
+ assert o.type == TYPE_ADDRESS
+ return o.address
+
+ def get_requestor(self):
+ return self.requestor if self.requestor else self.get_address()
+
+ def get_verify_status(self):
+ return self.error if self.requestor else "No Signature"
+
+ def get_memo(self):
+ return self.memo
+
+ def get_dict(self):
+ return {
+ 'requestor': self.get_requestor(),
+ 'memo':self.get_memo(),
+ 'exp': self.get_expiration_date(),
+ 'amount': self.get_amount(),
+ 'signature': self.get_verify_status(),
+ 'txid': self.tx,
+ 'outputs': self.get_outputs()
+ }
+
+ def get_id(self):
+ return self.id if self.requestor else self.get_address()
+
+ def get_outputs(self):
+ return self.outputs[:]
+
+ def send_ack(self, raw_tx, refund_addr):
+ pay_det = self.details
+ if not self.details.payment_url:
+ return False, "no url"
+ paymnt = pb2.Payment()
+ paymnt.merchant_data = pay_det.merchant_data
+ paymnt.transactions.append(bfh(raw_tx))
+ ref_out = paymnt.refund_to.add()
+ ref_out.script = util.bfh(transaction.Transaction.pay_script(TYPE_ADDRESS, refund_addr))
+ paymnt.memo = "Paid using Electrum"
+ pm = paymnt.SerializeToString()
+ payurl = urllib.parse.urlparse(pay_det.payment_url)
+ try:
+ r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=ca_path)
+ except requests.exceptions.SSLError:
+ print("Payment Message/PaymentACK verify Failed")
+ try:
+ r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=False)
+ except Exception as e:
+ print(e)
+ return False, "Payment Message/PaymentACK Failed"
+ if r.status_code >= 500:
+ return False, r.reason
+ try:
+ paymntack = pb2.PaymentACK()
+ paymntack.ParseFromString(r.content)
+ except Exception:
+ return False, "PaymentACK could not be processed. Payment was sent; please manually verify that payment was received."
+ print("PaymentACK message received: %s" % paymntack.memo)
+ return True, paymntack.memo
+
+
+def make_unsigned_request(req):
+ from .transaction import Transaction
+ addr = req['address']
+ time = req.get('time', 0)
+ exp = req.get('exp', 0)
+ if time and type(time) != int:
+ time = 0
+ if exp and type(exp) != int:
+ exp = 0
+ amount = req['amount']
+ if amount is None:
+ amount = 0
+ memo = req['memo']
+ script = bfh(Transaction.pay_script(TYPE_ADDRESS, addr))
+ outputs = [(script, amount)]
+ pd = pb2.PaymentDetails()
+ for script, amount in outputs:
+ pd.outputs.add(amount=amount, script=script)
+ pd.time = time
+ pd.expires = time + exp if exp else 0
+ pd.memo = memo
+ pr = pb2.PaymentRequest()
+ pr.serialized_payment_details = pd.SerializeToString()
+ pr.signature = util.to_bytes('')
+ return pr
+
+
+def sign_request_with_alias(pr, alias, alias_privkey):
+ pr.pki_type = 'dnssec+btc'
+ pr.pki_data = str(alias)
+ message = pr.SerializeToString()
+ ec_key = ecc.ECPrivkey(alias_privkey)
+ compressed = bitcoin.is_compressed(alias_privkey)
+ pr.signature = ec_key.sign_message(message, compressed)
+
+
+def verify_cert_chain(chain):
+ """ Verify a chain of certificates. The last certificate is the CA"""
+ load_ca_list()
+ # parse the chain
+ cert_num = len(chain)
+ x509_chain = []
+ for i in range(cert_num):
+ x = x509.X509(bytearray(chain[i]))
+ x509_chain.append(x)
+ if i == 0:
+ x.check_date()
+ else:
+ if not x.check_ca():
+ raise Exception("ERROR: Supplied CA Certificate Error")
+ if not cert_num > 1:
+ raise Exception("ERROR: CA Certificate Chain Not Provided by Payment Processor")
+ # if the root CA is not supplied, add it to the chain
+ ca = x509_chain[cert_num-1]
+ if ca.getFingerprint() not in ca_list:
+ keyID = ca.get_issuer_keyID()
+ f = ca_keyID.get(keyID)
+ if f:
+ root = ca_list[f]
+ x509_chain.append(root)
+ else:
+ raise Exception("Supplied CA Not Found in Trusted CA Store.")
+ # verify the chain of signatures
+ cert_num = len(x509_chain)
+ for i in range(1, cert_num):
+ x = x509_chain[i]
+ prev_x = x509_chain[i-1]
+ algo, sig, data = prev_x.get_signature()
+ sig = bytearray(sig)
+ pubkey = rsakey.RSAKey(x.modulus, x.exponent)
+ if algo == x509.ALGO_RSA_SHA1:
+ verify = pubkey.hashAndVerify(sig, data)
+ elif algo == x509.ALGO_RSA_SHA256:
+ hashBytes = bytearray(hashlib.sha256(data).digest())
+ verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes)
+ elif algo == x509.ALGO_RSA_SHA384:
+ hashBytes = bytearray(hashlib.sha384(data).digest())
+ verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes)
+ elif algo == x509.ALGO_RSA_SHA512:
+ hashBytes = bytearray(hashlib.sha512(data).digest())
+ verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes)
+ else:
+ raise Exception("Algorithm not supported")
+ util.print_error(self.error, algo.getComponentByName('algorithm'))
+ if not verify:
+ raise Exception("Certificate not Signed by Provided CA Certificate Chain")
+
+ return x509_chain[0], ca
+
+
+def check_ssl_config(config):
+ from . import pem
+ key_path = config.get('ssl_privkey')
+ cert_path = config.get('ssl_chain')
+ with open(key_path, 'r', encoding='utf-8') as f:
+ params = pem.parse_private_key(f.read())
+ with open(cert_path, 'r', encoding='utf-8') as f:
+ s = f.read()
+ bList = pem.dePemList(s, "CERTIFICATE")
+ # verify chain
+ x, ca = verify_cert_chain(bList)
+ # verify that privkey and pubkey match
+ privkey = rsakey.RSAKey(*params)
+ pubkey = rsakey.RSAKey(x.modulus, x.exponent)
+ assert x.modulus == params[0]
+ assert x.exponent == params[1]
+ # return requestor
+ requestor = x.get_common_name()
+ if requestor.startswith('*.'):
+ requestor = requestor[2:]
+ return requestor
+
+def sign_request_with_x509(pr, key_path, cert_path):
+ from . import pem
+ with open(key_path, 'r', encoding='utf-8') as f:
+ params = pem.parse_private_key(f.read())
+ privkey = rsakey.RSAKey(*params)
+ with open(cert_path, 'r', encoding='utf-8') as f:
+ s = f.read()
+ bList = pem.dePemList(s, "CERTIFICATE")
+ certificates = pb2.X509Certificates()
+ certificates.certificate.extend(map(bytes, bList))
+ pr.pki_type = 'x509+sha256'
+ pr.pki_data = certificates.SerializeToString()
+ msgBytes = bytearray(pr.SerializeToString())
+ hashBytes = bytearray(hashlib.sha256(msgBytes).digest())
+ sig = privkey.sign(x509.PREFIX_RSA_SHA256 + hashBytes)
+ pr.signature = bytes(sig)
+
+
+def serialize_request(req):
+ pr = make_unsigned_request(req)
+ signature = req.get('sig')
+ requestor = req.get('name')
+ if requestor and signature:
+ pr.signature = bfh(signature)
+ pr.pki_type = 'dnssec+btc'
+ pr.pki_data = str(requestor)
+ return pr
+
+
+def make_request(config, req):
+ pr = make_unsigned_request(req)
+ key_path = config.get('ssl_privkey')
+ cert_path = config.get('ssl_chain')
+ if key_path and cert_path:
+ sign_request_with_x509(pr, key_path, cert_path)
+ return pr
+
+
+
+class InvoiceStore(object):
+
+ def __init__(self, storage):
+ self.storage = storage
+ self.invoices = {}
+ self.paid = {}
+ d = self.storage.get('invoices', {})
+ self.load(d)
+
+ def set_paid(self, pr, txid):
+ pr.tx = txid
+ pr_id = pr.get_id()
+ self.paid[txid] = pr_id
+ if pr_id not in self.invoices:
+ # in case the user had deleted it previously
+ self.add(pr)
+
+ def load(self, d):
+ for k, v in d.items():
+ try:
+ pr = PaymentRequest(bfh(v.get('hex')))
+ pr.tx = v.get('txid')
+ pr.requestor = v.get('requestor')
+ self.invoices[k] = pr
+ if pr.tx:
+ self.paid[pr.tx] = k
+ except:
+ continue
+
+ def import_file(self, path):
+ def validate(data):
+ return data # TODO
+ import_meta(path, validate, self.on_import)
+
+ def on_import(self, data):
+ self.load(data)
+ self.save()
+
+ def export_file(self, filename):
+ export_meta(self.dump(), filename)
+
+ def dump(self):
+ d = {}
+ for k, pr in self.invoices.items():
+ d[k] = {
+ 'hex': bh2u(pr.raw),
+ 'requestor': pr.requestor,
+ 'txid': pr.tx
+ }
+ return d
+
+ def save(self):
+ self.storage.put('invoices', self.dump())
+
+ def get_status(self, key):
+ pr = self.get(key)
+ if pr is None:
+ print_error("[InvoiceStore] get_status() can't find pr for", key)
+ return
+ if pr.tx is not None:
+ return PR_PAID
+ if pr.has_expired():
+ return PR_EXPIRED
+ return PR_UNPAID
+
+ def add(self, pr):
+ key = pr.get_id()
+ self.invoices[key] = pr
+ self.save()
+ return key
+
+ def remove(self, key):
+ self.invoices.pop(key)
+ self.save()
+
+ def get(self, k):
+ return self.invoices.get(k)
+
+ def sorted_list(self):
+ # sort
+ return self.invoices.values()
+
+ def unpaid_invoices(self):
+ return [ self.invoices[k] for k in filter(lambda x: self.get_status(x)!=PR_PAID, self.invoices.keys())]
diff --git a/electrum/paymentrequest_pb2.py b/electrum/paymentrequest_pb2.py
new file mode 100644
index 000000000..f596128c1
--- /dev/null
+++ b/electrum/paymentrequest_pb2.py
@@ -0,0 +1,367 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: paymentrequest.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='paymentrequest.proto',
+ package='payments',
+ 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')
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+
+_OUTPUT = _descriptor.Descriptor(
+ name='Output',
+ full_name='payments.Output',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='amount', full_name='payments.Output.amount', index=0,
+ number=1, type=4, cpp_type=4, label=1,
+ has_default_value=True, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='script', full_name='payments.Output.script', index=1,
+ number=2, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=34,
+ serialized_end=77,
+)
+
+
+_PAYMENTDETAILS = _descriptor.Descriptor(
+ name='PaymentDetails',
+ full_name='payments.PaymentDetails',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='network', full_name='payments.PaymentDetails.network', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=True, default_value=_b("main").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='outputs', full_name='payments.PaymentDetails.outputs', index=1,
+ number=2, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='time', full_name='payments.PaymentDetails.time', index=2,
+ number=3, type=4, cpp_type=4, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='expires', full_name='payments.PaymentDetails.expires', index=3,
+ number=4, type=4, cpp_type=4, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='memo', full_name='payments.PaymentDetails.memo', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='payment_url', full_name='payments.PaymentDetails.payment_url', index=5,
+ number=6, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='merchant_data', full_name='payments.PaymentDetails.merchant_data', index=6,
+ number=7, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=80,
+ serialized_end=243,
+)
+
+
+_PAYMENTREQUEST = _descriptor.Descriptor(
+ name='PaymentRequest',
+ full_name='payments.PaymentRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='payment_details_version', full_name='payments.PaymentRequest.payment_details_version', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=True, default_value=1,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='pki_type', full_name='payments.PaymentRequest.pki_type', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=True, default_value=_b("none").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='pki_data', full_name='payments.PaymentRequest.pki_data', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='serialized_payment_details', full_name='payments.PaymentRequest.serialized_payment_details', index=3,
+ number=4, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='signature', full_name='payments.PaymentRequest.signature', index=4,
+ number=5, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=246,
+ serialized_end=395,
+)
+
+
+_X509CERTIFICATES = _descriptor.Descriptor(
+ name='X509Certificates',
+ full_name='payments.X509Certificates',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='certificate', full_name='payments.X509Certificates.certificate', index=0,
+ number=1, type=12, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=397,
+ serialized_end=436,
+)
+
+
+_PAYMENT = _descriptor.Descriptor(
+ name='Payment',
+ full_name='payments.Payment',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='merchant_data', full_name='payments.Payment.merchant_data', index=0,
+ number=1, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='transactions', full_name='payments.Payment.transactions', index=1,
+ number=2, type=12, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='refund_to', full_name='payments.Payment.refund_to', index=2,
+ number=3, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='memo', full_name='payments.Payment.memo', index=3,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=438,
+ serialized_end=543,
+)
+
+
+_PAYMENTACK = _descriptor.Descriptor(
+ name='PaymentACK',
+ full_name='payments.PaymentACK',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='payment', full_name='payments.PaymentACK.payment', index=0,
+ number=1, type=11, cpp_type=10, label=2,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='memo', full_name='payments.PaymentACK.memo', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=545,
+ serialized_end=607,
+)
+
+_PAYMENTDETAILS.fields_by_name['outputs'].message_type = _OUTPUT
+_PAYMENT.fields_by_name['refund_to'].message_type = _OUTPUT
+_PAYMENTACK.fields_by_name['payment'].message_type = _PAYMENT
+DESCRIPTOR.message_types_by_name['Output'] = _OUTPUT
+DESCRIPTOR.message_types_by_name['PaymentDetails'] = _PAYMENTDETAILS
+DESCRIPTOR.message_types_by_name['PaymentRequest'] = _PAYMENTREQUEST
+DESCRIPTOR.message_types_by_name['X509Certificates'] = _X509CERTIFICATES
+DESCRIPTOR.message_types_by_name['Payment'] = _PAYMENT
+DESCRIPTOR.message_types_by_name['PaymentACK'] = _PAYMENTACK
+
+Output = _reflection.GeneratedProtocolMessageType('Output', (_message.Message,), dict(
+ DESCRIPTOR = _OUTPUT,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.Output)
+ ))
+_sym_db.RegisterMessage(Output)
+
+PaymentDetails = _reflection.GeneratedProtocolMessageType('PaymentDetails', (_message.Message,), dict(
+ DESCRIPTOR = _PAYMENTDETAILS,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.PaymentDetails)
+ ))
+_sym_db.RegisterMessage(PaymentDetails)
+
+PaymentRequest = _reflection.GeneratedProtocolMessageType('PaymentRequest', (_message.Message,), dict(
+ DESCRIPTOR = _PAYMENTREQUEST,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.PaymentRequest)
+ ))
+_sym_db.RegisterMessage(PaymentRequest)
+
+X509Certificates = _reflection.GeneratedProtocolMessageType('X509Certificates', (_message.Message,), dict(
+ DESCRIPTOR = _X509CERTIFICATES,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.X509Certificates)
+ ))
+_sym_db.RegisterMessage(X509Certificates)
+
+Payment = _reflection.GeneratedProtocolMessageType('Payment', (_message.Message,), dict(
+ DESCRIPTOR = _PAYMENT,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.Payment)
+ ))
+_sym_db.RegisterMessage(Payment)
+
+PaymentACK = _reflection.GeneratedProtocolMessageType('PaymentACK', (_message.Message,), dict(
+ DESCRIPTOR = _PAYMENTACK,
+ __module__ = 'paymentrequest_pb2'
+ # @@protoc_insertion_point(class_scope:payments.PaymentACK)
+ ))
+_sym_db.RegisterMessage(PaymentACK)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\036org.bitcoin.protocols.paymentsB\006Protos'))
+# @@protoc_insertion_point(module_scope)
diff --git a/electrum/pem.py b/electrum/pem.py
new file mode 100644
index 000000000..40390081b
--- /dev/null
+++ b/electrum/pem.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+# This module uses code from TLSLlite
+# TLSLite Author: Trevor Perrin)
+
+
+import binascii
+
+from .x509 import ASN1_Node, bytestr_to_int, decode_OID
+
+
+def a2b_base64(s):
+ try:
+ b = bytearray(binascii.a2b_base64(s))
+ except Exception as e:
+ raise SyntaxError("base64 error: %s" % e)
+ return b
+
+def b2a_base64(b):
+ return binascii.b2a_base64(b)
+
+
+def dePem(s, name):
+ """Decode a PEM string into a bytearray of its payload.
+
+ The input must contain an appropriate PEM prefix and postfix
+ based on the input name string, e.g. for name="CERTIFICATE":
+
+ -----BEGIN CERTIFICATE-----
+ MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL
+ ...
+ KoZIhvcNAQEFBQADAwA5kw==
+ -----END CERTIFICATE-----
+
+ The first such PEM block in the input will be found, and its
+ payload will be base64 decoded and returned.
+ """
+ prefix = "-----BEGIN %s-----" % name
+ postfix = "-----END %s-----" % name
+ start = s.find(prefix)
+ if start == -1:
+ raise SyntaxError("Missing PEM prefix")
+ end = s.find(postfix, start+len(prefix))
+ if end == -1:
+ raise SyntaxError("Missing PEM postfix")
+ s = s[start+len("-----BEGIN %s-----" % name) : end]
+ retBytes = a2b_base64(s) # May raise SyntaxError
+ return retBytes
+
+def dePemList(s, name):
+ """Decode a sequence of PEM blocks into a list of bytearrays.
+
+ The input must contain any number of PEM blocks, each with the appropriate
+ PEM prefix and postfix based on the input name string, e.g. for
+ name="TACK BREAK SIG". Arbitrary text can appear between and before and
+ after the PEM blocks. For example:
+
+ " Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK
+ BREAK SIG-----
+ ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv
+ YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm
+ SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK
+ BREAK SIG----- Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:11Z
+ -----BEGIN TACK BREAK SIG-----
+ ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv
+ YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM
+ +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK
+ BREAK SIG----- "
+
+ All such PEM blocks will be found, decoded, and return in an ordered list
+ of bytearrays, which may have zero elements if not PEM blocks are found.
+ """
+ bList = []
+ prefix = "-----BEGIN %s-----" % name
+ postfix = "-----END %s-----" % name
+ while 1:
+ start = s.find(prefix)
+ if start == -1:
+ return bList
+ end = s.find(postfix, start+len(prefix))
+ if end == -1:
+ raise SyntaxError("Missing PEM postfix")
+ s2 = s[start+len(prefix) : end]
+ retBytes = a2b_base64(s2) # May raise SyntaxError
+ bList.append(retBytes)
+ s = s[end+len(postfix) : ]
+
+def pem(b, name):
+ """Encode a payload bytearray into a PEM string.
+
+ The input will be base64 encoded, then wrapped in a PEM prefix/postfix
+ based on the name string, e.g. for name="CERTIFICATE":
+
+ -----BEGIN CERTIFICATE-----
+ MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL
+ ...
+ KoZIhvcNAQEFBQADAwA5kw==
+ -----END CERTIFICATE-----
+ """
+ s1 = b2a_base64(b)[:-1] # remove terminating \n
+ s2 = b""
+ while s1:
+ s2 += s1[:64] + b"\n"
+ s1 = s1[64:]
+ s = ("-----BEGIN %s-----\n" % name).encode('ascii') + s2 + \
+ ("-----END %s-----\n" % name).encode('ascii')
+ return s
+
+def pemSniff(inStr, name):
+ searchStr = "-----BEGIN %s-----" % name
+ return searchStr in inStr
+
+
+def parse_private_key(s):
+ """Parse a string containing a PEM-encoded ."""
+ if pemSniff(s, "PRIVATE KEY"):
+ bytes = dePem(s, "PRIVATE KEY")
+ return _parsePKCS8(bytes)
+ elif pemSniff(s, "RSA PRIVATE KEY"):
+ bytes = dePem(s, "RSA PRIVATE KEY")
+ return _parseSSLeay(bytes)
+ else:
+ raise SyntaxError("Not a PEM private key file")
+
+
+def _parsePKCS8(_bytes):
+ s = ASN1_Node(_bytes)
+ root = s.root()
+ version_node = s.first_child(root)
+ version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER'))
+ if version != 0:
+ raise SyntaxError("Unrecognized PKCS8 version")
+ rsaOID_node = s.next_node(version_node)
+ ii = s.first_child(rsaOID_node)
+ rsaOID = decode_OID(s.get_value_of_type(ii, 'OBJECT IDENTIFIER'))
+ if rsaOID != '1.2.840.113549.1.1.1':
+ raise SyntaxError("Unrecognized AlgorithmIdentifier")
+ privkey_node = s.next_node(rsaOID_node)
+ value = s.get_value_of_type(privkey_node, 'OCTET STRING')
+ return _parseASN1PrivateKey(value)
+
+
+def _parseSSLeay(bytes):
+ return _parseASN1PrivateKey(ASN1_Node(bytes))
+
+
+def bytesToNumber(s):
+ return int(binascii.hexlify(s), 16)
+
+
+def _parseASN1PrivateKey(s):
+ s = ASN1_Node(s)
+ root = s.root()
+ version_node = s.first_child(root)
+ version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER'))
+ if version != 0:
+ raise SyntaxError("Unrecognized RSAPrivateKey version")
+ n = s.next_node(version_node)
+ e = s.next_node(n)
+ d = s.next_node(e)
+ p = s.next_node(d)
+ q = s.next_node(p)
+ dP = s.next_node(q)
+ dQ = s.next_node(dP)
+ qInv = s.next_node(dQ)
+ return list(map(lambda x: bytesToNumber(s.get_value_of_type(x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv]))
+
diff --git a/electrum/plot.py b/electrum/plot.py
new file mode 100644
index 000000000..0aaf110ab
--- /dev/null
+++ b/electrum/plot.py
@@ -0,0 +1,63 @@
+import datetime
+from collections import defaultdict
+
+import matplotlib
+matplotlib.use('Qt5Agg')
+import matplotlib.pyplot as plt
+import matplotlib.dates as md
+
+from .i18n import _
+from .bitcoin import COIN
+
+
+class NothingToPlotException(Exception):
+ def __str__(self):
+ return _("Nothing to plot.")
+
+
+def plot_history(history):
+ if len(history) == 0:
+ raise NothingToPlotException()
+ hist_in = defaultdict(int)
+ hist_out = defaultdict(int)
+ for item in history:
+ if not item['confirmations']:
+ continue
+ if item['timestamp'] is None:
+ continue
+ value = item['value'].value/COIN
+ date = item['date']
+ datenum = int(md.date2num(datetime.date(date.year, date.month, 1)))
+ if value > 0:
+ hist_in[datenum] += value
+ else:
+ hist_out[datenum] -= value
+
+ f, axarr = plt.subplots(2, sharex=True)
+ plt.subplots_adjust(bottom=0.2)
+ plt.xticks( rotation=25 )
+ ax = plt.gca()
+ plt.ylabel('BTX')
+ plt.xlabel('Month')
+ xfmt = md.DateFormatter('%Y-%m-%d')
+ ax.xaxis.set_major_formatter(xfmt)
+ axarr[0].set_title('Monthly Volume')
+ xfmt = md.DateFormatter('%Y-%m')
+ ax.xaxis.set_major_formatter(xfmt)
+ width = 20
+
+ r1 = None
+ r2 = None
+ dates_values = list(zip(*sorted(hist_in.items())))
+ if dates_values and len(dates_values) == 2:
+ dates, values = dates_values
+ r1 = axarr[0].bar(dates, values, width, label='incoming')
+ axarr[0].legend(loc='upper left')
+ dates_values = list(zip(*sorted(hist_out.items())))
+ if dates_values and len(dates_values) == 2:
+ dates, values = dates_values
+ r2 = axarr[1].bar(dates, values, width, color='r', label='outgoing')
+ axarr[1].legend(loc='upper left')
+ if r1 is None and r2 is None:
+ raise NothingToPlotException()
+ return plt
diff --git a/electrum/plugin.py b/electrum/plugin.py
new file mode 100644
index 000000000..d6c003014
--- /dev/null
+++ b/electrum/plugin.py
@@ -0,0 +1,568 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from collections import namedtuple
+import traceback
+import sys
+import os
+import pkgutil
+import time
+import threading
+
+from .util import print_error
+from .i18n import _
+from .util import profiler, PrintError, DaemonThread, UserCancelled, ThreadJob
+from . import bitcoin
+from . import plugins
+
+plugin_loaders = {}
+hook_names = set()
+hooks = {}
+
+
+class Plugins(DaemonThread):
+ verbosity_filter = 'p'
+
+ @profiler
+ def __init__(self, config, is_local, gui_name):
+ DaemonThread.__init__(self)
+ self.pkgpath = os.path.dirname(plugins.__file__)
+ self.config = config
+ self.hw_wallets = {}
+ self.plugins = {}
+ self.gui_name = gui_name
+ self.descriptions = {}
+ self.device_manager = DeviceMgr(config)
+ self.load_plugins()
+ self.add_jobs(self.device_manager.thread_jobs())
+ self.start()
+
+ def load_plugins(self):
+ for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):
+ mod = pkgutil.find_loader('electrum.plugins.' + name)
+ m = mod.load_module()
+ d = m.__dict__
+ gui_good = self.gui_name in d.get('available_for', [])
+ if not gui_good:
+ continue
+ details = d.get('registers_wallet_type')
+ if details:
+ self.register_wallet_type(name, gui_good, details)
+ details = d.get('registers_keystore')
+ if details:
+ self.register_keystore(name, gui_good, details)
+ self.descriptions[name] = d
+ if not d.get('requires_wallet_type') and self.config.get('use_' + name):
+ try:
+ self.load_plugin(name)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ self.print_error("cannot initialize plugin %s:" % name, str(e))
+
+ def get(self, name):
+ return self.plugins.get(name)
+
+ def count(self):
+ return len(self.plugins)
+
+ def load_plugin(self, name):
+ if name in self.plugins:
+ return self.plugins[name]
+ full_name = 'electrum.plugins.' + name + '.' + self.gui_name
+ loader = pkgutil.find_loader(full_name)
+ if not loader:
+ raise RuntimeError("%s implementation for %s plugin not found"
+ % (self.gui_name, name))
+ p = loader.load_module()
+ plugin = p.Plugin(self, self.config, name)
+ self.add_jobs(plugin.thread_jobs())
+ self.plugins[name] = plugin
+ self.print_error("loaded", name)
+ return plugin
+
+ def close_plugin(self, plugin):
+ self.remove_jobs(plugin.thread_jobs())
+
+ def enable(self, name):
+ self.config.set_key('use_' + name, True, True)
+ p = self.get(name)
+ if p:
+ return p
+ return self.load_plugin(name)
+
+ def disable(self, name):
+ self.config.set_key('use_' + name, False, True)
+ p = self.get(name)
+ if not p:
+ return
+ self.plugins.pop(name)
+ p.close()
+ self.print_error("closed", name)
+
+ def toggle(self, name):
+ p = self.get(name)
+ return self.disable(name) if p else self.enable(name)
+
+ def is_available(self, name, w):
+ d = self.descriptions.get(name)
+ if not d:
+ return False
+ deps = d.get('requires', [])
+ for dep, s in deps:
+ try:
+ __import__(dep)
+ except ImportError as e:
+ self.print_error('Plugin', name, 'unavailable:', type(e).__name__, ':', str(e))
+ return False
+ requires = d.get('requires_wallet_type', [])
+ return not requires or w.wallet_type in requires
+
+ def get_hardware_support(self):
+ out = []
+ for name, (gui_good, details) in self.hw_wallets.items():
+ if gui_good:
+ try:
+ p = self.get_plugin(name)
+ if p.is_enabled():
+ out.append([name, details[2], p])
+ except:
+ traceback.print_exc()
+ self.print_error("cannot load plugin for:", name)
+ return out
+
+ def register_wallet_type(self, name, gui_good, wallet_type):
+ from .wallet import register_wallet_type, register_constructor
+ self.print_error("registering wallet type", (wallet_type, name))
+ def loader():
+ plugin = self.get_plugin(name)
+ register_constructor(wallet_type, plugin.wallet_class)
+ register_wallet_type(wallet_type)
+ plugin_loaders[wallet_type] = loader
+
+ def register_keystore(self, name, gui_good, details):
+ from .keystore import register_keystore
+ def dynamic_constructor(d):
+ return self.get_plugin(name).keystore_class(d)
+ if details[0] == 'hardware':
+ self.hw_wallets[name] = (gui_good, details)
+ self.print_error("registering hardware %s: %s" %(name, details))
+ register_keystore(details[1], dynamic_constructor)
+
+ def get_plugin(self, name):
+ if not name in self.plugins:
+ self.load_plugin(name)
+ return self.plugins[name]
+
+ def run(self):
+ while self.is_running():
+ time.sleep(0.1)
+ self.run_jobs()
+ self.on_stop()
+
+
+def hook(func):
+ hook_names.add(func.__name__)
+ return func
+
+def run_hook(name, *args):
+ results = []
+ f_list = hooks.get(name, [])
+ for p, f in f_list:
+ if p.is_enabled():
+ try:
+ r = f(*args)
+ except Exception:
+ print_error("Plugin error")
+ traceback.print_exc(file=sys.stdout)
+ r = False
+ if r:
+ results.append(r)
+
+ if results:
+ assert len(results) == 1, results
+ return results[0]
+
+
+class BasePlugin(PrintError):
+
+ def __init__(self, parent, config, name):
+ self.parent = parent # The plugins object
+ self.name = name
+ self.config = config
+ self.wallet = None
+ # add self to hooks
+ for k in dir(self):
+ if k in hook_names:
+ l = hooks.get(k, [])
+ l.append((self, getattr(self, k)))
+ hooks[k] = l
+
+ def diagnostic_name(self):
+ return self.name
+
+ def __str__(self):
+ return self.name
+
+ def close(self):
+ # remove self from hooks
+ for k in dir(self):
+ if k in hook_names:
+ l = hooks.get(k, [])
+ l.remove((self, getattr(self, k)))
+ hooks[k] = l
+ self.parent.close_plugin(self)
+ self.on_close()
+
+ def on_close(self):
+ pass
+
+ def requires_settings(self):
+ return False
+
+ def thread_jobs(self):
+ return []
+
+ def is_enabled(self):
+ return self.is_available() and self.config.get('use_'+self.name) is True
+
+ def is_available(self):
+ return True
+
+ def can_user_disable(self):
+ return True
+
+ def settings_dialog(self):
+ pass
+
+
+class DeviceNotFoundError(Exception):
+ pass
+
+class DeviceUnpairableError(Exception):
+ pass
+
+Device = namedtuple("Device", "path interface_number id_ product_key usage_page")
+DeviceInfo = namedtuple("DeviceInfo", "device label initialized")
+
+class DeviceMgr(ThreadJob, PrintError):
+ '''Manages hardware clients. A client communicates over a hardware
+ channel with the device.
+
+ In addition to tracking device HID IDs, the device manager tracks
+ hardware wallets and manages wallet pairing. A HID ID may be
+ paired with a wallet when it is confirmed that the hardware device
+ matches the wallet, i.e. they have the same master public key. A
+ HID ID can be unpaired if e.g. it is wiped.
+
+ Because of hotplugging, a wallet must request its client
+ dynamically each time it is required, rather than caching it
+ itself.
+
+ The device manager is shared across plugins, so just one place
+ does hardware scans when needed. By tracking HID IDs, if a device
+ is plugged into a different port the wallet is automatically
+ re-paired.
+
+ Wallets are informed on connect / disconnect events. It must
+ implement connected(), disconnected() callbacks. Being connected
+ implies a pairing. Callbacks can happen in any thread context,
+ and we do them without holding the lock.
+
+ Confusingly, the HID ID (serial number) reported by the HID system
+ doesn't match the device ID reported by the device itself. We use
+ the HID IDs.
+
+ This plugin is thread-safe. Currently only devices supported by
+ hidapi are implemented.'''
+
+ def __init__(self, config):
+ super(DeviceMgr, self).__init__()
+ # Keyed by xpub. The value is the device id
+ # has been paired, and None otherwise.
+ self.xpub_ids = {}
+ # A list of clients. The key is the client, the value is
+ # a (path, id_) pair.
+ self.clients = {}
+ # What we recognise. Each entry is a (vendor_id, product_id)
+ # pair.
+ self.recognised_hardware = set()
+ # Custom enumerate functions for devices we don't know about.
+ self.enumerate_func = set()
+ # For synchronization
+ self.lock = threading.RLock()
+ self.hid_lock = threading.RLock()
+ self.config = config
+
+ def thread_jobs(self):
+ # Thread job to handle device timeouts
+ return [self]
+
+ def run(self):
+ '''Handle device timeouts. Runs in the context of the Plugins
+ thread.'''
+ with self.lock:
+ clients = list(self.clients.keys())
+ cutoff = time.time() - self.config.get_session_timeout()
+ for client in clients:
+ client.timeout(cutoff)
+
+ def register_devices(self, device_pairs):
+ for pair in device_pairs:
+ self.recognised_hardware.add(pair)
+
+ def register_enumerate_func(self, func):
+ self.enumerate_func.add(func)
+
+ def create_client(self, device, handler, plugin):
+ # Get from cache first
+ client = self.client_lookup(device.id_)
+ if client:
+ return client
+ client = plugin.create_client(device, handler)
+ if client:
+ self.print_error("Registering", client)
+ with self.lock:
+ self.clients[client] = (device.path, device.id_)
+ return client
+
+ def xpub_id(self, xpub):
+ with self.lock:
+ return self.xpub_ids.get(xpub)
+
+ def xpub_by_id(self, id_):
+ with self.lock:
+ for xpub, xpub_id in self.xpub_ids.items():
+ if xpub_id == id_:
+ return xpub
+ return None
+
+ def unpair_xpub(self, xpub):
+ with self.lock:
+ if not xpub in self.xpub_ids:
+ return
+ _id = self.xpub_ids.pop(xpub)
+ self._close_client(_id)
+
+ def unpair_id(self, id_):
+ xpub = self.xpub_by_id(id_)
+ if xpub:
+ self.unpair_xpub(xpub)
+ else:
+ self._close_client(id_)
+
+ def _close_client(self, id_):
+ client = self.client_lookup(id_)
+ self.clients.pop(client, None)
+ if client:
+ client.close()
+
+ def pair_xpub(self, xpub, id_):
+ with self.lock:
+ self.xpub_ids[xpub] = id_
+
+ def client_lookup(self, id_):
+ with self.lock:
+ for client, (path, client_id) in self.clients.items():
+ if client_id == id_:
+ return client
+ return None
+
+ def client_by_id(self, id_):
+ '''Returns a client for the device ID if one is registered. If
+ a device is wiped or in bootloader mode pairing is impossible;
+ in such cases we communicate by device ID and not wallet.'''
+ self.scan_devices()
+ return self.client_lookup(id_)
+
+ def client_for_keystore(self, plugin, handler, keystore, force_pair):
+ self.print_error("getting client for keystore")
+ if handler is None:
+ raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing."))
+ handler.update_status(False)
+ devices = self.scan_devices()
+ xpub = keystore.xpub
+ derivation = keystore.get_derivation()
+ client = self.client_by_xpub(plugin, xpub, handler, devices)
+ if client is None and force_pair:
+ info = self.select_device(plugin, handler, keystore, devices)
+ client = self.force_pair_xpub(plugin, handler, info, xpub, derivation, devices)
+ if client:
+ handler.update_status(True)
+ self.print_error("end client for keystore")
+ return client
+
+ def client_by_xpub(self, plugin, xpub, handler, devices):
+ _id = self.xpub_id(xpub)
+ client = self.client_lookup(_id)
+ if client:
+ # An unpaired client might have another wallet's handler
+ # from a prior scan. Replace to fix dialog parenting.
+ client.handler = handler
+ return client
+
+ for device in devices:
+ if device.id_ == _id:
+ return self.create_client(device, handler, plugin)
+
+
+ def force_pair_xpub(self, plugin, handler, info, xpub, derivation, devices):
+ # The wallet has not been previously paired, so let the user
+ # choose an unpaired device and compare its first address.
+ xtype = bitcoin.xpub_type(xpub)
+ client = self.client_lookup(info.device.id_)
+ if client and client.is_pairable():
+ # See comment above for same code
+ client.handler = handler
+ # This will trigger a PIN/passphrase entry request
+ try:
+ client_xpub = client.get_xpub(derivation, xtype)
+ except (UserCancelled, RuntimeError):
+ # Bad / cancelled PIN / passphrase
+ client_xpub = None
+ if client_xpub == xpub:
+ self.pair_xpub(xpub, info.device.id_)
+ return client
+
+ # The user input has wrong PIN or passphrase, or cancelled input,
+ # or it is not pairable
+ raise DeviceUnpairableError(
+ _('Electrum cannot pair with your {}.\n\n'
+ 'Before you request bitcores to be sent to addresses in this '
+ 'wallet, ensure you can pair with your device, or that you have '
+ 'its seed (and passphrase, if any). Otherwise all bitcores you '
+ 'receive will be unspendable.').format(plugin.device))
+
+ def unpaired_device_infos(self, handler, plugin, devices=None):
+ '''Returns a list of DeviceInfo objects: one for each connected,
+ unpaired device accepted by the plugin.'''
+ if not plugin.libraries_available:
+ message = plugin.get_library_not_available_message()
+ raise Exception(message)
+ if devices is None:
+ devices = self.scan_devices()
+ devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
+ infos = []
+ for device in devices:
+ if device.product_key not in plugin.DEVICE_IDS:
+ continue
+ client = self.create_client(device, handler, plugin)
+ if not client:
+ continue
+ infos.append(DeviceInfo(device, client.label(), client.is_initialized()))
+
+ return infos
+
+ def select_device(self, plugin, handler, keystore, devices=None):
+ '''Ask the user to select a device to use if there is more than one,
+ and return the DeviceInfo for the device.'''
+ while True:
+ infos = self.unpaired_device_infos(handler, plugin, devices)
+ if infos:
+ break
+ msg = _('Please insert your {}').format(plugin.device)
+ if keystore.label:
+ msg += ' ({})'.format(keystore.label)
+ msg += '. {}\n\n{}'.format(
+ _('Verify the cable is connected and that '
+ 'no other application is using it.'),
+ _('Try to connect again?')
+ )
+ if not handler.yes_no_question(msg):
+ raise UserCancelled()
+ devices = None
+ if len(infos) == 1:
+ return infos[0]
+ # select device by label
+ for info in infos:
+ if info.label == keystore.label:
+ return info
+ msg = _("Please select which {} device to use:").format(plugin.device)
+ descriptions = [str(info.label) + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos]
+ c = handler.query_choice(msg, descriptions)
+ if c is None:
+ raise UserCancelled()
+ info = infos[c]
+ # save new label
+ keystore.set_label(info.label)
+ if handler.win.wallet is not None:
+ handler.win.wallet.save_keystore()
+ return info
+
+ def _scan_devices_with_hid(self):
+ try:
+ import hid
+ except ImportError:
+ return []
+
+ with self.hid_lock:
+ hid_list = hid.enumerate(0, 0)
+
+ devices = []
+ for d in hid_list:
+ product_key = (d['vendor_id'], d['product_id'])
+ if product_key in self.recognised_hardware:
+ # Older versions of hid don't provide interface_number
+ interface_number = d.get('interface_number', -1)
+ usage_page = d['usage_page']
+ id_ = d['serial_number']
+ if len(id_) == 0:
+ id_ = str(d['path'])
+ id_ += str(interface_number) + str(usage_page)
+ devices.append(Device(d['path'], interface_number,
+ id_, product_key, usage_page))
+ return devices
+
+ def scan_devices(self):
+ self.print_error("scanning devices...")
+
+ # First see what's connected that we know about
+ devices = self._scan_devices_with_hid()
+
+ # Let plugin handlers enumerate devices we don't know about
+ for f in self.enumerate_func:
+ try:
+ new_devices = f()
+ except BaseException as e:
+ self.print_error('custom device enum failed. func {}, error {}'
+ .format(str(f), str(e)))
+ else:
+ devices.extend(new_devices)
+
+ # find out what was disconnected
+ pairs = [(dev.path, dev.id_) for dev in devices]
+ disconnected_ids = []
+ with self.lock:
+ connected = {}
+ for client, pair in self.clients.items():
+ if pair in pairs and client.has_usable_connection_with_device():
+ connected[client] = pair
+ else:
+ disconnected_ids.append(pair[1])
+ self.clients = connected
+
+ # Unpair disconnected devices
+ for id_ in disconnected_ids:
+ self.unpair_id(id_)
+
+ return devices
diff --git a/electrum/plugins/README b/electrum/plugins/README
new file mode 100644
index 000000000..a8e4db1a0
--- /dev/null
+++ b/electrum/plugins/README
@@ -0,0 +1,31 @@
+Plugin rules:
+
+ * The plugin system of Electrum is designed to allow the development
+ of new features without increasing the core code of Electrum.
+
+ * Electrum is written in pure python. if you want to add a feature
+ that requires non-python libraries, then it must be submitted as a
+ plugin. If the feature you want to add requires communication with
+ a remote server (not an Electrum server), then it should be a
+ plugin as well. If the feature you want to add introduces new
+ dependencies in the code, then it should probably be a plugin.
+
+ * We expect plugin developers to maintain their plugin code. However,
+ once a plugin is merged in Electrum, we will have to maintain it
+ too, because changes in the Electrum code often require updates in
+ the plugin code. Therefore, plugins have to be easy to maintain. If
+ we believe that a plugin will create too much maintenance work in
+ the future, it will be rejected.
+
+ * Plugins should be compatible with Electrum's conventions. If your
+ plugin does not fit with Electrum's architecture, or if we believe
+ that it will create too much maintenance work, it will not be
+ accepted. In particular, do not duplicate existing Electrum code in
+ your plugin.
+
+ * We may decide to remove a plugin after it has been merged in
+ Electrum. For this reason, a plugin must be easily removable,
+ without putting at risk the user's bitcoins. If we feel that a
+ plugin cannot be removed without threatening users who rely on it,
+ we will not merge it.
+
diff --git a/electrum/plugins/__init__.py b/electrum/plugins/__init__.py
new file mode 100644
index 000000000..c6cfe2fdb
--- /dev/null
+++ b/electrum/plugins/__init__.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
diff --git a/electrum/plugins/audio_modem/__init__.py b/electrum/plugins/audio_modem/__init__.py
new file mode 100644
index 000000000..46c2d2091
--- /dev/null
+++ b/electrum/plugins/audio_modem/__init__.py
@@ -0,0 +1,7 @@
+from electrum.i18n import _
+
+fullname = _('Audio MODEM')
+description = _('Provides support for air-gapped transaction signing.')
+requires = [('amodem', 'http://github.com/romanz/amodem/')]
+available_for = ['qt']
+
diff --git a/electrum/plugins/audio_modem/qt.py b/electrum/plugins/audio_modem/qt.py
new file mode 100644
index 000000000..2b88df885
--- /dev/null
+++ b/electrum/plugins/audio_modem/qt.py
@@ -0,0 +1,128 @@
+from functools import partial
+import zlib
+import json
+from io import BytesIO
+import sys
+import platform
+
+from electrum.plugin import BasePlugin, hook
+from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog
+from electrum.util import print_msg, print_error
+from electrum.i18n import _
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton)
+
+try:
+ import amodem.audio
+ import amodem.main
+ import amodem.config
+ print_error('Audio MODEM is available.')
+ amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
+ amodem.log.setLevel(amodem.logging.INFO)
+except ImportError:
+ amodem = None
+ print_error('Audio MODEM is not found.')
+
+
+class Plugin(BasePlugin):
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ if self.is_available():
+ self.modem_config = amodem.config.slowest()
+ self.library_name = {
+ 'Linux': 'libportaudio.so'
+ }[platform.system()]
+
+ def is_available(self):
+ return amodem is not None
+
+ def requires_settings(self):
+ return True
+
+ def settings_widget(self, window):
+ return EnterButton(_('Settings'), partial(self.settings_dialog, window))
+
+ def settings_dialog(self, window):
+ d = WindowModalDialog(window, _("Audio Modem Settings"))
+
+ layout = QGridLayout(d)
+ layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0)
+
+ bitrates = list(sorted(amodem.config.bitrates.keys()))
+
+ def _index_changed(index):
+ bitrate = bitrates[index]
+ self.modem_config = amodem.config.bitrates[bitrate]
+
+ combo = QComboBox()
+ combo.addItems([str(x) for x in bitrates])
+ combo.currentIndexChanged.connect(_index_changed)
+ layout.addWidget(combo, 0, 1)
+
+ ok_button = QPushButton(_("OK"))
+ ok_button.clicked.connect(d.accept)
+ layout.addWidget(ok_button, 1, 1)
+
+ return bool(d.exec_())
+
+ @hook
+ def transaction_dialog(self, dialog):
+ b = QPushButton()
+ b.setIcon(QIcon(":icons/speaker.png"))
+
+ def handler():
+ blob = json.dumps(dialog.tx.as_dict())
+ self._send(parent=dialog, blob=blob)
+ b.clicked.connect(handler)
+ dialog.sharing_buttons.insert(-1, b)
+
+ @hook
+ def scan_text_edit(self, parent):
+ parent.addButton(':icons/microphone.png', partial(self._recv, parent),
+ _("Read from microphone"))
+
+ @hook
+ def show_text_edit(self, parent):
+ def handler():
+ blob = str(parent.toPlainText())
+ self._send(parent=parent, blob=blob)
+ parent.addButton(':icons/speaker.png', handler, _("Send to speaker"))
+
+ def _audio_interface(self):
+ interface = amodem.audio.Interface(config=self.modem_config)
+ return interface.load(self.library_name)
+
+ def _send(self, parent, blob):
+ def sender_thread():
+ with self._audio_interface() as interface:
+ src = BytesIO(blob)
+ dst = interface.player()
+ amodem.main.send(config=self.modem_config, src=src, dst=dst)
+
+ print_msg('Sending:', repr(blob))
+ blob = zlib.compress(blob.encode('ascii'))
+
+ kbps = self.modem_config.modem_bps / 1e3
+ msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps)
+ WaitingDialog(parent, msg, sender_thread)
+
+ def _recv(self, parent):
+ def receiver_thread():
+ with self._audio_interface() as interface:
+ src = interface.recorder()
+ dst = BytesIO()
+ amodem.main.recv(config=self.modem_config, src=src, dst=dst)
+ return dst.getvalue()
+
+ def on_finished(blob):
+ if blob:
+ blob = zlib.decompress(blob).decode('ascii')
+ print_msg('Received:', repr(blob))
+ parent.setText(blob)
+
+ kbps = self.modem_config.modem_bps / 1e3
+ msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps)
+ WaitingDialog(parent, msg, receiver_thread, on_finished)
diff --git a/electrum/plugins/coldcard/README.md b/electrum/plugins/coldcard/README.md
new file mode 100644
index 000000000..651cd8047
--- /dev/null
+++ b/electrum/plugins/coldcard/README.md
@@ -0,0 +1,65 @@
+
+# Coldcard Hardware Wallet Plugin
+
+## Just the glue please
+
+This code connects the public USB API and Electrum. Leverages all
+the good work that's been done by the Electrum team to support
+hardware wallets.
+
+## Background
+
+The Coldcard has a larger screen (128x64) and a number pad. For
+this reason, all PIN code entry is done directly on the device.
+Coldcard does not appear on the USB bus until unlocked with appropriate
+PIN. Initial setup, and seed generation must be done offline.
+
+Coldcard uses an emerging standard for unsigned tranasctions:
+
+PSBT = Partially Signed Bitcoin Transaction = BIP174
+
+However, this spec is still under heavy discussion and in flux. At
+this point, the PSBT files generated will only be compatible with
+Coldcard.
+
+The Coldcard can be used 100% offline: it can generate a skeleton
+Electrum wallet and save it to MicroSD card. Transport that file
+to Electrum and it will fetch history, blockchain details and then
+operate in "unpaired" mode.
+
+Spending transactions can be saved to MicroSD using the "Export PSBT"
+button on the transaction preview dialog (when this plugin is
+owner of the wallet). That PSBT can be signed on the Coldcard
+(again using MicroSD both ways). The result is a ready-to-transmit
+bitcoin transaction, which can be transmitted using Tools > Load
+Transaction > From File in Electrum or really any tool.
+
+
+
+## TODO Items
+
+- No effort yet to support translations or languages other than English, sorry.
+- Coldcard PSBT format is not likely to be compatible with other devices, because the BIP174 is still in flux.
+- Segwit support not 100% complete: can pay to them, but cannot setup wallet to receive them.
+- Limited support for segwit wrapped in P2SH.
+- Someday we could support multisig hardware wallets based on PSBT where each participant
+ is using different devices/systems for signing, however, that belongs in an independant
+ plugin that is PSBT focused and might not require a Coldcard to be present.
+
+### Ctags
+
+- I find this command useful (at top level) ... but I'm a VIM user.
+
+ ctags -f .tags electrum `find . -name ENV -prune -o -name \*.py`
+
+
+### Working with latest ckcc-protocol
+
+- at top level, do this:
+
+ pip install -e git+ssh://git@github.com/Coldcard/ckcc-protocol.git#egg=ckcc-protocol
+
+- but you'll need the https version of that, not ssh like I can.
+- also a branch name would be good in there
+- do `pip uninstall ckcc` first
+- see
diff --git a/electrum/plugins/coldcard/__init__.py b/electrum/plugins/coldcard/__init__.py
new file mode 100644
index 000000000..7cb033f42
--- /dev/null
+++ b/electrum/plugins/coldcard/__init__.py
@@ -0,0 +1,7 @@
+from electrum.i18n import _
+
+fullname = 'Coldcard Wallet'
+description = 'Provides support for the Coldcard hardware wallet from Coinkite'
+requires = [('ckcc-protocol', 'github.com/Coldcard/ckcc-protocol')]
+registers_keystore = ('hardware', 'coldcard', _("Coldcard Wallet"))
+available_for = ['qt', 'cmdline']
diff --git a/electrum/plugins/coldcard/cmdline.py b/electrum/plugins/coldcard/cmdline.py
new file mode 100644
index 000000000..5fddd02a0
--- /dev/null
+++ b/electrum/plugins/coldcard/cmdline.py
@@ -0,0 +1,47 @@
+from electrum.plugin import hook
+from .coldcard import ColdcardPlugin
+from electrum.util import print_msg, print_error, raw_input, print_stderr
+
+class ColdcardCmdLineHandler:
+
+ def get_passphrase(self, msg, confirm):
+ raise NotImplementedError
+
+ def get_pin(self, msg):
+ raise NotImplementedError
+
+ def prompt_auth(self, msg):
+ raise NotImplementedError
+
+ def yes_no_question(self, msg):
+ print_msg(msg)
+ return raw_input() in 'yY'
+
+ def stop(self):
+ pass
+
+ def show_message(self, msg, on_cancel=None):
+ print_stderr(msg)
+
+ def show_error(self, msg, blocking=False):
+ print_error(msg)
+
+ def update_status(self, b):
+ print_error('hw device status', b)
+
+ def finished(self):
+ pass
+
+class Plugin(ColdcardPlugin):
+ handler = ColdcardCmdLineHandler()
+
+ @hook
+ def init_keystore(self, keystore):
+ if not isinstance(keystore, self.keystore_class):
+ return
+ keystore.handler = self.handler
+
+ def create_handler(self, window):
+ return self.handler
+
+# EOF
diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py
new file mode 100644
index 000000000..5327aa5eb
--- /dev/null
+++ b/electrum/plugins/coldcard/coldcard.py
@@ -0,0 +1,693 @@
+#
+# Coldcard Electrum plugin main code.
+#
+#
+from struct import pack, unpack
+import hashlib
+import os, sys, time, io
+import traceback
+
+from electrum import bitcoin
+from electrum.bitcoin import serialize_xpub, deserialize_xpub, InvalidMasterKeyVersionBytes
+from electrum import constants
+from electrum.bitcoin import TYPE_ADDRESS, int_to_hex
+from electrum.i18n import _
+from electrum.plugin import BasePlugin, Device
+from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub
+from electrum.transaction import Transaction
+from electrum.wallet import Standard_Wallet
+from electrum.crypto import hash_160
+from ..hw_wallet import HW_PluginBase
+from ..hw_wallet.plugin import is_any_tx_output_on_change_branch
+from electrum.util import print_error, bfh, bh2u, versiontuple
+from electrum.base_wizard import ScriptTypeNotSupported
+
+try:
+ import hid
+ from ckcc.protocol import CCProtocolPacker, CCProtocolUnpacker
+ from ckcc.protocol import CCProtoError, CCUserRefused, CCBusyError
+ from ckcc.constants import (MAX_MSG_LEN, MAX_BLK_LEN, MSG_SIGNING_MAX_LENGTH, MAX_TXN_LEN,
+ AF_CLASSIC, AF_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH)
+ from ckcc.constants import (
+ PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO,
+ PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT,
+ PSBT_IN_BIP32_DERIVATION, PSBT_OUT_BIP32_DERIVATION)
+
+ from ckcc.client import ColdcardDevice, COINKITE_VID, CKCC_PID, CKCC_SIMULATOR_PATH
+
+ requirements_ok = True
+
+
+ class ElectrumColdcardDevice(ColdcardDevice):
+ # avoid use of pycoin for MiTM message signature test
+ def mitm_verify(self, sig, expect_xpub):
+ # verify a signature (65 bytes) over the session key, using the master bip32 node
+ # - customized to use specific EC library of Electrum.
+ from electrum.ecc import ECPubkey
+
+ xtype, depth, parent_fingerprint, child_number, chain_code, K_or_k \
+ = bitcoin.deserialize_xpub(expect_xpub)
+
+ pubkey = ECPubkey(K_or_k)
+ try:
+ pubkey.verify_message_hash(sig[1:65], self.session_key)
+ return True
+ except:
+ return False
+
+except ImportError:
+ requirements_ok = False
+
+ COINKITE_VID = 0xd13e
+ CKCC_PID = 0xcc10
+
+CKCC_SIMULATED_PID = CKCC_PID ^ 0x55aa
+
+def my_var_int(l):
+ # Bitcoin serialization of integers... directly into binary!
+ if l < 253:
+ return pack("B", l)
+ elif l < 0x10000:
+ return pack("' % (self.dev.master_fingerprint,
+ self.label())
+
+ def verify_connection(self, expected_xfp, expected_xpub):
+ ex = (expected_xfp, expected_xpub)
+
+ if self._expected_device == ex:
+ # all is as expected
+ return
+
+ if ( (self._expected_device is not None)
+ or (self.dev.master_fingerprint != expected_xfp)
+ or (self.dev.master_xpub != expected_xpub)):
+ # probably indicating programing error, not hacking
+ raise RuntimeError("Expecting 0x%08x but that's not whats connected?!" %
+ expected_xfp)
+
+ # check signature over session key
+ # - mitm might have lied about xfp and xpub up to here
+ # - important that we use value capture at wallet creation time, not some value
+ # we read over USB today
+ self.dev.check_mitm(expected_xpub=expected_xpub)
+
+ self._expected_device = ex
+
+ print_error("[coldcard]", "Successfully verified against MiTM")
+
+ def is_pairable(self):
+ # can't do anything w/ devices that aren't setup (but not normally reachable)
+ return bool(self.dev.master_xpub)
+
+ def timeout(self, cutoff):
+ # nothing to do?
+ pass
+
+ def close(self):
+ # close the HID device (so can be reused)
+ self.dev.close()
+ self.dev = None
+
+ def is_initialized(self):
+ return bool(self.dev.master_xpub)
+
+ def label(self):
+ # 'label' of this Coldcard. Warning: gets saved into wallet file, which might
+ # not be encrypted, so better for privacy if based on xpub/fingerprint rather than
+ # USB serial number.
+ if self.dev.is_simulator:
+ lab = 'Coldcard Simulator 0x%08x' % self.dev.master_fingerprint
+ elif not self.dev.master_fingerprint:
+ # failback; not expected
+ lab = 'Coldcard #' + self.dev.serial
+ else:
+ lab = 'Coldcard 0x%08x' % self.dev.master_fingerprint
+
+ # Hack zone: during initial setup I need the xfp and master xpub but
+ # very few objects are passed between the various steps of base_wizard.
+ # Solution: return a string with some hidden metadata
+ # - see
+ # - needs to work w/ deepcopy
+ class LabelStr(str):
+ def __new__(cls, s, xfp=None, xpub=None):
+ self = super().__new__(cls, str(s))
+ self.xfp = getattr(s, 'xfp', xfp)
+ self.xpub = getattr(s, 'xpub', xpub)
+ return self
+
+ return LabelStr(lab, self.dev.master_fingerprint, self.dev.master_xpub)
+
+ def has_usable_connection_with_device(self):
+ # Do end-to-end ping test
+ try:
+ self.ping_check()
+ return True
+ except:
+ return False
+
+ def get_xpub(self, bip32_path, xtype):
+ assert xtype in ColdcardPlugin.SUPPORTED_XTYPES
+ print_error('[coldcard]', 'Derive xtype = %r' % xtype)
+ xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000)
+ # TODO handle timeout?
+ # change type of xpub to the requested type
+ try:
+ __, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
+ except InvalidMasterKeyVersionBytes:
+ raise Exception(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.')
+ .format(self.device)) from None
+ if xtype != 'standard':
+ xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
+ return xpub
+
+ def ping_check(self):
+ # check connection is working
+ assert self.dev.session_key, 'not encrypted?'
+ req = b'1234 Electrum Plugin 4321' # free up to 59 bytes
+ try:
+ echo = self.dev.send_recv(CCProtocolPacker.ping(req))
+ assert echo == req
+ except:
+ raise RuntimeError("Communication trouble with Coldcard")
+
+ def show_address(self, path, addr_fmt):
+ # prompt user w/ addres, also returns it immediately.
+ return self.dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None)
+
+ def get_version(self):
+ # gives list of strings
+ return self.dev.send_recv(CCProtocolPacker.version(), timeout=1000).split('\n')
+
+ def sign_message_start(self, path, msg):
+ # this starts the UX experience.
+ self.dev.send_recv(CCProtocolPacker.sign_message(msg, path), timeout=None)
+
+ def sign_message_poll(self):
+ # poll device... if user has approved, will get tuple: (addr, sig) else None
+ return self.dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None)
+
+ def sign_transaction_start(self, raw_psbt, finalize=True):
+ # Multiple steps to sign:
+ # - upload binary
+ # - start signing UX
+ # - wait for coldcard to complete process, or have it refused.
+ # - download resulting txn
+ assert 20 <= len(raw_psbt) < MAX_TXN_LEN, 'PSBT is too big'
+ dlen, chk = self.dev.upload_file(raw_psbt)
+
+ resp = self.dev.send_recv(CCProtocolPacker.sign_transaction(dlen, chk, finalize=finalize),
+ timeout=None)
+
+ if resp != None:
+ raise ValueError(resp)
+
+ def sign_transaction_poll(self):
+ # poll device... if user has approved, will get tuple: (legnth, checksum) else None
+ return self.dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)
+
+ def download_file(self, length, checksum, file_number=1):
+ # get a file
+ return self.dev.download_file(length, checksum, file_number=file_number)
+
+
+
+class Coldcard_KeyStore(Hardware_KeyStore):
+ hw_type = 'coldcard'
+ device = 'Coldcard'
+
+ def __init__(self, d):
+ Hardware_KeyStore.__init__(self, d)
+ # Errors and other user interaction is done through the wallet's
+ # handler. The handler is per-window and preserved across
+ # device reconnects
+ self.force_watching_only = False
+ self.ux_busy = False
+
+ # Seems like only the derivation path and resulting **derived** xpub is stored in
+ # the wallet file... however, we need to know at least the fingerprint of the master
+ # xpub to verify against MiTM, and also so we can put the right value into the subkey paths
+ # of PSBT files that might be generated offline.
+ # - save the fingerprint of the master xpub, as "xfp"
+ # - it's a LE32 int, but hex more natural way to see it
+ # - device reports these value during encryption setup process
+ lab = d['label']
+ if hasattr(lab, 'xfp'):
+ # initial setup
+ self.ckcc_xfp = lab.xfp
+ self.ckcc_xpub = lab.xpub
+ else:
+ # wallet load: fatal if missing, we need them!
+ self.ckcc_xfp = d['ckcc_xfp']
+ self.ckcc_xpub = d['ckcc_xpub']
+
+ def dump(self):
+ # our additions to the stored data about keystore -- only during creation?
+ d = Hardware_KeyStore.dump(self)
+
+ d['ckcc_xfp'] = self.ckcc_xfp
+ d['ckcc_xpub'] = self.ckcc_xpub
+
+ return d
+
+ def get_derivation(self):
+ return self.derivation
+
+ def get_client(self):
+ # called when user tries to do something like view address, sign somthing.
+ # - not called during probing/setup
+ rv = self.plugin.get_client(self)
+ if rv:
+ rv.verify_connection(self.ckcc_xfp, self.ckcc_xpub)
+
+ return rv
+
+ def give_error(self, message, clear_client=False):
+ print_error(message)
+ if not self.ux_busy:
+ self.handler.show_error(message)
+ else:
+ self.ux_busy = False
+ if clear_client:
+ self.client = None
+ raise Exception(message)
+
+ def wrap_busy(func):
+ # decorator: function takes over the UX on the device.
+ def wrapper(self, *args, **kwargs):
+ try:
+ self.ux_busy = True
+ return func(self, *args, **kwargs)
+ finally:
+ self.ux_busy = False
+ return wrapper
+
+ def decrypt_message(self, pubkey, message, password):
+ raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
+
+ @wrap_busy
+ def sign_message(self, sequence, message, password):
+ # Sign a message on device. Since we have big screen, of course we
+ # have to show the message unabiguously there first!
+ try:
+ msg = message.encode('ascii', errors='strict')
+ assert 1 <= len(msg) <= MSG_SIGNING_MAX_LENGTH
+ except (UnicodeError, AssertionError):
+ # there are other restrictions on message content,
+ # but let the device enforce and report those
+ self.handler.show_error('Only short (%d max) ASCII messages can be signed.'
+ % MSG_SIGNING_MAX_LENGTH)
+ return b''
+
+ client = self.get_client()
+ path = self.get_derivation() + ("/%d/%d" % sequence)
+ try:
+ cl = self.get_client()
+ try:
+ self.handler.show_message("Signing message (using %s)..." % path)
+
+ cl.sign_message_start(path, msg)
+
+ while 1:
+ # How to kill some time, without locking UI?
+ time.sleep(0.250)
+
+ resp = cl.sign_message_poll()
+ if resp is not None:
+ break
+
+ finally:
+ self.handler.finished()
+
+ assert len(resp) == 2
+ addr, raw_sig = resp
+
+ # already encoded in Bitcoin fashion, binary.
+ assert 40 < len(raw_sig) <= 65
+
+ return raw_sig
+
+ except (CCUserRefused, CCBusyError) as exc:
+ self.handler.show_error(str(exc))
+ except CCProtoError as exc:
+ traceback.print_exc(file=sys.stderr)
+ self.handler.show_error('{}\n\n{}'.format(
+ _('Error showing address') + ':', str(exc)))
+ except Exception as e:
+ self.give_error(e, True)
+
+ # give empty bytes for error cases; it seems to clear the old signature box
+ return b''
+
+ def build_psbt(self, tx, wallet=None, xfp=None):
+ # Render a PSBT file, for upload to Coldcard.
+ #
+ if xfp is None:
+ # need fingerprint of MASTER xpub, not the derived key
+ xfp = self.ckcc_xfp
+
+ inputs = tx.inputs()
+
+ if 'prev_tx' not in inputs[0]:
+ # fetch info about inputs, if needed?
+ # - needed during export PSBT flow, not normal online signing
+ assert wallet, 'need wallet reference'
+ wallet.add_hw_info(tx)
+
+ # wallet.add_hw_info installs this attr
+ assert hasattr(tx, 'output_info'), 'need data about outputs'
+
+ # Build map of pubkey needed as derivation from master, in PSBT binary format
+ # 1) binary version of the common subpath for all keys
+ # m/ => fingerprint LE32
+ # a/b/c => ints
+ base_path = pack(' not multisig, must be bip32
+ if type(wallet) is not Standard_Wallet:
+ keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device))
+ return
+
+ sequence = wallet.get_address_index(address)
+ txin_type = wallet.get_txin_type(address)
+ keystore.show_address(sequence, txin_type)
+
+# EOF
diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py
new file mode 100644
index 000000000..7e9c897f4
--- /dev/null
+++ b/electrum/plugins/coldcard/qt.py
@@ -0,0 +1,242 @@
+import time
+
+from electrum.i18n import _
+from electrum.plugin import hook
+from electrum.wallet import Standard_Wallet
+from electrum.gui.qt.util import *
+
+from .coldcard import ColdcardPlugin
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+
+
+class Plugin(ColdcardPlugin, QtPluginBase):
+ icon_unpaired = ":icons/coldcard_unpaired.png"
+ icon_paired = ":icons/coldcard.png"
+
+ def create_handler(self, window):
+ return Coldcard_Handler(window)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if type(wallet) is not Standard_Wallet:
+ return
+ keystore = wallet.get_keystore()
+ if type(keystore) == self.keystore_class and len(addrs) == 1:
+ def show_address():
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
+ menu.addAction(_("Show on Coldcard"), show_address)
+
+ @hook
+ def transaction_dialog(self, dia):
+ # see gui/qt/transaction_dialog.py
+
+ keystore = dia.wallet.get_keystore()
+ if type(keystore) != self.keystore_class:
+ # not a Coldcard wallet, hide feature
+ return
+
+ # - add a new button, near "export"
+ btn = QPushButton(_("Save PSBT"))
+ btn.clicked.connect(lambda unused: self.export_psbt(dia))
+ if dia.tx.is_complete():
+ # but disable it for signed transactions (nothing to do if already signed)
+ btn.setDisabled(True)
+
+ dia.sharing_buttons.append(btn)
+
+ def export_psbt(self, dia):
+ # Called from hook in transaction dialog
+ tx = dia.tx
+
+ if tx.is_complete():
+ # if they sign while dialog is open, it can transition from unsigned to signed,
+ # which we don't support here, so do nothing
+ return
+
+ # can only expect Coldcard wallets to work with these files (right now)
+ keystore = dia.wallet.get_keystore()
+ assert type(keystore) == self.keystore_class
+
+ # convert to PSBT
+ raw_psbt = keystore.build_psbt(tx, wallet=dia.wallet)
+
+ name = (dia.wallet.basename() + time.strftime('-%y%m%d-%H%M.psbt')).replace(' ', '-')
+ fileName = dia.main_window.getSaveFileName(_("Select where to save the PSBT file"),
+ name, "*.psbt")
+ if fileName:
+ with open(fileName, "wb+") as f:
+ f.write(raw_psbt)
+ dia.show_message(_("Transaction exported successfully"))
+ dia.saved = True
+
+ def show_settings_dialog(self, window, keystore):
+ # When they click on the icon for CC we come here.
+ device_id = self.choose_device(window, keystore)
+ if device_id:
+ CKCCSettingsDialog(window, self, keystore, device_id).exec_()
+
+
+class Coldcard_Handler(QtHandlerBase):
+ setup_signal = pyqtSignal()
+ #auth_signal = pyqtSignal(object)
+
+ def __init__(self, win):
+ super(Coldcard_Handler, self).__init__(win, 'Coldcard')
+ self.setup_signal.connect(self.setup_dialog)
+ #self.auth_signal.connect(self.auth_dialog)
+
+
+ def message_dialog(self, msg):
+ self.clear_dialog()
+ self.dialog = dialog = WindowModalDialog(self.top_level_window(), _("Coldcard Status"))
+ l = QLabel(msg)
+ vbox = QVBoxLayout(dialog)
+ vbox.addWidget(l)
+ dialog.show()
+
+ def get_setup(self):
+ self.done.clear()
+ self.setup_signal.emit()
+ self.done.wait()
+ return
+
+ def setup_dialog(self):
+ self.show_error(_('Please initialization your Coldcard while disconnected.'))
+ return
+
+class CKCCSettingsDialog(WindowModalDialog):
+ '''This dialog doesn't require a device be paired with a wallet.
+ We want users to be able to wipe a device even if they've forgotten
+ their PIN.'''
+
+ def __init__(self, window, plugin, keystore, device_id):
+ title = _("{} Settings").format(plugin.device)
+ super(CKCCSettingsDialog, self).__init__(window, title)
+ self.setMaximumWidth(540)
+
+ devmgr = plugin.device_manager()
+ config = devmgr.config
+ handler = keystore.handler
+ self.thread = thread = keystore.thread
+
+ def connect_and_doit():
+ client = devmgr.client_by_id(device_id)
+ if not client:
+ raise RuntimeError("Device not connected")
+ return client
+
+ body = QWidget()
+ body_layout = QVBoxLayout(body)
+ grid = QGridLayout()
+ grid.setColumnStretch(2, 1)
+
+ # see
+ title = QLabel('''
+Coldcard Wallet
+from Coinkite Inc.
+coldcardwallet.com ''')
+ title.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
+
+ grid.addWidget(title , 0,0, 1,2, Qt.AlignHCenter)
+ y = 3
+
+ rows = [
+ ('fw_version', _("Firmware Version")),
+ ('fw_built', _("Build Date")),
+ ('bl_version', _("Bootloader")),
+ ('xfp', _("Master Fingerprint")),
+ ('serial', _("USB Serial")),
+ ]
+ for row_num, (member_name, label) in enumerate(rows):
+ widget = QLabel('000000000000')
+ widget.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard)
+
+ grid.addWidget(QLabel(label), y, 0, 1,1, Qt.AlignRight)
+ grid.addWidget(widget, y, 1, 1, 1, Qt.AlignLeft)
+ setattr(self, member_name, widget)
+ y += 1
+ body_layout.addLayout(grid)
+
+ upg_btn = QPushButton('Upgrade')
+ #upg_btn.setDefault(False)
+ def _start_upgrade():
+ thread.add(connect_and_doit, on_success=self.start_upgrade)
+ upg_btn.clicked.connect(_start_upgrade)
+
+ y += 3
+ grid.addWidget(upg_btn, y, 0)
+ grid.addWidget(CloseButton(self), y, 1)
+
+ dialog_vbox = QVBoxLayout(self)
+ dialog_vbox.addWidget(body)
+
+ # Fetch values and show them
+ thread.add(connect_and_doit, on_success=self.show_values)
+
+ def show_values(self, client):
+ dev = client.dev
+
+ self.xfp.setText('0x%08x' % dev.master_fingerprint)
+ self.serial.setText('%s' % dev.serial)
+
+ # ask device for versions: allow extras for future
+ fw_date, fw_rel, bl_rel, *rfu = client.get_version()
+
+ self.fw_version.setText('%s' % fw_rel)
+ self.fw_built.setText('%s' % fw_date)
+ self.bl_version.setText('%s' % bl_rel)
+
+ def start_upgrade(self, client):
+ # ask for a filename (must have already downloaded it)
+ mw = get_parent_main_window(self)
+ dev = client.dev
+
+ fileName = mw.getOpenFileName("Select upgraded firmware file", "*.dfu")
+ if not fileName:
+ return
+
+ from ckcc.utils import dfu_parse
+ from ckcc.sigheader import FW_HEADER_SIZE, FW_HEADER_OFFSET, FW_HEADER_MAGIC
+ from ckcc.protocol import CCProtocolPacker
+ from hashlib import sha256
+ import struct
+
+ try:
+ with open(fileName, 'rb') as fd:
+
+ # unwrap firmware from the DFU
+ offset, size, *ignored = dfu_parse(fd)
+
+ fd.seek(offset)
+ firmware = fd.read(size)
+
+ hpos = FW_HEADER_OFFSET
+ hdr = bytes(firmware[hpos:hpos + FW_HEADER_SIZE]) # needed later too
+ magic = struct.unpack_from(" v2.0.0
+
+
+ def close(self):
+ if self.opened:
+ try:
+ self.dbb_hid.close()
+ except:
+ pass
+ self.opened = False
+
+
+ def timeout(self, cutoff):
+ pass
+
+
+ def label(self):
+ return " "
+
+
+ def is_pairable(self):
+ return True
+
+
+ def is_initialized(self):
+ return self.dbb_has_password()
+
+
+ def is_paired(self):
+ return self.password is not None
+
+ def has_usable_connection_with_device(self):
+ try:
+ self.dbb_has_password()
+ except BaseException:
+ return False
+ return True
+
+ def _get_xpub(self, bip32_path):
+ if self.check_device_dialog():
+ return self.hid_send_encrypt(('{"xpub": "%s"}' % bip32_path).encode('utf8'))
+
+
+ def get_xpub(self, bip32_path, xtype):
+ assert xtype in self.plugin.SUPPORTED_XTYPES
+ reply = self._get_xpub(bip32_path)
+ if reply:
+ xpub = reply['xpub']
+ # Change type of xpub to the requested type. The firmware
+ # only ever returns the mainnet standard type, but it is agnostic
+ # to the type when signing.
+ if xtype != 'standard' or constants.net.TESTNET:
+ _, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub, net=constants.BitcoinMainnet)
+ xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
+ return xpub
+ else:
+ raise Exception('no reply')
+
+
+ def dbb_has_password(self):
+ reply = self.hid_send_plain(b'{"ping":""}')
+ if 'ping' not in reply:
+ raise Exception(_('Device communication error. Please unplug and replug your Digital Bitbox.'))
+ if reply['ping'] == 'password':
+ return True
+ return False
+
+
+ def stretch_key(self, key):
+ import hmac
+ return to_hexstr(hashlib.pbkdf2_hmac('sha512', key.encode('utf-8'), b'Digital Bitbox', iterations = 20480))
+
+
+ def backup_password_dialog(self):
+ msg = _("Enter the password used when the backup was created:")
+ while True:
+ password = self.handler.get_passphrase(msg, False)
+ if password is None:
+ return None
+ if len(password) < 4:
+ msg = _("Password must have at least 4 characters.") \
+ + "\n\n" + _("Enter password:")
+ elif len(password) > 64:
+ msg = _("Password must have less than 64 characters.") \
+ + "\n\n" + _("Enter password:")
+ else:
+ return password.encode('utf8')
+
+
+ def password_dialog(self, msg):
+ while True:
+ password = self.handler.get_passphrase(msg, False)
+ if password is None:
+ return False
+ if len(password) < 4:
+ msg = _("Password must have at least 4 characters.") + \
+ "\n\n" + _("Enter password:")
+ elif len(password) > 64:
+ msg = _("Password must have less than 64 characters.") + \
+ "\n\n" + _("Enter password:")
+ else:
+ self.password = password.encode('utf8')
+ return True
+
+
+ def check_device_dialog(self):
+ # Set password if fresh device
+ if self.password is None and not self.dbb_has_password():
+ if not self.setupRunning:
+ return False # A fresh device cannot connect to an existing wallet
+ msg = _("An uninitialized Digital Bitbox is detected.") + " " + \
+ _("Enter a new password below.") + "\n\n" + \
+ _("REMEMBER THE PASSWORD!") + "\n\n" + \
+ _("You cannot access your coins or a backup without the password.") + "\n" + \
+ _("A backup is saved automatically when generating a new wallet.")
+ if self.password_dialog(msg):
+ reply = self.hid_send_plain(b'{"password":"' + self.password + b'"}')
+ else:
+ return False
+
+ # Get password from user if not yet set
+ msg = _("Enter your Digital Bitbox password:")
+ while self.password is None:
+ if not self.password_dialog(msg):
+ raise UserCancelled()
+ reply = self.hid_send_encrypt(b'{"led":"blink"}')
+ if 'error' in reply:
+ self.password = None
+ if reply['error']['code'] == 109:
+ msg = _("Incorrect password entered.") + "\n\n" + \
+ reply['error']['message'] + "\n\n" + \
+ _("Enter your Digital Bitbox password:")
+ else:
+ # Should never occur
+ msg = _("Unexpected error occurred.") + "\n\n" + \
+ reply['error']['message'] + "\n\n" + \
+ _("Enter your Digital Bitbox password:")
+
+ # Initialize device if not yet initialized
+ if not self.setupRunning:
+ self.isInitialized = True # Wallet exists. Electrum code later checks if the device matches the wallet
+ elif not self.isInitialized:
+ reply = self.hid_send_encrypt(b'{"device":"info"}')
+ if reply['device']['id'] != "":
+ self.recover_or_erase_dialog() # Already seeded
+ else:
+ self.seed_device_dialog() # Seed if not initialized
+ self.mobile_pairing_dialog()
+ return self.isInitialized
+
+
+ def recover_or_erase_dialog(self):
+ msg = _("The Digital Bitbox is already seeded. Choose an option:") + "\n"
+ choices = [
+ (_("Create a wallet using the current seed")),
+ (_("Load a wallet from the micro SD card (the current seed is overwritten)")),
+ (_("Erase the Digital Bitbox"))
+ ]
+ try:
+ reply = self.handler.win.query_choice(msg, choices)
+ except Exception:
+ return # Back button pushed
+ if reply == 2:
+ self.dbb_erase()
+ elif reply == 1:
+ if not self.dbb_load_backup():
+ return
+ else:
+ if self.hid_send_encrypt(b'{"device":"info"}')['device']['lock']:
+ raise Exception(_("Full 2FA enabled. This is not supported yet."))
+ # Use existing seed
+ self.isInitialized = True
+
+
+ def seed_device_dialog(self):
+ msg = _("Choose how to initialize your Digital Bitbox:") + "\n"
+ choices = [
+ (_("Generate a new random wallet")),
+ (_("Load a wallet from the micro SD card"))
+ ]
+ try:
+ reply = self.handler.win.query_choice(msg, choices)
+ except Exception:
+ return # Back button pushed
+ if reply == 0:
+ self.dbb_generate_wallet()
+ else:
+ if not self.dbb_load_backup(show_msg=False):
+ return
+ self.isInitialized = True
+
+ def mobile_pairing_dialog(self):
+ dbb_user_dir = None
+ if sys.platform == 'darwin':
+ dbb_user_dir = os.path.join(os.environ.get("HOME", ""), "Library", "Application Support", "DBB")
+ elif sys.platform == 'win32':
+ dbb_user_dir = os.path.join(os.environ["APPDATA"], "DBB")
+ else:
+ dbb_user_dir = os.path.join(os.environ["HOME"], ".dbb")
+
+ if not dbb_user_dir:
+ return
+
+ try:
+ # Python 3.5+
+ jsonDecodeError = json.JSONDecodeError
+ except AttributeError:
+ jsonDecodeError = ValueError
+ try:
+ with open(os.path.join(dbb_user_dir, "config.dat")) as f:
+ dbb_config = json.load(f)
+ except (FileNotFoundError, jsonDecodeError):
+ return
+
+ if 'encryptionprivkey' not in dbb_config or 'comserverchannelid' not in dbb_config:
+ return
+
+ choices = [
+ _('Do not pair'),
+ _('Import pairing from the Digital Bitbox desktop app'),
+ ]
+ try:
+ reply = self.handler.win.query_choice(_('Mobile pairing options'), choices)
+ except Exception:
+ return # Back button pushed
+
+ if reply == 0:
+ if self.plugin.is_mobile_paired():
+ del self.plugin.digitalbitbox_config['encryptionprivkey']
+ del self.plugin.digitalbitbox_config['comserverchannelid']
+ elif reply == 1:
+ # import pairing from dbb app
+ self.plugin.digitalbitbox_config['encryptionprivkey'] = dbb_config['encryptionprivkey']
+ self.plugin.digitalbitbox_config['comserverchannelid'] = dbb_config['comserverchannelid']
+ self.plugin.config.set_key('digitalbitbox', self.plugin.digitalbitbox_config)
+
+ def dbb_generate_wallet(self):
+ key = self.stretch_key(self.password)
+ filename = ("Electrum-" + time.strftime("%Y-%m-%d-%H-%M-%S") + ".pdf")
+ msg = ('{"seed":{"source": "create", "key": "%s", "filename": "%s", "entropy": "%s"}}' % (key, filename, 'Digital Bitbox Electrum Plugin')).encode('utf8')
+ reply = self.hid_send_encrypt(msg)
+ if 'error' in reply:
+ raise Exception(reply['error']['message'])
+
+
+ def dbb_erase(self):
+ self.handler.show_message(_("Are you sure you want to erase the Digital Bitbox?") + "\n\n" +
+ _("To continue, touch the Digital Bitbox's light for 3 seconds.") + "\n\n" +
+ _("To cancel, briefly touch the light or wait for the timeout."))
+ hid_reply = self.hid_send_encrypt(b'{"reset":"__ERASE__"}')
+ self.handler.finished()
+ if 'error' in hid_reply:
+ raise Exception(hid_reply['error']['message'])
+ else:
+ self.password = None
+ raise Exception('Device erased')
+
+
+ def dbb_load_backup(self, show_msg=True):
+ backups = self.hid_send_encrypt(b'{"backup":"list"}')
+ if 'error' in backups:
+ raise Exception(backups['error']['message'])
+ try:
+ f = self.handler.win.query_choice(_("Choose a backup file:"), backups['backup'])
+ except Exception:
+ return False # Back button pushed
+ key = self.backup_password_dialog()
+ if key is None:
+ raise Exception('Canceled by user')
+ key = self.stretch_key(key)
+ if show_msg:
+ self.handler.show_message(_("Loading backup...") + "\n\n" +
+ _("To continue, touch the Digital Bitbox's light for 3 seconds.") + "\n\n" +
+ _("To cancel, briefly touch the light or wait for the timeout."))
+ msg = ('{"seed":{"source": "backup", "key": "%s", "filename": "%s"}}' % (key, backups['backup'][f])).encode('utf8')
+ hid_reply = self.hid_send_encrypt(msg)
+ self.handler.finished()
+ if 'error' in hid_reply:
+ raise Exception(hid_reply['error']['message'])
+ return True
+
+
+ def hid_send_frame(self, data):
+ HWW_CID = 0xFF000000
+ HWW_CMD = 0x80 + 0x40 + 0x01
+ data_len = len(data)
+ seq = 0;
+ idx = 0;
+ write = []
+ while idx < data_len:
+ if idx == 0:
+ # INIT frame
+ write = data[idx : idx + min(data_len, self.usbReportSize - 7)]
+ self.dbb_hid.write(b'\0' + struct.pack(">IBH", HWW_CID, HWW_CMD, data_len & 0xFFFF) + write + b'\xEE' * (self.usbReportSize - 7 - len(write)))
+ else:
+ # CONT frame
+ write = data[idx : idx + min(data_len, self.usbReportSize - 5)]
+ self.dbb_hid.write(b'\0' + struct.pack(">IB", HWW_CID, seq) + write + b'\xEE' * (self.usbReportSize - 5 - len(write)))
+ seq += 1
+ idx += len(write)
+
+
+ def hid_read_frame(self):
+ # INIT response
+ read = bytearray(self.dbb_hid.read(self.usbReportSize))
+ cid = ((read[0] * 256 + read[1]) * 256 + read[2]) * 256 + read[3]
+ cmd = read[4]
+ data_len = read[5] * 256 + read[6]
+ data = read[7:]
+ idx = len(read) - 7;
+ while idx < data_len:
+ # CONT response
+ read = bytearray(self.dbb_hid.read(self.usbReportSize))
+ data += read[5:]
+ idx += len(read) - 5
+ return data
+
+
+ def hid_send_plain(self, msg):
+ reply = ""
+ try:
+ serial_number = self.dbb_hid.get_serial_number_string()
+ if "v2.0." in serial_number or "v1." in serial_number:
+ hidBufSize = 4096
+ self.dbb_hid.write('\0' + msg + '\0' * (hidBufSize - len(msg)))
+ r = bytearray()
+ while len(r) < hidBufSize:
+ r += bytearray(self.dbb_hid.read(hidBufSize))
+ else:
+ self.hid_send_frame(msg)
+ r = self.hid_read_frame()
+ r = r.rstrip(b' \t\r\n\0')
+ r = r.replace(b"\0", b'')
+ r = to_string(r, 'utf8')
+ reply = json.loads(r)
+ except Exception as e:
+ print_error('Exception caught ' + str(e))
+ return reply
+
+
+ def hid_send_encrypt(self, msg):
+ reply = ""
+ try:
+ secret = Hash(self.password)
+ msg = EncodeAES(secret, msg)
+ reply = self.hid_send_plain(msg)
+ if 'ciphertext' in reply:
+ reply = DecodeAES(secret, ''.join(reply["ciphertext"]))
+ reply = to_string(reply, 'utf8')
+ reply = json.loads(reply)
+ if 'error' in reply:
+ self.password = None
+ except Exception as e:
+ print_error('Exception caught ' + str(e))
+ return reply
+
+
+
+# ----------------------------------------------------------------------------------
+#
+#
+
+class DigitalBitbox_KeyStore(Hardware_KeyStore):
+ hw_type = 'digitalbitbox'
+ device = 'DigitalBitbox'
+
+
+ def __init__(self, d):
+ Hardware_KeyStore.__init__(self, d)
+ self.force_watching_only = False
+ self.maxInputs = 14 # maximum inputs per single sign command
+
+
+ def get_derivation(self):
+ return str(self.derivation)
+
+
+ def is_p2pkh(self):
+ return self.derivation.startswith("m/44'/")
+
+
+ def give_error(self, message, clear_client = False):
+ if clear_client:
+ self.client = None
+ raise Exception(message)
+
+
+ def decrypt_message(self, pubkey, message, password):
+ raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
+
+
+ def sign_message(self, sequence, message, password):
+ sig = None
+ try:
+ message = message.encode('utf8')
+ inputPath = self.get_derivation() + "/%d/%d" % sequence
+ msg_hash = Hash(msg_magic(message))
+ inputHash = to_hexstr(msg_hash)
+ hasharray = []
+ hasharray.append({'hash': inputHash, 'keypath': inputPath})
+ hasharray = json.dumps(hasharray)
+
+ msg = ('{"sign":{"meta":"sign message", "data":%s}}' % hasharray).encode('utf8')
+
+ dbb_client = self.plugin.get_client(self)
+
+ if not dbb_client.is_paired():
+ raise Exception(_("Could not sign message."))
+
+ reply = dbb_client.hid_send_encrypt(msg)
+ self.handler.show_message(_("Signing message ...") + "\n\n" +
+ _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" +
+ _("To cancel, briefly touch the blinking light or wait for the timeout."))
+ reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)
+ self.handler.finished()
+
+ if 'error' in reply:
+ raise Exception(reply['error']['message'])
+
+ if 'sign' not in reply:
+ raise Exception(_("Could not sign message."))
+
+ if 'recid' in reply['sign'][0]:
+ # firmware > v2.1.1
+ sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
+ recid = int(reply['sign'][0]['recid'], 16)
+ sig = ecc.construct_sig65(sig_string, recid, True)
+ pubkey, compressed = ecc.ECPubkey.from_signature65(sig, msg_hash)
+ addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed))
+ if ecc.verify_message_with_address(addr, sig, message) is False:
+ raise Exception(_("Could not sign message"))
+ elif 'pubkey' in reply['sign'][0]:
+ # firmware <= v2.1.1
+ for recid in range(4):
+ sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
+ sig = ecc.construct_sig65(sig_string, recid, True)
+ try:
+ addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey']))
+ if ecc.verify_message_with_address(addr, sig, message):
+ break
+ except Exception:
+ continue
+ else:
+ raise Exception(_("Could not sign message"))
+
+
+ except BaseException as e:
+ self.give_error(e)
+ return sig
+
+
+ def sign_transaction(self, tx, password):
+ if tx.is_complete():
+ return
+
+ try:
+ p2pkhTransaction = True
+ derivations = self.get_tx_derivations(tx)
+ inputhasharray = []
+ hasharray = []
+ pubkeyarray = []
+
+ # Build hasharray from inputs
+ for i, txin in enumerate(tx.inputs()):
+ if txin['type'] == 'coinbase':
+ self.give_error("Coinbase not supported") # should never happen
+
+ if txin['type'] != 'p2pkh':
+ p2pkhTransaction = False
+
+ for x_pubkey in txin['x_pubkeys']:
+ if x_pubkey in derivations:
+ index = derivations.get(x_pubkey)
+ inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1])
+ inputHash = Hash(binascii.unhexlify(tx.serialize_preimage(i)))
+ hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}
+ hasharray.append(hasharray_i)
+ inputhasharray.append(inputHash)
+ break
+ else:
+ self.give_error("No matching x_key for sign_transaction") # should never happen
+
+ # Build pubkeyarray from outputs
+ for o in tx.outputs():
+ assert o.type == TYPE_ADDRESS
+ info = tx.output_info.get(o.address)
+ if info is not None:
+ index = info.address_index
+ changePath = self.get_derivation() + "/%d/%d" % index
+ changePubkey = self.derive_pubkey(index[0], index[1])
+ pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath}
+ pubkeyarray.append(pubkeyarray_i)
+
+ # Special serialization of the unsigned transaction for
+ # the mobile verification app.
+ # At the moment, verification only works for p2pkh transactions.
+ if p2pkhTransaction:
+ class CustomTXSerialization(Transaction):
+ @classmethod
+ def input_script(self, txin, estimate_size=False):
+ if txin['type'] == 'p2pkh':
+ return Transaction.get_preimage_script(txin)
+ if txin['type'] == 'p2sh':
+ # Multisig verification has partial support, but is disabled. This is the
+ # expected serialization though, so we leave it here until we activate it.
+ return '00' + push_script(Transaction.get_preimage_script(txin))
+ raise Exception("unsupported type %s" % txin['type'])
+ tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize_to_network()
+ else:
+ # We only need this for the signing echo / verification.
+ tx_dbb_serialized = None
+
+ # Build sign command
+ dbb_signatures = []
+ steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)
+ for step in range(int(steps)):
+ hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs]
+
+ msg = {
+ "sign": {
+ "data": hashes,
+ "checkpub": pubkeyarray,
+ },
+ }
+ if tx_dbb_serialized is not None:
+ msg["sign"]["meta"] = to_hexstr(Hash(tx_dbb_serialized))
+ msg = json.dumps(msg).encode('ascii')
+ dbb_client = self.plugin.get_client(self)
+
+ if not dbb_client.is_paired():
+ raise Exception("Could not sign transaction.")
+
+ reply = dbb_client.hid_send_encrypt(msg)
+ if 'error' in reply:
+ raise Exception(reply['error']['message'])
+
+ if 'echo' not in reply:
+ raise Exception("Could not sign transaction.")
+
+ if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None:
+ reply['tx'] = tx_dbb_serialized
+ self.plugin.comserver_post_notification(reply)
+
+ if steps > 1:
+ self.handler.show_message(_("Signing large transaction. Please be patient ...") + "\n\n" +
+ _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " +
+ _("(Touch {} of {})").format((step + 1), steps) + "\n\n" +
+ _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n")
+ else:
+ self.handler.show_message(_("Signing transaction...") + "\n\n" +
+ _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" +
+ _("To cancel, briefly touch the blinking light or wait for the timeout."))
+
+ # Send twice, first returns an echo for smart verification
+ reply = dbb_client.hid_send_encrypt(msg)
+ self.handler.finished()
+
+ if 'error' in reply:
+ if reply["error"].get('code') in (600, 601):
+ # aborted via LED short touch or timeout
+ raise UserCancelled()
+ raise Exception(reply['error']['message'])
+
+ if 'sign' not in reply:
+ raise Exception("Could not sign transaction.")
+
+ dbb_signatures.extend(reply['sign'])
+
+ # Fill signatures
+ if len(dbb_signatures) != len(tx.inputs()):
+ raise Exception("Incorrect number of transactions signed.") # Should never occur
+ for i, txin in enumerate(tx.inputs()):
+ num = txin['num_sig']
+ for pubkey in txin['pubkeys']:
+ signatures = list(filter(None, txin['signatures']))
+ if len(signatures) == num:
+ break # txin is complete
+ ii = txin['pubkeys'].index(pubkey)
+ signed = dbb_signatures[i]
+ if 'recid' in signed:
+ # firmware > v2.1.1
+ recid = int(signed['recid'], 16)
+ s = binascii.unhexlify(signed['sig'])
+ h = inputhasharray[i]
+ pk = ecc.ECPubkey.from_sig_string(s, recid, h)
+ pk = pk.get_public_key_hex(compressed=True)
+ elif 'pubkey' in signed:
+ # firmware <= v2.1.1
+ pk = signed['pubkey']
+ if pk != pubkey:
+ continue
+ sig_r = int(signed['sig'][:64], 16)
+ sig_s = int(signed['sig'][64:], 16)
+ sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
+ sig = to_hexstr(sig) + '01'
+ tx.add_signature_to_txin(i, ii, sig)
+ except UserCancelled:
+ raise
+ except BaseException as e:
+ self.give_error(e, True)
+ else:
+ print_error("Transaction is_complete", tx.is_complete())
+ tx.raw = tx.serialize()
+
+
+class DigitalBitboxPlugin(HW_PluginBase):
+
+ libraries_available = DIGIBOX
+ keystore_class = DigitalBitbox_KeyStore
+ client = None
+ DEVICE_IDS = [
+ (0x03eb, 0x2402) # Digital Bitbox
+ ]
+ SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
+
+ def __init__(self, parent, config, name):
+ HW_PluginBase.__init__(self, parent, config, name)
+ if self.libraries_available:
+ self.device_manager().register_devices(self.DEVICE_IDS)
+
+ self.digitalbitbox_config = self.config.get('digitalbitbox', {})
+
+
+ def get_dbb_device(self, device):
+ dev = hid.device()
+ dev.open_path(device.path)
+ return dev
+
+
+ def create_client(self, device, handler):
+ if device.interface_number == 0 or device.usage_page == 0xffff:
+ if handler:
+ self.handler = handler
+ client = self.get_dbb_device(device)
+ if client is not None:
+ client = DigitalBitbox_Client(self, client)
+ return client
+ else:
+ return None
+
+
+ def setup_device(self, device_info, wizard, purpose):
+ devmgr = self.device_manager()
+ device_id = device_info.device.id_
+ client = devmgr.client_by_id(device_id)
+ if client is None:
+ raise Exception(_('Failed to create a client for this device.') + '\n' +
+ _('Make sure it is in the correct state.'))
+ client.handler = self.create_handler(wizard)
+ if purpose == HWD_SETUP_NEW_WALLET:
+ client.setupRunning = True
+ client.get_xpub("m/44'/160'", 'standard')
+
+
+ def is_mobile_paired(self):
+ return 'encryptionprivkey' in self.digitalbitbox_config
+
+
+ def comserver_post_notification(self, payload):
+ assert self.is_mobile_paired(), "unexpected mobile pairing error"
+ url = 'https://digitalbitbox.com/smartverification/index.php'
+ key_s = base64.b64decode(self.digitalbitbox_config['encryptionprivkey'])
+ args = 'c=data&s=0&dt=0&uuid=%s&pl=%s' % (
+ self.digitalbitbox_config['comserverchannelid'],
+ EncodeAES(key_s, json.dumps(payload).encode('ascii')).decode('ascii'),
+ )
+ try:
+ requests.post(url, args)
+ except Exception as e:
+ self.handler.show_error(str(e))
+
+
+ def get_xpub(self, device_id, derivation, xtype, wizard):
+ if xtype not in self.SUPPORTED_XTYPES:
+ raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device))
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ client.handler = self.create_handler(wizard)
+ client.check_device_dialog()
+ xpub = client.get_xpub(derivation, xtype)
+ return xpub
+
+
+ def get_client(self, keystore, force_pair=True):
+ devmgr = self.device_manager()
+ handler = keystore.handler
+ with devmgr.hid_lock:
+ client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
+ if client is not None:
+ client.check_device_dialog()
+ return client
+
+ def show_address(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not self.show_address_helper(wallet, address, keystore):
+ return
+ if type(wallet) is not Standard_Wallet:
+ keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device))
+ return
+ if not self.is_mobile_paired():
+ keystore.handler.show_error(_('This function is only available after pairing your {} with a mobile device.').format(self.device))
+ return
+ if not keystore.is_p2pkh():
+ keystore.handler.show_error(_('This function is only available for p2pkh keystores when using {}.').format(self.device))
+ return
+ change, index = wallet.get_address_index(address)
+ keypath = '%s/%d/%d' % (keystore.derivation, change, index)
+ xpub = self.get_client(keystore)._get_xpub(keypath)
+ verify_request_payload = {
+ "type": 'p2pkh',
+ "echo": xpub['echo'],
+ }
+ self.comserver_post_notification(verify_request_payload)
diff --git a/electrum/plugins/digitalbitbox/qt.py b/electrum/plugins/digitalbitbox/qt.py
new file mode 100644
index 000000000..594756969
--- /dev/null
+++ b/electrum/plugins/digitalbitbox/qt.py
@@ -0,0 +1,43 @@
+from functools import partial
+
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+from .digitalbitbox import DigitalBitboxPlugin
+
+from electrum.i18n import _
+from electrum.plugin import hook
+from electrum.wallet import Standard_Wallet
+
+
+class Plugin(DigitalBitboxPlugin, QtPluginBase):
+ icon_unpaired = ":icons/digitalbitbox_unpaired.png"
+ icon_paired = ":icons/digitalbitbox.png"
+
+ def create_handler(self, window):
+ return DigitalBitbox_Handler(window)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if type(wallet) is not Standard_Wallet:
+ return
+
+ keystore = wallet.get_keystore()
+ if type(keystore) is not self.keystore_class:
+ return
+
+ if not self.is_mobile_paired():
+ return
+
+ if not keystore.is_p2pkh():
+ return
+
+ if len(addrs) == 1:
+ def show_address():
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
+
+ menu.addAction(_("Show on {}").format(self.device), show_address)
+
+
+class DigitalBitbox_Handler(QtHandlerBase):
+
+ def __init__(self, win):
+ super(DigitalBitbox_Handler, self).__init__(win, 'Digital Bitbox')
diff --git a/electrum/plugins/email_requests/__init__.py b/electrum/plugins/email_requests/__init__.py
new file mode 100644
index 000000000..8c9e82472
--- /dev/null
+++ b/electrum/plugins/email_requests/__init__.py
@@ -0,0 +1,5 @@
+from electrum.i18n import _
+
+fullname = _('Email')
+description = _("Send and receive payment request with an email account")
+available_for = ['qt']
diff --git a/electrum/plugins/email_requests/qt.py b/electrum/plugins/email_requests/qt.py
new file mode 100644
index 000000000..02dda9970
--- /dev/null
+++ b/electrum/plugins/email_requests/qt.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import random
+import time
+import threading
+import base64
+from functools import partial
+import traceback
+import sys
+
+import smtplib
+import imaplib
+import email
+from email.mime.multipart import MIMEMultipart
+from email.mime.base import MIMEBase
+from email.encoders import encode_base64
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
+ QInputDialog)
+
+from electrum.plugin import BasePlugin, hook
+from electrum.paymentrequest import PaymentRequest
+from electrum.i18n import _
+from electrum.util import PrintError
+from ...gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
+ WindowModalDialog, get_parent_main_window)
+
+
+class Processor(threading.Thread, PrintError):
+ polling_interval = 5*60
+
+ def __init__(self, imap_server, username, password, callback):
+ threading.Thread.__init__(self)
+ self.daemon = True
+ self.username = username
+ self.password = password
+ self.imap_server = imap_server
+ self.on_receive = callback
+ self.M = None
+ self.reset_connect_wait()
+
+ def reset_connect_wait(self):
+ self.connect_wait = 100 # ms, between failed connection attempts
+
+ def poll(self):
+ try:
+ self.M.select()
+ except:
+ return
+ typ, data = self.M.search(None, 'ALL')
+ for num in str(data[0], 'utf8').split():
+ typ, msg_data = self.M.fetch(num, '(RFC822)')
+ msg = email.message_from_bytes(msg_data[0][1])
+ p = msg.get_payload()
+ if not msg.is_multipart():
+ p = [p]
+ continue
+ for item in p:
+ if item.get_content_type() == "application/bitcore-paymentrequest":
+ pr_str = item.get_payload()
+ pr_str = base64.b64decode(pr_str)
+ self.on_receive(pr_str)
+
+ def run(self):
+ while True:
+ try:
+ self.M = imaplib.IMAP4_SSL(self.imap_server)
+ self.M.login(self.username, self.password)
+ except BaseException as e:
+ self.print_error('connecting failed: {}'.format(e))
+ self.connect_wait *= 2
+ else:
+ self.reset_connect_wait()
+ # Reconnect when host changes
+ while self.M and self.M.host == self.imap_server:
+ try:
+ self.poll()
+ except BaseException as e:
+ self.print_error('polling failed: {}'.format(e))
+ break
+ time.sleep(self.polling_interval)
+ time.sleep(random.randint(0, self.connect_wait))
+
+ def send(self, recipient, message, payment_request):
+ msg = MIMEMultipart()
+ msg['Subject'] = message
+ msg['To'] = recipient
+ msg['From'] = self.username
+ part = MIMEBase('application', "bitcore-paymentrequest")
+ part.set_payload(payment_request)
+ encode_base64(part)
+ part.add_header('Content-Disposition', 'attachment; filename="payreq.btx"')
+ msg.attach(part)
+ try:
+ s = smtplib.SMTP_SSL(self.imap_server, timeout=2)
+ s.login(self.username, self.password)
+ s.sendmail(self.username, [recipient], msg.as_string())
+ s.quit()
+ except BaseException as e:
+ self.print_error(e)
+
+
+class QEmailSignalObject(QObject):
+ email_new_invoice_signal = pyqtSignal()
+
+
+class Plugin(BasePlugin):
+
+ def fullname(self):
+ return 'Email'
+
+ def description(self):
+ return _("Send and receive payment requests via email")
+
+ def is_available(self):
+ return True
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ self.imap_server = self.config.get('email_server', '')
+ self.username = self.config.get('email_username', '')
+ self.password = self.config.get('email_password', '')
+ if self.imap_server and self.username and self.password:
+ self.processor = Processor(self.imap_server, self.username, self.password, self.on_receive)
+ self.processor.start()
+ self.obj = QEmailSignalObject()
+ self.obj.email_new_invoice_signal.connect(self.new_invoice)
+ self.wallets = set()
+
+ def on_receive(self, pr_str):
+ self.print_error('received payment request')
+ self.pr = PaymentRequest(pr_str)
+ self.obj.email_new_invoice_signal.emit()
+
+ @hook
+ def load_wallet(self, wallet, main_window):
+ self.wallets |= {wallet}
+
+ @hook
+ def close_wallet(self, wallet):
+ self.wallets -= {wallet}
+
+ def new_invoice(self):
+ for wallet in self.wallets:
+ wallet.invoices.add(self.pr)
+ #main_window.invoice_list.update()
+
+ @hook
+ def receive_list_menu(self, menu, addr):
+ window = get_parent_main_window(menu)
+ menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr))
+
+ def send(self, window, addr):
+ from electrum import paymentrequest
+ r = window.wallet.receive_requests.get(addr)
+ message = r.get('memo', '')
+ if r.get('signature'):
+ pr = paymentrequest.serialize_request(r)
+ else:
+ pr = paymentrequest.make_request(self.config, r)
+ if not pr:
+ return
+ recipient, ok = QInputDialog.getText(window, 'Send request', 'Email invoice to:')
+ if not ok:
+ return
+ recipient = str(recipient)
+ payload = pr.SerializeToString()
+ self.print_error('sending mail to', recipient)
+ try:
+ # FIXME this runs in the GUI thread and blocks it...
+ self.processor.send(recipient, message, payload)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ window.show_message(str(e))
+ else:
+ window.show_message(_('Request sent.'))
+
+ def requires_settings(self):
+ return True
+
+ def settings_widget(self, window):
+ return EnterButton(_('Settings'), partial(self.settings_dialog, window))
+
+ def settings_dialog(self, window):
+ d = WindowModalDialog(window, _("Email settings"))
+ d.setMinimumSize(500, 200)
+
+ vbox = QVBoxLayout(d)
+ vbox.addWidget(QLabel(_('Server hosting your email account')))
+ grid = QGridLayout()
+ vbox.addLayout(grid)
+ grid.addWidget(QLabel('Server (IMAP)'), 0, 0)
+ server_e = QLineEdit()
+ server_e.setText(self.imap_server)
+ grid.addWidget(server_e, 0, 1)
+
+ grid.addWidget(QLabel('Username'), 1, 0)
+ username_e = QLineEdit()
+ username_e.setText(self.username)
+ grid.addWidget(username_e, 1, 1)
+
+ grid.addWidget(QLabel('Password'), 2, 0)
+ password_e = QLineEdit()
+ password_e.setText(self.password)
+ grid.addWidget(password_e, 2, 1)
+
+ vbox.addStretch()
+ vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
+
+ if not d.exec_():
+ return
+
+ server = str(server_e.text())
+ self.config.set_key('email_server', server)
+ self.imap_server = server
+
+ username = str(username_e.text())
+ self.config.set_key('email_username', username)
+ self.username = username
+
+ password = str(password_e.text())
+ self.config.set_key('email_password', password)
+ self.password = password
+
+ check_connection = CheckConnectionThread(server, username, password)
+ check_connection.connection_error_signal.connect(lambda e: window.show_message(
+ _("Unable to connect to mail server:\n {}").format(e) + "\n" +
+ _("Please check your connection and credentials.")
+ ))
+ check_connection.start()
+
+
+class CheckConnectionThread(QThread):
+ connection_error_signal = pyqtSignal(str)
+
+ def __init__(self, server, username, password):
+ super().__init__()
+ self.server = server
+ self.username = username
+ self.password = password
+
+ def run(self):
+ try:
+ conn = imaplib.IMAP4_SSL(self.server)
+ conn.login(self.username, self.password)
+ except BaseException as e:
+ self.connection_error_signal.emit(str(e))
diff --git a/electrum/plugins/greenaddress_instant/__init__.py b/electrum/plugins/greenaddress_instant/__init__.py
new file mode 100644
index 000000000..d2b317854
--- /dev/null
+++ b/electrum/plugins/greenaddress_instant/__init__.py
@@ -0,0 +1,5 @@
+from electrum.i18n import _
+
+fullname = 'GreenAddress instant'
+description = _("Allows validating if your transactions have instant confirmations by GreenAddress")
+available_for = ['qt']
diff --git a/electrum/plugins/greenaddress_instant/qt.py b/electrum/plugins/greenaddress_instant/qt.py
new file mode 100644
index 000000000..81e6686e1
--- /dev/null
+++ b/electrum/plugins/greenaddress_instant/qt.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import base64
+import urllib.parse
+import sys
+import requests
+
+from PyQt5.QtWidgets import QApplication, QPushButton
+
+from electrum.plugin import BasePlugin, hook
+from electrum.i18n import _
+
+
+
+class Plugin(BasePlugin):
+
+ button_label = _("Verify GA instant")
+
+ @hook
+ def transaction_dialog(self, d):
+ d.verify_button = QPushButton(self.button_label)
+ d.verify_button.clicked.connect(lambda: self.do_verify(d))
+ d.buttons.insert(0, d.verify_button)
+ self.transaction_dialog_update(d)
+
+ def get_my_addr(self, d):
+ """Returns the address for given tx which can be used to request
+ instant confirmation verification from GreenAddress"""
+ for addr, _ in d.tx.get_outputs():
+ if d.wallet.is_mine(addr):
+ return addr
+ return None
+
+ @hook
+ def transaction_dialog_update(self, d):
+ if d.tx.is_complete() and self.get_my_addr(d):
+ d.verify_button.show()
+ else:
+ d.verify_button.hide()
+
+ def do_verify(self, d):
+ tx = d.tx
+ wallet = d.wallet
+ window = d.main_window
+
+ if wallet.is_watching_only():
+ d.show_critical(_('This feature is not available for watch-only wallets.'))
+ return
+
+ # 1. get the password and sign the verification request
+ password = None
+ if wallet.has_keystore_encryption():
+ msg = _('GreenAddress requires your signature \n'
+ 'to verify that transaction is instant.\n'
+ 'Please enter your password to sign a\n'
+ 'verification request.')
+ password = window.password_dialog(msg, parent=d)
+ if not password:
+ return
+ try:
+ d.verify_button.setText(_('Verifying...'))
+ QApplication.processEvents() # update the button label
+
+ addr = self.get_my_addr(d)
+ message = "Please verify if %s is GreenAddress instant confirmed" % tx.txid()
+ sig = wallet.sign_message(addr, message, password)
+ sig = base64.b64encode(sig).decode('ascii')
+
+ # 2. send the request
+ response = requests.request("GET", ("https://greenaddress.it/verify/?signature=%s&txhash=%s" % (urllib.parse.quote(sig), tx.txid())),
+ headers = {'User-Agent': 'Electrum'})
+ response = response.json()
+
+ # 3. display the result
+ if response.get('verified'):
+ d.show_message(_('{} is covered by GreenAddress instant confirmation').format(tx.txid()), title=_('Verification successful!'))
+ else:
+ d.show_critical(_('{} is not covered by GreenAddress instant confirmation').format(tx.txid()), title=_('Verification failed!'))
+ except BaseException as e:
+ import traceback
+ traceback.print_exc(file=sys.stdout)
+ d.show_error(str(e))
+ finally:
+ d.verify_button.setText(self.button_label)
diff --git a/electrum/plugins/hw_wallet/__init__.py b/electrum/plugins/hw_wallet/__init__.py
new file mode 100644
index 000000000..9e118e508
--- /dev/null
+++ b/electrum/plugins/hw_wallet/__init__.py
@@ -0,0 +1,2 @@
+from .plugin import HW_PluginBase
+from .cmdline import CmdLineHandler
diff --git a/electrum/plugins/hw_wallet/cmdline.py b/electrum/plugins/hw_wallet/cmdline.py
new file mode 100644
index 000000000..de29780c7
--- /dev/null
+++ b/electrum/plugins/hw_wallet/cmdline.py
@@ -0,0 +1,46 @@
+from electrum.util import print_msg, print_error, raw_input
+
+
+class CmdLineHandler:
+
+ def get_passphrase(self, msg, confirm):
+ import getpass
+ print_msg(msg)
+ return getpass.getpass('')
+
+ def get_pin(self, msg):
+ t = { 'a':'7', 'b':'8', 'c':'9', 'd':'4', 'e':'5', 'f':'6', 'g':'1', 'h':'2', 'i':'3'}
+ print_msg(msg)
+ print_msg("a b c\nd e f\ng h i\n-----")
+ o = raw_input()
+ try:
+ return ''.join(map(lambda x: t[x], o))
+ except KeyError as e:
+ raise Exception("Character {} not in matrix!".format(e)) from e
+
+ def prompt_auth(self, msg):
+ import getpass
+ print_msg(msg)
+ response = getpass.getpass('')
+ if len(response) == 0:
+ return None
+ return response
+
+ def yes_no_question(self, msg):
+ print_msg(msg)
+ return raw_input() in 'yY'
+
+ def stop(self):
+ pass
+
+ def show_message(self, msg, on_cancel=None):
+ print_msg(msg)
+
+ def show_error(self, msg, blocking=False):
+ print_msg(msg)
+
+ def update_status(self, b):
+ print_error('hw device status', b)
+
+ def finished(self):
+ pass
diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py
new file mode 100644
index 000000000..5ad0dcd14
--- /dev/null
+++ b/electrum/plugins/hw_wallet/plugin.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python2
+# -*- mode: python -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2016 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.plugin import BasePlugin, hook
+from electrum.i18n import _
+from electrum.bitcoin import is_address, TYPE_SCRIPT
+from electrum.util import bfh, versiontuple
+from electrum.transaction import opcodes, TxOutput
+
+
+class HW_PluginBase(BasePlugin):
+ # Derived classes provide:
+ #
+ # class-static variables: client_class, firmware_URL, handler_class,
+ # libraries_available, libraries_URL, minimum_firmware,
+ # wallet_class, ckd_public, types, HidTransport
+
+ minimum_library = (0, )
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ self.device = self.keystore_class.device
+ self.keystore_class.plugin = self
+
+ def is_enabled(self):
+ return True
+
+ def device_manager(self):
+ return self.parent.device_manager
+
+ @hook
+ def close_wallet(self, wallet):
+ for keystore in wallet.get_keystores():
+ if isinstance(keystore, self.keystore_class):
+ self.device_manager().unpair_xpub(keystore.xpub)
+
+ def setup_device(self, device_info, wizard, purpose):
+ """Called when creating a new wallet or when using the device to decrypt
+ an existing wallet. Select the device to use. If the device is
+ uninitialized, go through the initialization process.
+ """
+ raise NotImplementedError()
+
+ def show_address(self, wallet, address, keystore=None):
+ pass # implemented in child classes
+
+ def show_address_helper(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not is_address(address):
+ keystore.handler.show_error(_('Invalid Bitcore Address'))
+ return False
+ if not wallet.is_mine(address):
+ keystore.handler.show_error(_('Address not in wallet.'))
+ return False
+ if type(keystore) != self.keystore_class:
+ return False
+ return True
+
+ def get_library_version(self) -> str:
+ """Returns the version of the 3rd party python library
+ for the hw wallet. For example '0.9.0'
+
+ Returns 'unknown' if library is found but cannot determine version.
+ Raises 'ImportError' if library is not found.
+ """
+ raise NotImplementedError()
+
+ def check_libraries_available(self) -> bool:
+ try:
+ library_version = self.get_library_version()
+ except ImportError:
+ return False
+ if library_version == 'unknown' or \
+ versiontuple(library_version) < self.minimum_library:
+ self.libraries_available_message = (
+ _("Library version for '{}' is too old.").format(self.name)
+ + '\nInstalled: {}, Needed: {}'
+ .format(library_version, self.minimum_library))
+ self.print_stderr(self.libraries_available_message)
+ return False
+ return True
+
+ def get_library_not_available_message(self) -> str:
+ if hasattr(self, 'libraries_available_message'):
+ message = self.libraries_available_message
+ else:
+ message = _("Missing libraries for {}.").format(self.name)
+ message += '\n' + _("Make sure you install it with python3")
+ return message
+
+
+def is_any_tx_output_on_change_branch(tx):
+ if not hasattr(tx, 'output_info'):
+ return False
+ for _type, address, amount in tx.outputs():
+ info = tx.output_info.get(address)
+ if info is not None:
+ index, xpubs, m = info.address_index, info.sorted_xpubs, info.num_sig
+ if index[0] == 1:
+ return True
+ return False
+
+
+def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes:
+ if output.type != TYPE_SCRIPT:
+ raise Exception("Unexpected output type: {}".format(output.type))
+ script = bfh(output.address)
+ if not (script[0] == opcodes.OP_RETURN and
+ script[1] == len(script) - 2 and script[1] <= 75):
+ raise Exception(_("Only OP_RETURN scripts, with one constant push, are supported."))
+ if output.value != 0:
+ raise Exception(_("Amount for OP_RETURN output must be zero."))
+ return script[2:]
diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py
new file mode 100644
index 000000000..2b5215eb6
--- /dev/null
+++ b/electrum/plugins/hw_wallet/qt.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# -*- mode: python -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2016 The Electrum developers
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import threading
+
+from PyQt5.Qt import QVBoxLayout, QLabel
+from electrum.gui.qt.password_dialog import PasswordDialog, PW_PASSPHRASE
+from electrum.gui.qt.util import *
+
+from electrum.i18n import _
+from electrum.util import PrintError
+
+# The trickiest thing about this handler was getting windows properly
+# parented on macOS.
+class QtHandlerBase(QObject, PrintError):
+ '''An interface between the GUI (here, QT) and the device handling
+ logic for handling I/O.'''
+
+ passphrase_signal = pyqtSignal(object, object)
+ message_signal = pyqtSignal(object, object)
+ error_signal = pyqtSignal(object, object)
+ word_signal = pyqtSignal(object)
+ clear_signal = pyqtSignal()
+ query_signal = pyqtSignal(object, object)
+ yes_no_signal = pyqtSignal(object)
+ status_signal = pyqtSignal(object)
+
+ def __init__(self, win, device):
+ super(QtHandlerBase, self).__init__()
+ self.clear_signal.connect(self.clear_dialog)
+ self.error_signal.connect(self.error_dialog)
+ self.message_signal.connect(self.message_dialog)
+ self.passphrase_signal.connect(self.passphrase_dialog)
+ self.word_signal.connect(self.word_dialog)
+ self.query_signal.connect(self.win_query_choice)
+ self.yes_no_signal.connect(self.win_yes_no_question)
+ self.status_signal.connect(self._update_status)
+ self.win = win
+ self.device = device
+ self.dialog = None
+ self.done = threading.Event()
+
+ def top_level_window(self):
+ return self.win.top_level_window()
+
+ def update_status(self, paired):
+ self.status_signal.emit(paired)
+
+ def _update_status(self, paired):
+ if hasattr(self, 'button'):
+ button = self.button
+ icon = button.icon_paired if paired else button.icon_unpaired
+ button.setIcon(QIcon(icon))
+
+ def query_choice(self, msg, labels):
+ self.done.clear()
+ self.query_signal.emit(msg, labels)
+ self.done.wait()
+ return self.choice
+
+ def yes_no_question(self, msg):
+ self.done.clear()
+ self.yes_no_signal.emit(msg)
+ self.done.wait()
+ return self.ok
+
+ def show_message(self, msg, on_cancel=None):
+ self.message_signal.emit(msg, on_cancel)
+
+ def show_error(self, msg, blocking=False):
+ self.done.clear()
+ self.error_signal.emit(msg, blocking)
+ if blocking:
+ self.done.wait()
+
+ def finished(self):
+ self.clear_signal.emit()
+
+ def get_word(self, msg):
+ self.done.clear()
+ self.word_signal.emit(msg)
+ self.done.wait()
+ return self.word
+
+ def get_passphrase(self, msg, confirm):
+ self.done.clear()
+ self.passphrase_signal.emit(msg, confirm)
+ self.done.wait()
+ return self.passphrase
+
+ def passphrase_dialog(self, msg, confirm):
+ # If confirm is true, require the user to enter the passphrase twice
+ parent = self.top_level_window()
+ if confirm:
+ d = PasswordDialog(parent, None, msg, PW_PASSPHRASE)
+ confirmed, p, passphrase = d.run()
+ else:
+ d = WindowModalDialog(parent, _("Enter Passphrase"))
+ pw = QLineEdit()
+ pw.setEchoMode(2)
+ pw.setMinimumWidth(200)
+ vbox = QVBoxLayout()
+ vbox.addWidget(WWLabel(msg))
+ vbox.addWidget(pw)
+ vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
+ d.setLayout(vbox)
+ passphrase = pw.text() if d.exec_() else None
+ self.passphrase = passphrase
+ self.done.set()
+
+ def word_dialog(self, msg):
+ dialog = WindowModalDialog(self.top_level_window(), "")
+ hbox = QHBoxLayout(dialog)
+ hbox.addWidget(QLabel(msg))
+ text = QLineEdit()
+ text.setMaximumWidth(100)
+ text.returnPressed.connect(dialog.accept)
+ hbox.addWidget(text)
+ hbox.addStretch(1)
+ dialog.exec_() # Firmware cannot handle cancellation
+ self.word = text.text()
+ self.done.set()
+
+ def message_dialog(self, msg, on_cancel):
+ # Called more than once during signing, to confirm output and fee
+ self.clear_dialog()
+ title = _('Please check your {} device').format(self.device)
+ self.dialog = dialog = WindowModalDialog(self.top_level_window(), title)
+ l = QLabel(msg)
+ vbox = QVBoxLayout(dialog)
+ vbox.addWidget(l)
+ if on_cancel:
+ dialog.rejected.connect(on_cancel)
+ vbox.addLayout(Buttons(CancelButton(dialog)))
+ dialog.show()
+
+ def error_dialog(self, msg, blocking):
+ self.win.show_error(msg, parent=self.top_level_window())
+ if blocking:
+ self.done.set()
+
+ def clear_dialog(self):
+ if self.dialog:
+ self.dialog.accept()
+ self.dialog = None
+
+ def win_query_choice(self, msg, labels):
+ self.choice = self.win.query_choice(msg, labels)
+ self.done.set()
+
+ def win_yes_no_question(self, msg):
+ self.ok = self.win.question(msg)
+ self.done.set()
+
+
+
+from electrum.plugin import hook
+from electrum.util import UserCancelled
+from electrum.gui.qt.main_window import StatusBarButton
+
+class QtPluginBase(object):
+
+ @hook
+ def load_wallet(self, wallet, window):
+ for keystore in wallet.get_keystores():
+ if not isinstance(keystore, self.keystore_class):
+ continue
+ if not self.libraries_available:
+ message = keystore.plugin.get_library_not_available_message()
+ window.show_error(message)
+ return
+ tooltip = self.device + '\n' + (keystore.label or 'unnamed')
+ cb = partial(self.show_settings_dialog, window, keystore)
+ button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb)
+ button.icon_paired = self.icon_paired
+ button.icon_unpaired = self.icon_unpaired
+ window.statusBar().addPermanentWidget(button)
+ handler = self.create_handler(window)
+ handler.button = button
+ keystore.handler = handler
+ keystore.thread = TaskThread(window, window.on_error)
+ self.add_show_address_on_hw_device_button_for_receive_addr(wallet, keystore, window)
+ # Trigger a pairing
+ keystore.thread.add(partial(self.get_client, keystore))
+
+ def choose_device(self, window, keystore):
+ '''This dialog box should be usable even if the user has
+ forgotten their PIN or it is in bootloader mode.'''
+ device_id = self.device_manager().xpub_id(keystore.xpub)
+ if not device_id:
+ try:
+ info = self.device_manager().select_device(self, keystore.handler, keystore)
+ except UserCancelled:
+ return
+ device_id = info.device.id_
+ return device_id
+
+ def show_settings_dialog(self, window, keystore):
+ device_id = self.choose_device(window, keystore)
+
+ def add_show_address_on_hw_device_button_for_receive_addr(self, wallet, keystore, main_window):
+ plugin = keystore.plugin
+ receive_address_e = main_window.receive_address_e
+
+ def show_address():
+ addr = receive_address_e.text()
+ keystore.thread.add(partial(plugin.show_address, wallet, addr, keystore))
+ receive_address_e.addButton(":icons/eye1.png", show_address, _("Show on {}").format(plugin.device))
diff --git a/electrum/plugins/keepkey/__init__.py b/electrum/plugins/keepkey/__init__.py
new file mode 100644
index 000000000..0b54bc4a0
--- /dev/null
+++ b/electrum/plugins/keepkey/__init__.py
@@ -0,0 +1,7 @@
+from electrum.i18n import _
+
+fullname = 'KeepKey'
+description = _('Provides support for KeepKey hardware wallet')
+requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
+registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet"))
+available_for = ['qt', 'cmdline']
diff --git a/electrum/plugins/keepkey/client.py b/electrum/plugins/keepkey/client.py
new file mode 100644
index 000000000..73bf66dc8
--- /dev/null
+++ b/electrum/plugins/keepkey/client.py
@@ -0,0 +1,14 @@
+from keepkeylib.client import proto, BaseClient, ProtocolMixin
+from .clientbase import KeepKeyClientBase
+
+class KeepKeyClient(KeepKeyClientBase, ProtocolMixin, BaseClient):
+ def __init__(self, transport, handler, plugin):
+ BaseClient.__init__(self, transport)
+ ProtocolMixin.__init__(self, transport)
+ KeepKeyClientBase.__init__(self, handler, plugin, proto)
+
+ def recovery_device(self, *args):
+ ProtocolMixin.recovery_device(self, False, *args)
+
+
+KeepKeyClientBase.wrap_methods(KeepKeyClient)
diff --git a/electrum/plugins/keepkey/clientbase.py b/electrum/plugins/keepkey/clientbase.py
new file mode 100644
index 000000000..332795ca8
--- /dev/null
+++ b/electrum/plugins/keepkey/clientbase.py
@@ -0,0 +1,236 @@
+import time
+from struct import pack
+
+from electrum.i18n import _
+from electrum.util import PrintError, UserCancelled
+from electrum.keystore import bip39_normalize_passphrase
+from electrum.bitcoin import serialize_xpub, convert_bip32_path_to_list_of_uint32
+
+
+class GuiMixin(object):
+ # Requires: self.proto, self.device
+
+ messages = {
+ 3: _("Confirm the transaction output on your {} device"),
+ 4: _("Confirm internal entropy on your {} device to begin"),
+ 5: _("Write down the seed word shown on your {}"),
+ 6: _("Confirm on your {} that you want to wipe it clean"),
+ 7: _("Confirm on your {} device the message to sign"),
+ 8: _("Confirm the total amount spent and the transaction fee on your "
+ "{} device"),
+ 10: _("Confirm wallet address on your {} device"),
+ 'default': _("Check your {} device to continue"),
+ }
+
+ def callback_Failure(self, msg):
+ # BaseClient's unfortunate call() implementation forces us to
+ # raise exceptions on failure in order to unwind the stack.
+ # However, making the user acknowledge they cancelled
+ # gets old very quickly, so we suppress those. The NotInitialized
+ # one is misnamed and indicates a passphrase request was cancelled.
+ if msg.code in (self.types.Failure_PinCancelled,
+ self.types.Failure_ActionCancelled,
+ self.types.Failure_NotInitialized):
+ raise UserCancelled()
+ raise RuntimeError(msg.message)
+
+ def callback_ButtonRequest(self, msg):
+ message = self.msg
+ if not message:
+ message = self.messages.get(msg.code, self.messages['default'])
+ self.handler.show_message(message.format(self.device), self.cancel)
+ return self.proto.ButtonAck()
+
+ def callback_PinMatrixRequest(self, msg):
+ if msg.type == 2:
+ msg = _("Enter a new PIN for your {}:")
+ elif msg.type == 3:
+ msg = (_("Re-enter the new PIN for your {}.\n\n"
+ "NOTE: the positions of the numbers have changed!"))
+ else:
+ msg = _("Enter your current {} PIN:")
+ pin = self.handler.get_pin(msg.format(self.device))
+ if len(pin) > 9:
+ self.handler.show_error(_('The PIN cannot be longer than 9 characters.'))
+ pin = '' # to cancel below
+ if not pin:
+ return self.proto.Cancel()
+ return self.proto.PinMatrixAck(pin=pin)
+
+ def callback_PassphraseRequest(self, req):
+ if self.creating_wallet:
+ msg = _("Enter a passphrase to generate this wallet. Each time "
+ "you use this wallet your {} will prompt you for the "
+ "passphrase. If you forget the passphrase you cannot "
+ "access the bitcores in the wallet.").format(self.device)
+ else:
+ msg = _("Enter the passphrase to unlock this wallet:")
+ passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
+ if passphrase is None:
+ return self.proto.Cancel()
+ passphrase = bip39_normalize_passphrase(passphrase)
+
+ ack = self.proto.PassphraseAck(passphrase=passphrase)
+ length = len(ack.passphrase)
+ if length > 50:
+ self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length))
+ return self.proto.Cancel()
+ return ack
+
+ def callback_WordRequest(self, msg):
+ self.step += 1
+ msg = _("Step {}/24. Enter seed word as explained on "
+ "your {}:").format(self.step, self.device)
+ word = self.handler.get_word(msg)
+ # Unfortunately the device can't handle self.proto.Cancel()
+ return self.proto.WordAck(word=word)
+
+ def callback_CharacterRequest(self, msg):
+ char_info = self.handler.get_char(msg)
+ if not char_info:
+ return self.proto.Cancel()
+ return self.proto.CharacterAck(**char_info)
+
+
+class KeepKeyClientBase(GuiMixin, PrintError):
+
+ def __init__(self, handler, plugin, proto):
+ assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
+ self.proto = proto
+ self.device = plugin.device
+ self.handler = handler
+ self.tx_api = plugin
+ self.types = plugin.types
+ self.msg = None
+ self.creating_wallet = False
+ self.used()
+
+ def __str__(self):
+ return "%s/%s" % (self.label(), self.features.device_id)
+
+ def label(self):
+ '''The name given by the user to the device.'''
+ return self.features.label
+
+ def is_initialized(self):
+ '''True if initialized, False if wiped.'''
+ return self.features.initialized
+
+ def is_pairable(self):
+ return not self.features.bootloader_mode
+
+ def has_usable_connection_with_device(self):
+ try:
+ res = self.ping("electrum pinging device")
+ assert res == "electrum pinging device"
+ except BaseException:
+ return False
+ return True
+
+ def used(self):
+ self.last_operation = time.time()
+
+ def prevent_timeouts(self):
+ self.last_operation = float('inf')
+
+ def timeout(self, cutoff):
+ '''Time out the client if the last operation was before cutoff.'''
+ if self.last_operation < cutoff:
+ self.print_error("timed out")
+ self.clear_session()
+
+ @staticmethod
+ def expand_path(n):
+ return convert_bip32_path_to_list_of_uint32(n)
+
+ def cancel(self):
+ '''Provided here as in keepkeylib but not trezorlib.'''
+ self.transport.write(self.proto.Cancel())
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def get_xpub(self, bip32_path, xtype):
+ address_n = self.expand_path(bip32_path)
+ creating = False
+ node = self.get_public_node(address_n, creating).node
+ return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
+
+ def toggle_passphrase(self):
+ if self.features.passphrase_protection:
+ self.msg = _("Confirm on your {} device to disable passphrases")
+ else:
+ self.msg = _("Confirm on your {} device to enable passphrases")
+ enabled = not self.features.passphrase_protection
+ self.apply_settings(use_passphrase=enabled)
+
+ def change_label(self, label):
+ self.msg = _("Confirm the new label on your {} device")
+ self.apply_settings(label=label)
+
+ def change_homescreen(self, homescreen):
+ self.msg = _("Confirm on your {} device to change your home screen")
+ self.apply_settings(homescreen=homescreen)
+
+ def set_pin(self, remove):
+ if remove:
+ self.msg = _("Confirm on your {} device to disable PIN protection")
+ elif self.features.pin_protection:
+ self.msg = _("Confirm on your {} device to change your PIN")
+ else:
+ self.msg = _("Confirm on your {} device to set a PIN")
+ self.change_pin(remove)
+
+ def clear_session(self):
+ '''Clear the session to force pin (and passphrase if enabled)
+ re-entry. Does not leak exceptions.'''
+ self.print_error("clear session:", self)
+ self.prevent_timeouts()
+ try:
+ super(KeepKeyClientBase, self).clear_session()
+ except BaseException as e:
+ # If the device was removed it has the same effect...
+ self.print_error("clear_session: ignoring error", str(e))
+
+ def get_public_node(self, address_n, creating):
+ self.creating_wallet = creating
+ return super(KeepKeyClientBase, self).get_public_node(address_n)
+
+ def close(self):
+ '''Called when Our wallet was closed or the device removed.'''
+ self.print_error("closing client")
+ self.clear_session()
+ # Release the device
+ self.transport.close()
+
+ def firmware_version(self):
+ f = self.features
+ return (f.major_version, f.minor_version, f.patch_version)
+
+ def atleast_version(self, major, minor=0, patch=0):
+ return self.firmware_version() >= (major, minor, patch)
+
+ @staticmethod
+ def wrapper(func):
+ '''Wrap methods to clear any message box they opened.'''
+
+ def wrapped(self, *args, **kwargs):
+ try:
+ self.prevent_timeouts()
+ return func(self, *args, **kwargs)
+ finally:
+ self.used()
+ self.handler.finished()
+ self.creating_wallet = False
+ self.msg = None
+
+ return wrapped
+
+ @staticmethod
+ def wrap_methods(cls):
+ for method in ['apply_settings', 'change_pin',
+ 'get_address', 'get_public_node',
+ 'load_device_by_mnemonic', 'load_device_by_xprv',
+ 'recovery_device', 'reset_device', 'sign_message',
+ 'sign_tx', 'wipe_device']:
+ setattr(cls, method, cls.wrapper(getattr(cls, method)))
diff --git a/electrum/plugins/keepkey/cmdline.py b/electrum/plugins/keepkey/cmdline.py
new file mode 100644
index 000000000..7cbad274e
--- /dev/null
+++ b/electrum/plugins/keepkey/cmdline.py
@@ -0,0 +1,14 @@
+from electrum.plugin import hook
+from .keepkey import KeepKeyPlugin
+from ..hw_wallet import CmdLineHandler
+
+class Plugin(KeepKeyPlugin):
+ handler = CmdLineHandler()
+ @hook
+ def init_keystore(self, keystore):
+ if not isinstance(keystore, self.keystore_class):
+ return
+ keystore.handler = self.handler
+
+ def create_handler(self, window):
+ return self.handler
diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
new file mode 100644
index 000000000..fbb62f0c5
--- /dev/null
+++ b/electrum/plugins/keepkey/keepkey.py
@@ -0,0 +1,441 @@
+from binascii import hexlify, unhexlify
+import traceback
+import sys
+
+from electrum.util import bfh, bh2u, UserCancelled
+from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey,
+ TYPE_ADDRESS, TYPE_SCRIPT,
+ is_segwit_address)
+from electrum import constants
+from electrum.i18n import _
+from electrum.plugin import BasePlugin
+from electrum.transaction import deserialize, Transaction
+from electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey
+from electrum.wallet import Standard_Wallet
+from electrum.base_wizard import ScriptTypeNotSupported
+
+from ..hw_wallet import HW_PluginBase
+from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
+
+
+# TREZOR initialization methods
+TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
+
+
+class KeepKey_KeyStore(Hardware_KeyStore):
+ hw_type = 'keepkey'
+ device = 'KeepKey'
+
+ def get_derivation(self):
+ return self.derivation
+
+ def is_segwit(self):
+ return self.derivation.startswith("m/49'/")
+
+ def get_client(self, force_pair=True):
+ return self.plugin.get_client(self, force_pair)
+
+ def decrypt_message(self, sequence, message, password):
+ raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
+
+ def sign_message(self, sequence, message, password):
+ client = self.get_client()
+ address_path = self.get_derivation() + "/%d/%d"%sequence
+ address_n = client.expand_path(address_path)
+ msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)
+ return msg_sig.signature
+
+ def sign_transaction(self, tx, password):
+ if tx.is_complete():
+ return
+ # previous transactions used as inputs
+ prev_tx = {}
+ # path of the xpubs that are involved
+ xpub_path = {}
+ for txin in tx.inputs():
+ pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
+ tx_hash = txin['prevout_hash']
+ if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
+ raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
+ prev_tx[tx_hash] = txin['prev_tx']
+ for x_pubkey in x_pubkeys:
+ if not is_xpubkey(x_pubkey):
+ continue
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub == self.get_master_public_key():
+ xpub_path[xpub] = self.get_derivation()
+
+ self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
+
+
+class KeepKeyPlugin(HW_PluginBase):
+ # Derived classes provide:
+ #
+ # class-static variables: client_class, firmware_URL, handler_class,
+ # libraries_available, libraries_URL, minimum_firmware,
+ # wallet_class, ckd_public, types, HidTransport
+
+ firmware_URL = 'https://www.keepkey.com'
+ libraries_URL = 'https://github.com/keepkey/python-keepkey'
+ minimum_firmware = (1, 0, 0)
+ keystore_class = KeepKey_KeyStore
+ SUPPORTED_XTYPES = ('standard', )
+
+ MAX_LABEL_LEN = 32
+
+ def __init__(self, parent, config, name):
+ HW_PluginBase.__init__(self, parent, config, name)
+
+ try:
+ from . import client
+ import keepkeylib
+ import keepkeylib.ckd_public
+ import keepkeylib.transport_hid
+ self.client_class = client.KeepKeyClient
+ self.ckd_public = keepkeylib.ckd_public
+ self.types = keepkeylib.client.types
+ self.DEVICE_IDS = keepkeylib.transport_hid.DEVICE_IDS
+ self.device_manager().register_devices(self.DEVICE_IDS)
+ self.libraries_available = True
+ except ImportError:
+ self.libraries_available = False
+
+ def hid_transport(self, pair):
+ from keepkeylib.transport_hid import HidTransport
+ return HidTransport(pair)
+
+ def _try_hid(self, device):
+ self.print_error("Trying to connect over USB...")
+ if device.interface_number == 1:
+ pair = [None, device.path]
+ else:
+ pair = [device.path, None]
+
+ try:
+ return self.hid_transport(pair)
+ except BaseException as e:
+ # see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114
+ # raise
+ self.print_error("cannot connect at", device.path, str(e))
+ return None
+
+ def create_client(self, device, handler):
+ transport = self._try_hid(device)
+ if not transport:
+ self.print_error("cannot connect to device")
+ return
+
+ self.print_error("connected to device at", device.path)
+
+ client = self.client_class(transport, handler, self)
+
+ # Try a ping for device sanity
+ try:
+ client.ping('t')
+ except BaseException as e:
+ self.print_error("ping failed", str(e))
+ return None
+
+ if not client.atleast_version(*self.minimum_firmware):
+ msg = (_('Outdated {} firmware for device labelled {}. Please '
+ 'download the updated firmware from {}')
+ .format(self.device, client.label(), self.firmware_URL))
+ self.print_error(msg)
+ if handler:
+ handler.show_error(msg)
+ else:
+ raise Exception(msg)
+ return None
+
+ return client
+
+ def get_client(self, keystore, force_pair=True):
+ devmgr = self.device_manager()
+ handler = keystore.handler
+ with devmgr.hid_lock:
+ client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
+ # returns the client for a given keystore. can use xpub
+ if client:
+ client.used()
+ return client
+
+ def get_coin_name(self):
+ return "Testnet" if constants.net.TESTNET else "Bitcore"
+
+ def initialize_device(self, device_id, wizard, handler):
+ # Initialization method
+ msg = _("Choose how you want to initialize your {}.\n\n"
+ "The first two methods are secure as no secret information "
+ "is entered into your computer.\n\n"
+ "For the last two methods you input secrets on your keyboard "
+ "and upload them to your {}, and so you should "
+ "only do those on a computer you know to be trustworthy "
+ "and free of malware."
+ ).format(self.device, self.device)
+ choices = [
+ # Must be short as QT doesn't word-wrap radio button text
+ (TIM_NEW, _("Let the device generate a completely new seed randomly")),
+ (TIM_RECOVER, _("Recover from a seed you have previously written down")),
+ (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
+ (TIM_PRIVKEY, _("Upload a master private key"))
+ ]
+ def f(method):
+ import threading
+ settings = self.request_trezor_init_settings(wizard, method, self.device)
+ t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
+ t.setDaemon(True)
+ t.start()
+ exit_code = wizard.loop.exec_()
+ if exit_code != 0:
+ # this method (initialize_device) was called with the expectation
+ # of leaving the device in an initialized state when finishing.
+ # signal that this is not the case:
+ raise UserCancelled()
+ wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)
+
+ def _initialize_device_safe(self, settings, method, device_id, wizard, handler):
+ exit_code = 0
+ try:
+ self._initialize_device(settings, method, device_id, wizard, handler)
+ except UserCancelled:
+ exit_code = 1
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ handler.show_error(str(e))
+ exit_code = 1
+ finally:
+ wizard.loop.exit(exit_code)
+
+ def _initialize_device(self, settings, method, device_id, wizard, handler):
+ item, label, pin_protection, passphrase_protection = settings
+
+ language = 'english'
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+
+ if method == TIM_NEW:
+ strength = 64 * (item + 2) # 128, 192 or 256
+ client.reset_device(True, strength, passphrase_protection,
+ pin_protection, label, language)
+ elif method == TIM_RECOVER:
+ word_count = 6 * (item + 2) # 12, 18 or 24
+ client.step = 0
+ client.recovery_device(word_count, passphrase_protection,
+ pin_protection, label, language)
+ elif method == TIM_MNEMONIC:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_mnemonic(str(item), pin,
+ passphrase_protection,
+ label, language)
+ else:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_xprv(item, pin, passphrase_protection,
+ label, language)
+
+ def setup_device(self, device_info, wizard, purpose):
+ devmgr = self.device_manager()
+ device_id = device_info.device.id_
+ client = devmgr.client_by_id(device_id)
+ if client is None:
+ raise Exception(_('Failed to create a client for this device.') + '\n' +
+ _('Make sure it is in the correct state.'))
+ # fixme: we should use: client.handler = wizard
+ client.handler = self.create_handler(wizard)
+ if not device_info.initialized:
+ self.initialize_device(device_id, wizard, client.handler)
+ client.get_xpub('m', 'standard')
+ client.used()
+
+ def get_xpub(self, device_id, derivation, xtype, wizard):
+ if xtype not in self.SUPPORTED_XTYPES:
+ raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device))
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ client.handler = wizard
+ xpub = client.get_xpub(derivation, xtype)
+ client.used()
+ return xpub
+
+ def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
+ self.prev_tx = prev_tx
+ self.xpub_path = xpub_path
+ client = self.get_client(keystore)
+ inputs = self.tx_inputs(tx, True, keystore.is_segwit())
+ outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.is_segwit())
+ signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
+ signatures = [(bh2u(x) + '01') for x in signatures]
+ tx.update_signatures(signatures)
+
+ def show_address(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not self.show_address_helper(wallet, address, keystore):
+ return
+ if type(wallet) is not Standard_Wallet:
+ keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device))
+ return
+ client = self.get_client(wallet.keystore)
+ if not client.atleast_version(1, 3):
+ wallet.keystore.handler.show_error(_("Your device firmware is too old"))
+ return
+ change, index = wallet.get_address_index(address)
+ derivation = wallet.keystore.derivation
+ address_path = "%s/%d/%d"%(derivation, change, index)
+ address_n = client.expand_path(address_path)
+ segwit = wallet.keystore.is_segwit()
+ script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS
+ client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)
+
+ def tx_inputs(self, tx, for_sig=False, segwit=False):
+ inputs = []
+ for txin in tx.inputs():
+ txinputtype = self.types.TxInputType()
+ if txin['type'] == 'coinbase':
+ prev_hash = b"\x00"*32
+ prev_index = 0xffffffff # signed int -1
+ else:
+ if for_sig:
+ x_pubkeys = txin['x_pubkeys']
+ if len(x_pubkeys) == 1:
+ x_pubkey = x_pubkeys[0]
+ xpub, s = parse_xpubkey(x_pubkey)
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype.address_n.extend(xpub_n + s)
+ txinputtype.script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS
+ else:
+ def f(x_pubkey):
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ else:
+ xpub = xpub_from_pubkey(0, bfh(x_pubkey))
+ s = []
+ node = self.ckd_public.deserialize(xpub)
+ return self.types.HDNodePathType(node=node, address_n=s)
+ pubkeys = map(f, x_pubkeys)
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')),
+ m=txin.get('num_sig'),
+ )
+ script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDMULTISIG
+ txinputtype = self.types.TxInputType(
+ script_type=script_type,
+ multisig=multisig
+ )
+ # find which key is mine
+ for x_pubkey in x_pubkeys:
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub in self.xpub_path:
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype.address_n.extend(xpub_n + s)
+ break
+
+ prev_hash = unhexlify(txin['prevout_hash'])
+ prev_index = txin['prevout_n']
+
+ if 'value' in txin:
+ txinputtype.amount = txin['value']
+ txinputtype.prev_hash = prev_hash
+ txinputtype.prev_index = prev_index
+
+ if txin.get('scriptSig') is not None:
+ script_sig = bfh(txin['scriptSig'])
+ txinputtype.script_sig = script_sig
+
+ txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)
+
+ inputs.append(txinputtype)
+
+ return inputs
+
+ def tx_outputs(self, derivation, tx, segwit=False):
+
+ def create_output_by_derivation():
+ if len(xpubs) == 1:
+ script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS
+ address_n = self.client_class.expand_path(derivation + "/%d/%d" % index)
+ txoutputtype = self.types.TxOutputType(
+ amount=amount,
+ script_type=script_type,
+ address_n=address_n,
+ )
+ else:
+ script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG
+ address_n = self.client_class.expand_path("/%d/%d" % index)
+ nodes = map(self.ckd_public.deserialize, xpubs)
+ pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes]
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=[b''] * len(pubkeys),
+ m=m)
+ txoutputtype = self.types.TxOutputType(
+ multisig=multisig,
+ amount=amount,
+ address_n=self.client_class.expand_path(derivation + "/%d/%d" % index),
+ script_type=script_type)
+ return txoutputtype
+
+ def create_output_by_address():
+ txoutputtype = self.types.TxOutputType()
+ txoutputtype.amount = amount
+ if _type == TYPE_SCRIPT:
+ txoutputtype.script_type = self.types.PAYTOOPRETURN
+ txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
+ elif _type == TYPE_ADDRESS:
+ if is_segwit_address(address):
+ txoutputtype.script_type = self.types.PAYTOWITNESS
+ else:
+ addrtype, hash_160 = b58_address_to_hash160(address)
+ if addrtype == constants.net.ADDRTYPE_P2PKH:
+ txoutputtype.script_type = self.types.PAYTOADDRESS
+ elif addrtype == constants.net.ADDRTYPE_P2SH:
+ txoutputtype.script_type = self.types.PAYTOSCRIPTHASH
+ else:
+ raise Exception('addrtype: ' + str(addrtype))
+ txoutputtype.address = address
+ return txoutputtype
+
+ outputs = []
+ has_change = False
+ any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
+
+ for o in tx.outputs():
+ _type, address, amount = o.type, o.address, o.value
+ use_create_by_derivation = False
+
+ info = tx.output_info.get(address)
+ if info is not None and not has_change:
+ index, xpubs, m = info.address_index, info.sorted_xpubs, info.num_sig
+ on_change_branch = index[0] == 1
+ # prioritise hiding outputs on the 'change' branch from user
+ # because no more than one change address allowed
+ if on_change_branch == any_output_on_change_branch:
+ use_create_by_derivation = True
+ has_change = True
+
+ if use_create_by_derivation:
+ txoutputtype = create_output_by_derivation()
+ else:
+ txoutputtype = create_output_by_address()
+ outputs.append(txoutputtype)
+
+ return outputs
+
+ def electrum_tx_to_txtype(self, tx):
+ t = self.types.TransactionType()
+ d = deserialize(tx.raw)
+ t.version = d['version']
+ t.lock_time = d['lockTime']
+ inputs = self.tx_inputs(tx)
+ t.inputs.extend(inputs)
+ for vout in d['outputs']:
+ o = t.bin_outputs.add()
+ o.amount = vout['value']
+ o.script_pubkey = bfh(vout['scriptPubKey'])
+ return t
+
+ # This function is called from the TREZOR libraries (via tx_api)
+ def get_tx(self, tx_hash):
+ tx = self.prev_tx[tx_hash]
+ return self.electrum_tx_to_txtype(tx)
diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py
new file mode 100644
index 000000000..a544be588
--- /dev/null
+++ b/electrum/plugins/keepkey/qt.py
@@ -0,0 +1,586 @@
+from functools import partial
+import threading
+
+from PyQt5.Qt import Qt
+from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton
+from PyQt5.Qt import QVBoxLayout, QLabel
+
+from electrum.gui.qt.util import *
+from electrum.i18n import _
+from electrum.plugin import hook, DeviceMgr
+from electrum.util import PrintError, UserCancelled, bh2u
+from electrum.wallet import Wallet, Standard_Wallet
+
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+from .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
+
+
+PASSPHRASE_HELP_SHORT =_(
+ "Passphrases allow you to access new wallets, each "
+ "hidden behind a particular case-sensitive passphrase.")
+PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + " " + _(
+ "You need to create a separate Electrum wallet for each passphrase "
+ "you use as they each generate different addresses. Changing "
+ "your passphrase does not lose other wallets, each is still "
+ "accessible behind its own passphrase.")
+RECOMMEND_PIN = _(
+ "You should enable PIN protection. Your PIN is the only protection "
+ "for your bitcores if your device is lost or stolen.")
+PASSPHRASE_NOT_PIN = _(
+ "If you forget a passphrase you will be unable to access any "
+ "bitcores in the wallet behind it. A passphrase is not a PIN. "
+ "Only change this if you are sure you understand it.")
+CHARACTER_RECOVERY = (
+ "Use the recovery cipher shown on your device to input your seed words. "
+ "The cipher changes with every keypress.\n"
+ "After at most 4 letters the device will auto-complete a word.\n"
+ "Press SPACE or the Accept Word button to accept the device's auto-"
+ "completed word and advance to the next one.\n"
+ "Press BACKSPACE to go back a character or word.\n"
+ "Press ENTER or the Seed Entered button once the last word in your "
+ "seed is auto-completed.")
+
+class CharacterButton(QPushButton):
+ def __init__(self, text=None):
+ QPushButton.__init__(self, text)
+
+ def keyPressEvent(self, event):
+ event.setAccepted(False) # Pass through Enter and Space keys
+
+
+class CharacterDialog(WindowModalDialog):
+
+ def __init__(self, parent):
+ super(CharacterDialog, self).__init__(parent)
+ self.setWindowTitle(_("KeepKey Seed Recovery"))
+ self.character_pos = 0
+ self.word_pos = 0
+ self.loop = QEventLoop()
+ self.word_help = QLabel()
+ self.char_buttons = []
+
+ vbox = QVBoxLayout(self)
+ vbox.addWidget(WWLabel(CHARACTER_RECOVERY))
+ hbox = QHBoxLayout()
+ hbox.addWidget(self.word_help)
+ for i in range(4):
+ char_button = CharacterButton('*')
+ char_button.setMaximumWidth(36)
+ self.char_buttons.append(char_button)
+ hbox.addWidget(char_button)
+ self.accept_button = CharacterButton(_("Accept Word"))
+ self.accept_button.clicked.connect(partial(self.process_key, 32))
+ self.rejected.connect(partial(self.loop.exit, 1))
+ hbox.addWidget(self.accept_button)
+ hbox.addStretch(1)
+ vbox.addLayout(hbox)
+
+ self.finished_button = QPushButton(_("Seed Entered"))
+ self.cancel_button = QPushButton(_("Cancel"))
+ self.finished_button.clicked.connect(partial(self.process_key,
+ Qt.Key_Return))
+ self.cancel_button.clicked.connect(self.rejected)
+ buttons = Buttons(self.finished_button, self.cancel_button)
+ vbox.addSpacing(40)
+ vbox.addLayout(buttons)
+ self.refresh()
+ self.show()
+
+ def refresh(self):
+ self.word_help.setText("Enter seed word %2d:" % (self.word_pos + 1))
+ self.accept_button.setEnabled(self.character_pos >= 3)
+ self.finished_button.setEnabled((self.word_pos in (11, 17, 23)
+ and self.character_pos >= 3))
+ for n, button in enumerate(self.char_buttons):
+ button.setEnabled(n == self.character_pos)
+ if n == self.character_pos:
+ button.setFocus()
+
+ def is_valid_alpha_space(self, key):
+ # Auto-completion requires at least 3 characters
+ if key == ord(' ') and self.character_pos >= 3:
+ return True
+ # Firmware aborts protocol if the 5th character is non-space
+ if self.character_pos >= 4:
+ return False
+ return (key >= ord('a') and key <= ord('z')
+ or (key >= ord('A') and key <= ord('Z')))
+
+ def process_key(self, key):
+ self.data = None
+ if key == Qt.Key_Return and self.finished_button.isEnabled():
+ self.data = {'done': True}
+ elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos):
+ self.data = {'delete': True}
+ elif self.is_valid_alpha_space(key):
+ self.data = {'character': chr(key).lower()}
+ if self.data:
+ self.loop.exit(0)
+
+ def keyPressEvent(self, event):
+ self.process_key(event.key())
+ if not self.data:
+ QDialog.keyPressEvent(self, event)
+
+ def get_char(self, word_pos, character_pos):
+ self.word_pos = word_pos
+ self.character_pos = character_pos
+ self.refresh()
+ if self.loop.exec_():
+ self.data = None # User cancelled
+
+
+class QtHandler(QtHandlerBase):
+
+ char_signal = pyqtSignal(object)
+ pin_signal = pyqtSignal(object)
+ close_char_dialog_signal = pyqtSignal()
+
+ def __init__(self, win, pin_matrix_widget_class, device):
+ super(QtHandler, self).__init__(win, device)
+ self.char_signal.connect(self.update_character_dialog)
+ self.pin_signal.connect(self.pin_dialog)
+ self.close_char_dialog_signal.connect(self._close_char_dialog)
+ self.pin_matrix_widget_class = pin_matrix_widget_class
+ self.character_dialog = None
+
+ def get_char(self, msg):
+ self.done.clear()
+ self.char_signal.emit(msg)
+ self.done.wait()
+ data = self.character_dialog.data
+ if not data or 'done' in data:
+ self.close_char_dialog_signal.emit()
+ return data
+
+ def _close_char_dialog(self):
+ if self.character_dialog:
+ self.character_dialog.accept()
+ self.character_dialog = None
+
+ def get_pin(self, msg):
+ self.done.clear()
+ self.pin_signal.emit(msg)
+ self.done.wait()
+ return self.response
+
+ def pin_dialog(self, msg):
+ # Needed e.g. when resetting a device
+ self.clear_dialog()
+ dialog = WindowModalDialog(self.top_level_window(), _("Enter PIN"))
+ matrix = self.pin_matrix_widget_class()
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(matrix)
+ vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
+ dialog.setLayout(vbox)
+ dialog.exec_()
+ self.response = str(matrix.get_value())
+ self.done.set()
+
+ def update_character_dialog(self, msg):
+ if not self.character_dialog:
+ self.character_dialog = CharacterDialog(self.top_level_window())
+ self.character_dialog.get_char(msg.word_pos, msg.character_pos)
+ self.done.set()
+
+
+
+class QtPlugin(QtPluginBase):
+ # Derived classes must provide the following class-static variables:
+ # icon_file
+ # pin_matrix_widget_class
+
+ def create_handler(self, window):
+ return QtHandler(window, self.pin_matrix_widget_class(), self.device)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if type(wallet) is not Standard_Wallet:
+ return
+ keystore = wallet.get_keystore()
+ if type(keystore) == self.keystore_class and len(addrs) == 1:
+ def show_address():
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
+ menu.addAction(_("Show on {}").format(self.device), show_address)
+
+ def show_settings_dialog(self, window, keystore):
+ device_id = self.choose_device(window, keystore)
+ if device_id:
+ SettingsDialog(window, self, keystore, device_id).exec_()
+
+ def request_trezor_init_settings(self, wizard, method, device):
+ vbox = QVBoxLayout()
+ next_enabled = True
+ label = QLabel(_("Enter a label to name your device:"))
+ name = QLineEdit()
+ hl = QHBoxLayout()
+ hl.addWidget(label)
+ hl.addWidget(name)
+ hl.addStretch(1)
+ vbox.addLayout(hl)
+
+ def clean_text(widget):
+ text = widget.toPlainText().strip()
+ return ' '.join(text.split())
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ gb = QGroupBox()
+ hbox1 = QHBoxLayout()
+ gb.setLayout(hbox1)
+ # KeepKey recovery doesn't need a word count
+ if method == TIM_NEW:
+ vbox.addWidget(gb)
+ gb.setTitle(_("Select your seed length:"))
+ bg = QButtonGroup()
+ for i, count in enumerate([12, 18, 24]):
+ rb = QRadioButton(gb)
+ rb.setText(_("{} words").format(count))
+ bg.addButton(rb)
+ bg.setId(rb, i)
+ hbox1.addWidget(rb)
+ rb.setChecked(True)
+ cb_pin = QCheckBox(_('Enable PIN protection'))
+ cb_pin.setChecked(True)
+ else:
+ text = QTextEdit()
+ text.setMaximumHeight(60)
+ if method == TIM_MNEMONIC:
+ msg = _("Enter your BIP39 mnemonic:")
+ else:
+ msg = _("Enter the master private key beginning with xprv:")
+ def set_enabled():
+ from keystore import is_xprv
+ wizard.next_button.setEnabled(is_xprv(clean_text(text)))
+ text.textChanged.connect(set_enabled)
+ next_enabled = False
+
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(text)
+ pin = QLineEdit()
+ pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
+ pin.setMaximumWidth(100)
+ hbox_pin = QHBoxLayout()
+ hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
+ hbox_pin.addWidget(pin)
+ hbox_pin.addStretch(1)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ vbox.addWidget(WWLabel(RECOMMEND_PIN))
+ vbox.addWidget(cb_pin)
+ else:
+ vbox.addLayout(hbox_pin)
+
+ passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ cb_phrase = QCheckBox(_('Enable passphrases'))
+ cb_phrase.setChecked(False)
+ vbox.addWidget(passphrase_msg)
+ vbox.addWidget(passphrase_warning)
+ vbox.addWidget(cb_phrase)
+
+ wizard.exec_layout(vbox, next_enabled=next_enabled)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ item = bg.checkedId()
+ pin = cb_pin.isChecked()
+ else:
+ item = ' '.join(str(clean_text(text)).split())
+ pin = str(pin.text())
+
+ return (item, name.text(), pin, cb_phrase.isChecked())
+
+
+class Plugin(KeepKeyPlugin, QtPlugin):
+ icon_paired = ":icons/keepkey.png"
+ icon_unpaired = ":icons/keepkey_unpaired.png"
+
+ @classmethod
+ def pin_matrix_widget_class(self):
+ from keepkeylib.qt.pinmatrix import PinMatrixWidget
+ return PinMatrixWidget
+
+
+class SettingsDialog(WindowModalDialog):
+ '''This dialog doesn't require a device be paired with a wallet.
+ We want users to be able to wipe a device even if they've forgotten
+ their PIN.'''
+
+ def __init__(self, window, plugin, keystore, device_id):
+ title = _("{} Settings").format(plugin.device)
+ super(SettingsDialog, self).__init__(window, title)
+ self.setMaximumWidth(540)
+
+ devmgr = plugin.device_manager()
+ config = devmgr.config
+ handler = keystore.handler
+ thread = keystore.thread
+ hs_rows, hs_cols = (64, 128)
+
+ def invoke_client(method, *args, **kw_args):
+ unpair_after = kw_args.pop('unpair_after', False)
+
+ def task():
+ client = devmgr.client_by_id(device_id)
+ if not client:
+ raise RuntimeError("Device not connected")
+ if method:
+ getattr(client, method)(*args, **kw_args)
+ if unpair_after:
+ devmgr.unpair_id(device_id)
+ return client.features
+
+ thread.add(task, on_success=update)
+
+ def update(features):
+ self.features = features
+ set_label_enabled()
+ bl_hash = bh2u(features.bootloader_hash)
+ bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
+ noyes = [_("No"), _("Yes")]
+ endis = [_("Enable Passphrases"), _("Disable Passphrases")]
+ disen = [_("Disabled"), _("Enabled")]
+ setchange = [_("Set a PIN"), _("Change PIN")]
+
+ version = "%d.%d.%d" % (features.major_version,
+ features.minor_version,
+ features.patch_version)
+ coins = ", ".join(coin.coin_name for coin in features.coins)
+
+ device_label.setText(features.label)
+ pin_set_label.setText(noyes[features.pin_protection])
+ passphrases_label.setText(disen[features.passphrase_protection])
+ bl_hash_label.setText(bl_hash)
+ label_edit.setText(features.label)
+ device_id_label.setText(features.device_id)
+ initialized_label.setText(noyes[features.initialized])
+ version_label.setText(version)
+ coins_label.setText(coins)
+ clear_pin_button.setVisible(features.pin_protection)
+ clear_pin_warning.setVisible(features.pin_protection)
+ pin_button.setText(setchange[features.pin_protection])
+ pin_msg.setVisible(not features.pin_protection)
+ passphrase_button.setText(endis[features.passphrase_protection])
+ language_label.setText(features.language)
+
+ def set_label_enabled():
+ label_apply.setEnabled(label_edit.text() != self.features.label)
+
+ def rename():
+ invoke_client('change_label', label_edit.text())
+
+ def toggle_passphrase():
+ title = _("Confirm Toggle Passphrase Protection")
+ currently_enabled = self.features.passphrase_protection
+ if currently_enabled:
+ msg = _("After disabling passphrases, you can only pair this "
+ "Electrum wallet if it had an empty passphrase. "
+ "If its passphrase was not empty, you will need to "
+ "create a new wallet with the install wizard. You "
+ "can use this wallet again at any time by re-enabling "
+ "passphrases and entering its passphrase.")
+ else:
+ msg = _("Your current Electrum wallet can only be used with "
+ "an empty passphrase. You must create a separate "
+ "wallet with the install wizard for other passphrases "
+ "as each one generates a new set of addresses.")
+ msg += "\n\n" + _("Are you sure you want to proceed?")
+ if not self.question(msg, title=title):
+ return
+ invoke_client('toggle_passphrase', unpair_after=currently_enabled)
+
+ def change_homescreen():
+ from PIL import Image # FIXME
+ dialog = QFileDialog(self, _("Choose Homescreen"))
+ filename, __ = dialog.getOpenFileName()
+ if filename:
+ im = Image.open(str(filename))
+ if im.size != (hs_cols, hs_rows):
+ raise Exception('Image must be 64 x 128 pixels')
+ im = im.convert('1')
+ pix = im.load()
+ img = ''
+ for j in range(hs_rows):
+ for i in range(hs_cols):
+ img += '1' if pix[i, j] else '0'
+ img = ''.join(chr(int(img[i:i + 8], 2))
+ for i in range(0, len(img), 8))
+ invoke_client('change_homescreen', img)
+
+ def clear_homescreen():
+ invoke_client('change_homescreen', '\x00')
+
+ def set_pin():
+ invoke_client('set_pin', remove=False)
+
+ def clear_pin():
+ invoke_client('set_pin', remove=True)
+
+ def wipe_device():
+ wallet = window.wallet
+ if wallet and sum(wallet.get_balance()):
+ title = _("Confirm Device Wipe")
+ msg = _("Are you SURE you want to wipe the device?\n"
+ "Your wallet still has bitcores in it!")
+ if not self.question(msg, title=title,
+ icon=QMessageBox.Critical):
+ return
+ invoke_client('wipe_device', unpair_after=True)
+
+ def slider_moved():
+ mins = timeout_slider.sliderPosition()
+ timeout_minutes.setText(_("%2d minutes") % mins)
+
+ def slider_released():
+ config.set_session_timeout(timeout_slider.sliderPosition() * 60)
+
+ # Information tab
+ info_tab = QWidget()
+ info_layout = QVBoxLayout(info_tab)
+ info_glayout = QGridLayout()
+ info_glayout.setColumnStretch(2, 1)
+ device_label = QLabel()
+ pin_set_label = QLabel()
+ passphrases_label = QLabel()
+ version_label = QLabel()
+ device_id_label = QLabel()
+ bl_hash_label = QLabel()
+ bl_hash_label.setWordWrap(True)
+ coins_label = QLabel()
+ coins_label.setWordWrap(True)
+ language_label = QLabel()
+ initialized_label = QLabel()
+ rows = [
+ (_("Device Label"), device_label),
+ (_("PIN set"), pin_set_label),
+ (_("Passphrases"), passphrases_label),
+ (_("Firmware Version"), version_label),
+ (_("Device ID"), device_id_label),
+ (_("Bootloader Hash"), bl_hash_label),
+ (_("Supported Coins"), coins_label),
+ (_("Language"), language_label),
+ (_("Initialized"), initialized_label),
+ ]
+ for row_num, (label, widget) in enumerate(rows):
+ info_glayout.addWidget(QLabel(label), row_num, 0)
+ info_glayout.addWidget(widget, row_num, 1)
+ info_layout.addLayout(info_glayout)
+
+ # Settings tab
+ settings_tab = QWidget()
+ settings_layout = QVBoxLayout(settings_tab)
+ settings_glayout = QGridLayout()
+
+ # Settings tab - Label
+ label_msg = QLabel(_("Name this {}. If you have multiple devices "
+ "their labels help distinguish them.")
+ .format(plugin.device))
+ label_msg.setWordWrap(True)
+ label_label = QLabel(_("Device Label"))
+ label_edit = QLineEdit()
+ label_edit.setMinimumWidth(150)
+ label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
+ label_apply = QPushButton(_("Apply"))
+ label_apply.clicked.connect(rename)
+ label_edit.textChanged.connect(set_label_enabled)
+ settings_glayout.addWidget(label_label, 0, 0)
+ settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
+ settings_glayout.addWidget(label_apply, 0, 3)
+ settings_glayout.addWidget(label_msg, 1, 1, 1, -1)
+
+ # Settings tab - PIN
+ pin_label = QLabel(_("PIN Protection"))
+ pin_button = QPushButton()
+ pin_button.clicked.connect(set_pin)
+ settings_glayout.addWidget(pin_label, 2, 0)
+ settings_glayout.addWidget(pin_button, 2, 1)
+ pin_msg = QLabel(_("PIN protection is strongly recommended. "
+ "A PIN is your only protection against someone "
+ "stealing your bitcores if they obtain physical "
+ "access to your {}.").format(plugin.device))
+ pin_msg.setWordWrap(True)
+ pin_msg.setStyleSheet("color: red")
+ settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)
+
+ # Settings tab - Session Timeout
+ timeout_label = QLabel(_("Session Timeout"))
+ timeout_minutes = QLabel()
+ timeout_slider = QSlider(Qt.Horizontal)
+ timeout_slider.setRange(1, 60)
+ timeout_slider.setSingleStep(1)
+ timeout_slider.setTickInterval(5)
+ timeout_slider.setTickPosition(QSlider.TicksBelow)
+ timeout_slider.setTracking(True)
+ timeout_msg = QLabel(
+ _("Clear the session after the specified period "
+ "of inactivity. Once a session has timed out, "
+ "your PIN and passphrase (if enabled) must be "
+ "re-entered to use the device."))
+ timeout_msg.setWordWrap(True)
+ timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
+ slider_moved()
+ timeout_slider.valueChanged.connect(slider_moved)
+ timeout_slider.sliderReleased.connect(slider_released)
+ settings_glayout.addWidget(timeout_label, 6, 0)
+ settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
+ settings_glayout.addWidget(timeout_minutes, 6, 4)
+ settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
+ settings_layout.addLayout(settings_glayout)
+ settings_layout.addStretch(1)
+
+ # Advanced tab
+ advanced_tab = QWidget()
+ advanced_layout = QVBoxLayout(advanced_tab)
+ advanced_glayout = QGridLayout()
+
+ # Advanced tab - clear PIN
+ clear_pin_button = QPushButton(_("Disable PIN"))
+ clear_pin_button.clicked.connect(clear_pin)
+ clear_pin_warning = QLabel(
+ _("If you disable your PIN, anyone with physical access to your "
+ "{} device can spend your bitcores").format(plugin.device))
+ clear_pin_warning.setWordWrap(True)
+ clear_pin_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(clear_pin_button, 0, 2)
+ advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)
+
+ # Advanced tab - toggle passphrase protection
+ passphrase_button = QPushButton()
+ passphrase_button.clicked.connect(toggle_passphrase)
+ passphrase_msg = WWLabel(PASSPHRASE_HELP)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(passphrase_button, 3, 2)
+ advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
+ advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)
+
+ # Advanced tab - wipe device
+ wipe_device_button = QPushButton(_("Wipe Device"))
+ wipe_device_button.clicked.connect(wipe_device)
+ wipe_device_msg = QLabel(
+ _("Wipe the device, removing all data from it. The firmware "
+ "is left unchanged."))
+ wipe_device_msg.setWordWrap(True)
+ wipe_device_warning = QLabel(
+ _("Only wipe a device if you have the recovery seed written down "
+ "and the device wallet(s) are empty, otherwise the bitcores "
+ "will be lost forever."))
+ wipe_device_warning.setWordWrap(True)
+ wipe_device_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(wipe_device_button, 6, 2)
+ advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
+ advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
+ advanced_layout.addLayout(advanced_glayout)
+ advanced_layout.addStretch(1)
+
+ tabs = QTabWidget(self)
+ tabs.addTab(info_tab, _("Information"))
+ tabs.addTab(settings_tab, _("Settings"))
+ tabs.addTab(advanced_tab, _("Advanced"))
+ dialog_vbox = QVBoxLayout(self)
+ dialog_vbox.addWidget(tabs)
+ dialog_vbox.addLayout(Buttons(CloseButton(self)))
+
+ # Update information
+ invoke_client(None)
diff --git a/electrum/plugins/labels/__init__.py b/electrum/plugins/labels/__init__.py
new file mode 100644
index 000000000..4596bcae4
--- /dev/null
+++ b/electrum/plugins/labels/__init__.py
@@ -0,0 +1,9 @@
+from electrum.i18n import _
+
+fullname = _('LabelSync')
+description = ' '.join([
+ _("Save your wallet labels on a remote server, and synchronize them across multiple devices where you use Electrum."),
+ _("Labels, transactions IDs and addresses are encrypted before they are sent to the remote server.")
+])
+available_for = ['qt', 'kivy', 'cmdline']
+
diff --git a/electrum/plugins/labels/cmdline.py b/electrum/plugins/labels/cmdline.py
new file mode 100644
index 000000000..2f9aaa069
--- /dev/null
+++ b/electrum/plugins/labels/cmdline.py
@@ -0,0 +1,11 @@
+from .labels import LabelsPlugin
+from electrum.plugin import hook
+
+class Plugin(LabelsPlugin):
+
+ @hook
+ def load_wallet(self, wallet, window):
+ self.start_wallet(wallet)
+
+ def on_pulled(self, wallet):
+ self.print_error('labels pulled from server')
diff --git a/electrum/plugins/labels/kivy.py b/electrum/plugins/labels/kivy.py
new file mode 100644
index 000000000..56f1d079e
--- /dev/null
+++ b/electrum/plugins/labels/kivy.py
@@ -0,0 +1,14 @@
+from .labels import LabelsPlugin
+from electrum.plugin import hook
+
+class Plugin(LabelsPlugin):
+
+ @hook
+ def load_wallet(self, wallet, window):
+ self.window = window
+ self.start_wallet(wallet)
+
+ def on_pulled(self, wallet):
+ self.print_error('on pulled')
+ self.window._trigger_update_history()
+
diff --git a/electrum/plugins/labels/labels.py b/electrum/plugins/labels/labels.py
new file mode 100644
index 000000000..d5fa50b2c
--- /dev/null
+++ b/electrum/plugins/labels/labels.py
@@ -0,0 +1,167 @@
+import hashlib
+import requests
+import threading
+import json
+import sys
+import traceback
+
+import base64
+
+from electrum.plugin import BasePlugin, hook
+from electrum.crypto import aes_encrypt_with_iv, aes_decrypt_with_iv
+from electrum.i18n import _
+
+
+class LabelsPlugin(BasePlugin):
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ self.target_host = 'labels.electrum.org'
+ self.wallets = {}
+
+ def encode(self, wallet, msg):
+ password, iv, wallet_id = self.wallets[wallet]
+ encrypted = aes_encrypt_with_iv(password, iv,
+ msg.encode('utf8'))
+ return base64.b64encode(encrypted).decode()
+
+ def decode(self, wallet, message):
+ password, iv, wallet_id = self.wallets[wallet]
+ decoded = base64.b64decode(message)
+ decrypted = aes_decrypt_with_iv(password, iv, decoded)
+ return decrypted.decode('utf8')
+
+ def get_nonce(self, wallet):
+ # nonce is the nonce to be used with the next change
+ nonce = wallet.storage.get('wallet_nonce')
+ if nonce is None:
+ nonce = 1
+ self.set_nonce(wallet, nonce)
+ return nonce
+
+ def set_nonce(self, wallet, nonce):
+ self.print_error("set", wallet.basename(), "nonce to", nonce)
+ wallet.storage.put("wallet_nonce", nonce)
+
+ @hook
+ def set_label(self, wallet, item, label):
+ if wallet not in self.wallets:
+ return
+ if not item:
+ return
+ nonce = self.get_nonce(wallet)
+ wallet_id = self.wallets[wallet][2]
+ bundle = {"walletId": wallet_id,
+ "walletNonce": nonce,
+ "externalId": self.encode(wallet, item),
+ "encryptedLabel": self.encode(wallet, label)}
+ t = threading.Thread(target=self.do_request_safe,
+ args=["POST", "/label", False, bundle])
+ t.setDaemon(True)
+ t.start()
+ # Caller will write the wallet
+ self.set_nonce(wallet, nonce + 1)
+
+ def do_request(self, method, url = "/labels", is_batch=False, data=None):
+ url = 'https://' + self.target_host + url
+ kwargs = {'headers': {}}
+ if method == 'GET' and data:
+ kwargs['params'] = data
+ elif method == 'POST' and data:
+ kwargs['data'] = json.dumps(data)
+ kwargs['headers']['Content-Type'] = 'application/json'
+ response = requests.request(method, url, **kwargs)
+ if response.status_code != 200:
+ raise Exception(response.status_code, response.text)
+ response = response.json()
+ if "error" in response:
+ raise Exception(response["error"])
+ return response
+
+ def do_request_safe(self, *args, **kwargs):
+ try:
+ self.do_request(*args, **kwargs)
+ except BaseException as e:
+ #traceback.print_exc(file=sys.stderr)
+ self.print_error('error doing request')
+
+ def push_thread(self, wallet):
+ wallet_data = self.wallets.get(wallet, None)
+ if not wallet_data:
+ raise Exception('Wallet {} not loaded'.format(wallet))
+ wallet_id = wallet_data[2]
+ bundle = {"labels": [],
+ "walletId": wallet_id,
+ "walletNonce": self.get_nonce(wallet)}
+ for key, value in wallet.labels.items():
+ try:
+ encoded_key = self.encode(wallet, key)
+ encoded_value = self.encode(wallet, value)
+ except:
+ self.print_error('cannot encode', repr(key), repr(value))
+ continue
+ bundle["labels"].append({'encryptedLabel': encoded_value,
+ 'externalId': encoded_key})
+ self.do_request("POST", "/labels", True, bundle)
+
+ def pull_thread(self, wallet, force):
+ wallet_data = self.wallets.get(wallet, None)
+ if not wallet_data:
+ raise Exception('Wallet {} not loaded'.format(wallet))
+ wallet_id = wallet_data[2]
+ nonce = 1 if force else self.get_nonce(wallet) - 1
+ self.print_error("asking for labels since nonce", nonce)
+ response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) ))
+ if response["labels"] is None:
+ self.print_error('no new labels')
+ return
+ result = {}
+ for label in response["labels"]:
+ try:
+ key = self.decode(wallet, label["externalId"])
+ value = self.decode(wallet, label["encryptedLabel"])
+ except:
+ continue
+ try:
+ json.dumps(key)
+ json.dumps(value)
+ except:
+ self.print_error('error: no json', key)
+ continue
+ result[key] = value
+
+ for key, value in result.items():
+ if force or not wallet.labels.get(key):
+ wallet.labels[key] = value
+
+ self.print_error("received %d labels" % len(response))
+ # do not write to disk because we're in a daemon thread
+ wallet.storage.put('labels', wallet.labels)
+ self.set_nonce(wallet, response["nonce"] + 1)
+ self.on_pulled(wallet)
+
+ def pull_thread_safe(self, wallet, force):
+ try:
+ self.pull_thread(wallet, force)
+ except BaseException as e:
+ # traceback.print_exc(file=sys.stderr)
+ self.print_error('could not retrieve labels')
+
+ def start_wallet(self, wallet):
+ nonce = self.get_nonce(wallet)
+ self.print_error("wallet", wallet.basename(), "nonce is", nonce)
+ mpk = wallet.get_fingerprint()
+ if not mpk:
+ return
+ mpk = mpk.encode('ascii')
+ password = hashlib.sha1(mpk).hexdigest()[:32].encode('ascii')
+ iv = hashlib.sha256(password).digest()[:16]
+ wallet_id = hashlib.sha256(mpk).hexdigest()
+ self.wallets[wallet] = (password, iv, wallet_id)
+ # If there is an auth token we can try to actually start syncing
+ t = threading.Thread(target=self.pull_thread_safe, args=(wallet, False))
+ t.setDaemon(True)
+ t.start()
+
+ def stop_wallet(self, wallet):
+ self.wallets.pop(wallet, None)
diff --git a/electrum/plugins/labels/qt.py b/electrum/plugins/labels/qt.py
new file mode 100644
index 000000000..df4ae55c2
--- /dev/null
+++ b/electrum/plugins/labels/qt.py
@@ -0,0 +1,78 @@
+from functools import partial
+import traceback
+import sys
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout)
+
+from electrum.plugin import hook
+from electrum.i18n import _
+from electrum.gui.qt import EnterButton
+from electrum.gui.qt.util import ThreadedButton, Buttons
+from electrum.gui.qt.util import WindowModalDialog, OkButton
+
+from .labels import LabelsPlugin
+
+
+class QLabelsSignalObject(QObject):
+ labels_changed_signal = pyqtSignal(object)
+
+
+class Plugin(LabelsPlugin):
+
+ def __init__(self, *args):
+ LabelsPlugin.__init__(self, *args)
+ self.obj = QLabelsSignalObject()
+
+ def requires_settings(self):
+ return True
+
+ def settings_widget(self, window):
+ return EnterButton(_('Settings'),
+ partial(self.settings_dialog, window))
+
+ def settings_dialog(self, window):
+ wallet = window.parent().wallet
+ d = WindowModalDialog(window, _("Label Settings"))
+ hbox = QHBoxLayout()
+ hbox.addWidget(QLabel("Label sync options:"))
+ upload = ThreadedButton("Force upload",
+ partial(self.push_thread, wallet),
+ partial(self.done_processing_success, d),
+ partial(self.done_processing_error, d))
+ download = ThreadedButton("Force download",
+ partial(self.pull_thread, wallet, True),
+ partial(self.done_processing_success, d),
+ partial(self.done_processing_error, d))
+ vbox = QVBoxLayout()
+ vbox.addWidget(upload)
+ vbox.addWidget(download)
+ hbox.addLayout(vbox)
+ vbox = QVBoxLayout(d)
+ vbox.addLayout(hbox)
+ vbox.addSpacing(20)
+ vbox.addLayout(Buttons(OkButton(d)))
+ return bool(d.exec_())
+
+ def on_pulled(self, wallet):
+ self.obj.labels_changed_signal.emit(wallet)
+
+ def done_processing_success(self, dialog, result):
+ dialog.show_message(_("Your labels have been synchronised."))
+
+ def done_processing_error(self, dialog, result):
+ traceback.print_exception(*result, file=sys.stderr)
+ dialog.show_error(_("Error synchronising labels") + ':\n' + str(result[:2]))
+
+ @hook
+ def load_wallet(self, wallet, window):
+ # FIXME if the user just enabled the plugin, this hook won't be called
+ # as the wallet is already loaded, and hence the plugin will be in
+ # a non-functional state for that window
+ self.obj.labels_changed_signal.connect(window.update_tabs)
+ self.start_wallet(wallet)
+
+ @hook
+ def on_close_window(self, window):
+ self.stop_wallet(window.wallet)
diff --git a/electrum/plugins/ledger/__init__.py b/electrum/plugins/ledger/__init__.py
new file mode 100644
index 000000000..b97f02335
--- /dev/null
+++ b/electrum/plugins/ledger/__init__.py
@@ -0,0 +1,7 @@
+from electrum.i18n import _
+
+fullname = 'Ledger Wallet'
+description = 'Provides support for Ledger hardware wallet'
+requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
+registers_keystore = ('hardware', 'ledger', _("Ledger wallet"))
+available_for = ['qt', 'cmdline']
diff --git a/electrum/plugins/ledger/auth2fa.py b/electrum/plugins/ledger/auth2fa.py
new file mode 100644
index 000000000..f6ddeab01
--- /dev/null
+++ b/electrum/plugins/ledger/auth2fa.py
@@ -0,0 +1,358 @@
+import os
+import hashlib
+import logging
+import json
+import copy
+from binascii import hexlify, unhexlify
+
+import websocket
+
+from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel
+import PyQt5.QtCore as QtCore
+from PyQt5.QtWidgets import *
+
+from btchip.btchip import *
+
+from electrum.i18n import _
+from electrum.util import print_msg
+from electrum import constants, bitcoin
+from electrum.gui.qt.qrcodewidget import QRCodeWidget
+from electrum.gui.qt.util import *
+
+
+DEBUG = False
+
+helpTxt = [_("Your Ledger Wallet wants to tell you a one-time PIN code. " \
+ "For best security you should unplug your device, open a text editor on another computer, " \
+ "put your cursor into it, and plug your device into that computer. " \
+ "It will output a summary of the transaction being signed and a one-time PIN. " \
+ "Verify the transaction summary and type the PIN code here. " \
+ "Before pressing enter, plug the device back into this computer. " ),
+ _("Verify the address below. Type the character from your security card corresponding to the BOLD character."),
+ _("Waiting for authentication on your mobile phone"),
+ _("Transaction accepted by mobile phone. Waiting for confirmation."),
+ _("Click Pair button to begin pairing a mobile phone."),
+ _("Scan this QR code with your Ledger Wallet phone app to pair it with this Ledger device. "
+ "To complete pairing you will need your security card to answer a challenge." )
+ ]
+
+class LedgerAuthDialog(QDialog):
+ def __init__(self, handler, data):
+ '''Ask user for 2nd factor authentication. Support text, security card and paired mobile methods.
+ Use last method from settings, but support new pairing and downgrade.
+ '''
+ QDialog.__init__(self, handler.top_level_window())
+ self.handler = handler
+ self.txdata = data
+ self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''
+ self.setMinimumWidth(650)
+ self.setWindowTitle(_("Ledger Wallet Authentication"))
+ self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
+ self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle
+ self.ws = None
+ self.pin = ''
+
+ self.devmode = self.getDevice2FAMode()
+ if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:
+ self.cfg['mode'] = 0
+
+ vbox = QVBoxLayout()
+ self.setLayout(vbox)
+
+ def on_change_mode(idx):
+ if idx < 2 and self.ws:
+ self.ws.stop()
+ self.ws = None
+ self.cfg['mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1
+ if self.cfg['mode'] > 1 and self.cfg['pair'] and not self.ws:
+ self.req_validation()
+ if self.cfg['mode'] > 0:
+ self.handler.win.wallet.get_keystore().cfg = self.cfg
+ self.handler.win.wallet.save_keystore()
+ self.update_dlg()
+ def add_pairing():
+ self.do_pairing()
+ def return_pin():
+ self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text()
+ if self.cfg['mode'] == 1:
+ self.pin = ''.join(chr(int(str(i),16)) for i in self.pin)
+ self.accept()
+
+ self.modebox = QWidget()
+ modelayout = QHBoxLayout()
+ self.modebox.setLayout(modelayout)
+ modelayout.addWidget(QLabel(_("Method:")))
+ self.modes = QComboBox()
+ modelayout.addWidget(self.modes, 2)
+ self.addPair = QPushButton(_("Pair"))
+ self.addPair.setMaximumWidth(60)
+ modelayout.addWidget(self.addPair)
+ modelayout.addStretch(1)
+ self.modebox.setMaximumHeight(50)
+ vbox.addWidget(self.modebox)
+
+ self.populate_modes()
+ self.modes.currentIndexChanged.connect(on_change_mode)
+ self.addPair.clicked.connect(add_pairing)
+
+ self.helpmsg = QTextEdit()
+ self.helpmsg.setStyleSheet("QTextEdit { background-color: lightgray; }")
+ self.helpmsg.setReadOnly(True)
+ vbox.addWidget(self.helpmsg)
+
+ self.pinbox = QWidget()
+ pinlayout = QHBoxLayout()
+ self.pinbox.setLayout(pinlayout)
+ self.pintxt = QLineEdit()
+ self.pintxt.setEchoMode(2)
+ self.pintxt.setMaxLength(4)
+ self.pintxt.returnPressed.connect(return_pin)
+ pinlayout.addWidget(QLabel(_("Enter PIN:")))
+ pinlayout.addWidget(self.pintxt)
+ pinlayout.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
+ pinlayout.addStretch(1)
+ self.pinbox.setVisible(self.cfg['mode'] == 0)
+ vbox.addWidget(self.pinbox)
+
+ self.cardbox = QWidget()
+ card = QVBoxLayout()
+ self.cardbox.setLayout(card)
+ self.addrtext = QTextEdit()
+ self.addrtext.setStyleSheet("QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; font-family:monospace; }")
+ self.addrtext.setReadOnly(True)
+ self.addrtext.setMaximumHeight(130)
+ card.addWidget(self.addrtext)
+
+ def pin_changed(s):
+ if len(s) < len(self.idxs):
+ i = self.idxs[len(s)]
+ addr = self.txdata['address']
+ if not constants.net.TESTNET:
+ text = addr[:i] + '' + addr[i:i+1] + ' ' + addr[i+1:]
+ else:
+ # pin needs to be created from mainnet address
+ addr_mainnet = bitcoin.script_to_address(bitcoin.address_to_script(addr), net=constants.BitcoinMainnet)
+ addr_mainnet = addr_mainnet[:i] + '' + addr_mainnet[i:i+1] + ' ' + addr_mainnet[i+1:]
+ text = str(addr) + '\n' + str(addr_mainnet)
+ self.addrtext.setHtml(str(text))
+ else:
+ self.addrtext.setHtml(_("Press Enter"))
+
+ pin_changed('')
+ cardpin = QHBoxLayout()
+ cardpin.addWidget(QLabel(_("Enter PIN:")))
+ self.cardtxt = QLineEdit()
+ self.cardtxt.setEchoMode(2)
+ self.cardtxt.setMaxLength(len(self.idxs))
+ self.cardtxt.textChanged.connect(pin_changed)
+ self.cardtxt.returnPressed.connect(return_pin)
+ cardpin.addWidget(self.cardtxt)
+ cardpin.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
+ cardpin.addStretch(1)
+ card.addLayout(cardpin)
+ self.cardbox.setVisible(self.cfg['mode'] == 1)
+ vbox.addWidget(self.cardbox)
+
+ self.pairbox = QWidget()
+ pairlayout = QVBoxLayout()
+ self.pairbox.setLayout(pairlayout)
+ pairhelp = QTextEdit(helpTxt[5])
+ pairhelp.setStyleSheet("QTextEdit { background-color: lightgray; }")
+ pairhelp.setReadOnly(True)
+ pairlayout.addWidget(pairhelp, 1)
+ self.pairqr = QRCodeWidget()
+ pairlayout.addWidget(self.pairqr, 4)
+ self.pairbox.setVisible(False)
+ vbox.addWidget(self.pairbox)
+ self.update_dlg()
+
+ if self.cfg['mode'] > 1 and not self.ws:
+ self.req_validation()
+
+ def populate_modes(self):
+ self.modes.blockSignals(True)
+ self.modes.clear()
+ self.modes.addItem(_("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _("Summary Text PIN is Disabled"))
+ if self.txdata['confirmationType'] > 1:
+ self.modes.addItem(_("Security Card Challenge"))
+ if not self.cfg['pair']:
+ self.modes.addItem(_("Mobile - Not paired"))
+ else:
+ self.modes.addItem(_("Mobile - {}").format(self.cfg['pair'][1]))
+ self.modes.blockSignals(False)
+
+ def update_dlg(self):
+ self.modes.setCurrentIndex(self.cfg['mode'])
+ self.modebox.setVisible(True)
+ self.addPair.setText(_("Pair") if not self.cfg['pair'] else _("Re-Pair"))
+ self.addPair.setVisible(self.txdata['confirmationType'] > 2)
+ self.helpmsg.setText(helpTxt[self.cfg['mode'] if self.cfg['mode'] < 2 else 2 if self.cfg['pair'] else 4])
+ self.helpmsg.setMinimumHeight(180 if self.txdata['confirmationType'] == 1 else 100)
+ self.pairbox.setVisible(False)
+ self.helpmsg.setVisible(True)
+ self.pinbox.setVisible(self.cfg['mode'] == 0)
+ self.cardbox.setVisible(self.cfg['mode'] == 1)
+ self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
+ self.setMaximumHeight(400)
+
+ def do_pairing(self):
+ rng = os.urandom(16)
+ pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8')
+ self.pairqr.setData(pairID)
+ self.modebox.setVisible(False)
+ self.helpmsg.setVisible(False)
+ self.pinbox.setVisible(False)
+ self.cardbox.setVisible(False)
+ self.pairbox.setVisible(True)
+ self.pairqr.setMinimumSize(300,300)
+ if self.ws:
+ self.ws.stop()
+ self.ws = LedgerWebSocket(self, pairID)
+ self.ws.pairing_done.connect(self.pairing_done)
+ self.ws.start()
+
+ def pairing_done(self, data):
+ if data is not None:
+ self.cfg['pair'] = [ data['pairid'], data['name'], data['platform'] ]
+ self.cfg['mode'] = 2
+ self.handler.win.wallet.get_keystore().cfg = self.cfg
+ self.handler.win.wallet.save_keystore()
+ self.pin = 'paired'
+ self.accept()
+
+ def req_validation(self):
+ if self.cfg['pair'] and 'secureScreenData' in self.txdata:
+ if self.ws:
+ self.ws.stop()
+ self.ws = LedgerWebSocket(self, self.cfg['pair'][0], self.txdata)
+ self.ws.req_updated.connect(self.req_updated)
+ self.ws.start()
+
+ def req_updated(self, pin):
+ if pin == 'accepted':
+ self.helpmsg.setText(helpTxt[3])
+ else:
+ self.pin = str(pin)
+ self.accept()
+
+ def getDevice2FAMode(self):
+ apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
+ try:
+ mode = self.dongle.exchange( bytearray(apdu) )
+ return mode
+ except BTChipException as e:
+ debug_msg('Device getMode Failed')
+ return 0x11
+
+ def closeEvent(self, evnt):
+ debug_msg("CLOSE - Stop WS")
+ if self.ws:
+ self.ws.stop()
+ if self.pairbox.isVisible():
+ evnt.ignore()
+ self.update_dlg()
+
+class LedgerWebSocket(QThread):
+ pairing_done = pyqtSignal(object)
+ req_updated = pyqtSignal(str)
+
+ def __init__(self, dlg, pairID, txdata=None):
+ QThread.__init__(self)
+ self.stopping = False
+ self.pairID = pairID
+ self.txreq = '{"type":"request","second_factor_data":"' + hexlify(txdata['secureScreenData']).decode('utf-8') + '"}' if txdata else None
+ self.dlg = dlg
+ self.dongle = self.dlg.dongle
+ self.data = None
+
+ #websocket.enableTrace(True)
+ logging.basicConfig(level=logging.INFO)
+ self.ws = websocket.WebSocketApp('wss://ws.ledgerwallet.com/2fa/channels',
+ on_message = self.on_message, on_error = self.on_error,
+ on_close = self.on_close, on_open = self.on_open)
+
+ def run(self):
+ while not self.stopping:
+ self.ws.run_forever()
+ def stop(self):
+ debug_msg("WS: Stopping")
+ self.stopping = True
+ self.ws.close()
+
+ def on_message(self, ws, msg):
+ data = json.loads(msg)
+ if data['type'] == 'identify':
+ debug_msg('Identify')
+ apdu = [0xe0, 0x12, 0x01, 0x00, 0x41] # init pairing
+ apdu.extend(unhexlify(data['public_key']))
+ try:
+ challenge = self.dongle.exchange( bytearray(apdu) )
+ ws.send( '{"type":"challenge","data":"%s" }' % hexlify(challenge).decode('utf-8') )
+ self.data = data
+ except BTChipException as e:
+ debug_msg('Identify Failed')
+
+ if data['type'] == 'challenge':
+ debug_msg('Challenge')
+ apdu = [0xe0, 0x12, 0x02, 0x00, 0x10] # confirm pairing
+ apdu.extend(unhexlify(data['data']))
+ try:
+ self.dongle.exchange( bytearray(apdu) )
+ debug_msg('Pairing Successful')
+ ws.send( '{"type":"pairing","is_successful":"true"}' )
+ self.data['pairid'] = self.pairID
+ self.pairing_done.emit(self.data)
+ except BTChipException as e:
+ debug_msg('Pairing Failed')
+ ws.send( '{"type":"pairing","is_successful":"false"}' )
+ self.pairing_done.emit(None)
+ ws.send( '{"type":"disconnect"}' )
+ self.stopping = True
+ ws.close()
+
+ if data['type'] == 'accept':
+ debug_msg('Accepted')
+ self.req_updated.emit('accepted')
+ if data['type'] == 'response':
+ debug_msg('Responded', data)
+ self.req_updated.emit(str(data['pin']) if data['is_accepted'] else '')
+ self.txreq = None
+ self.stopping = True
+ ws.close()
+
+ if data['type'] == 'repeat':
+ debug_msg('Repeat')
+ if self.txreq:
+ ws.send( self.txreq )
+ debug_msg("Req Sent", self.txreq)
+ if data['type'] == 'connect':
+ debug_msg('Connected')
+ if self.txreq:
+ ws.send( self.txreq )
+ debug_msg("Req Sent", self.txreq)
+ if data['type'] == 'disconnect':
+ debug_msg('Disconnected')
+ ws.close()
+
+ def on_error(self, ws, error):
+ message = getattr(error, 'strerror', '')
+ if not message:
+ message = getattr(error, 'message', '')
+ debug_msg("WS: %s" % message)
+
+ def on_close(self, ws):
+ debug_msg("WS: ### socket closed ###")
+
+ def on_open(self, ws):
+ debug_msg("WS: ### socket open ###")
+ debug_msg("Joining with pairing ID", self.pairID)
+ ws.send( '{"type":"join","room":"%s"}' % self.pairID )
+ ws.send( '{"type":"repeat"}' )
+ if self.txreq:
+ ws.send( self.txreq )
+ debug_msg("Req Sent", self.txreq)
+
+
+def debug_msg(*args):
+ if DEBUG:
+ print_msg(*args)
diff --git a/electrum/plugins/ledger/cmdline.py b/electrum/plugins/ledger/cmdline.py
new file mode 100644
index 000000000..3e8101697
--- /dev/null
+++ b/electrum/plugins/ledger/cmdline.py
@@ -0,0 +1,14 @@
+from electrum.plugin import hook
+from .ledger import LedgerPlugin
+from ..hw_wallet import CmdLineHandler
+
+class Plugin(LedgerPlugin):
+ handler = CmdLineHandler()
+ @hook
+ def init_keystore(self, keystore):
+ if not isinstance(keystore, self.keystore_class):
+ return
+ keystore.handler = self.handler
+
+ def create_handler(self, window):
+ return self.handler
diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
new file mode 100644
index 000000000..199fa3e3b
--- /dev/null
+++ b/electrum/plugins/ledger/ledger.py
@@ -0,0 +1,637 @@
+from struct import pack, unpack
+import hashlib
+import sys
+import traceback
+
+from electrum import bitcoin
+from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int
+from electrum.i18n import _
+from electrum.plugin import BasePlugin
+from electrum.keystore import Hardware_KeyStore
+from electrum.transaction import Transaction
+from electrum.wallet import Standard_Wallet
+from ..hw_wallet import HW_PluginBase
+from ..hw_wallet.plugin import is_any_tx_output_on_change_branch
+from electrum.util import print_error, bfh, bh2u, versiontuple
+from electrum.base_wizard import ScriptTypeNotSupported
+
+try:
+ import hid
+ from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
+ from btchip.btchip import btchip
+ from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script
+ from btchip.bitcoinTransaction import bitcoinTransaction
+ from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
+ from btchip.btchipException import BTChipException
+ BTCHIP = True
+ BTCHIP_DEBUG = False
+except ImportError:
+ BTCHIP = False
+
+MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
+ ' https://www.ledgerwallet.com'
+MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcore" app) too old for Segwit support. Please update at') + \
+ ' https://www.ledgerwallet.com'
+MULTI_OUTPUT_SUPPORT = '1.1.4'
+SEGWIT_SUPPORT = '1.1.10'
+SEGWIT_SUPPORT_SPECIAL = '1.0.4'
+
+
+def test_pin_unlocked(func):
+ """Function decorator to test the Ledger for being unlocked, and if not,
+ raise a human-readable exception.
+ """
+ def catch_exception(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ except BTChipException as e:
+ if e.sw == 0x6982:
+ raise Exception(_('Your Ledger is locked. Please unlock it.'))
+ else:
+ raise
+ return catch_exception
+
+
+class Ledger_Client():
+ def __init__(self, hidDevice):
+ self.dongleObject = btchip(hidDevice)
+ self.preflightDone = False
+
+ def is_pairable(self):
+ return True
+
+ def close(self):
+ self.dongleObject.dongle.close()
+
+ def timeout(self, cutoff):
+ pass
+
+ def is_initialized(self):
+ return True
+
+ def label(self):
+ return ""
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def has_usable_connection_with_device(self):
+ try:
+ self.dongleObject.getFirmwareVersion()
+ except BaseException:
+ return False
+ return True
+
+ @test_pin_unlocked
+ def get_xpub(self, bip32_path, xtype):
+ self.checkDevice()
+ # bip32_path is of the form 44'/0'/1'
+ # S-L-O-W - we don't handle the fingerprint directly, so compute
+ # it manually from the previous node
+ # This only happens once so it's bearable
+ #self.get_client() # prompt for the PIN before displaying the dialog if necessary
+ #self.handler.show_message("Computing master public key")
+ if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():
+ raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
+ if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():
+ raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
+ splitPath = bip32_path.split('/')
+ if splitPath[0] == 'm':
+ splitPath = splitPath[1:]
+ bip32_path = bip32_path[2:]
+ fingerprint = 0
+ if len(splitPath) > 1:
+ prevPath = "/".join(splitPath[0:len(splitPath) - 1])
+ nodeData = self.dongleObject.getWalletPublicKey(prevPath)
+ publicKey = compress_public_key(nodeData['publicKey'])
+ h = hashlib.new('ripemd160')
+ h.update(hashlib.sha256(publicKey).digest())
+ fingerprint = unpack(">I", h.digest()[0:4])[0]
+ nodeData = self.dongleObject.getWalletPublicKey(bip32_path)
+ publicKey = compress_public_key(nodeData['publicKey'])
+ depth = len(splitPath)
+ lastChild = splitPath[len(splitPath) - 1].split('\'')
+ childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0])
+ xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum))
+ return xpub
+
+ def has_detached_pin_support(self, client):
+ try:
+ client.getVerifyPinRemainingAttempts()
+ return True
+ except BTChipException as e:
+ if e.sw == 0x6d00:
+ return False
+ raise e
+
+ def is_pin_validated(self, client):
+ try:
+ # Invalid SET OPERATION MODE to verify the PIN status
+ client.dongle.exchange(bytearray([0xe0, 0x26, 0x00, 0x00, 0x01, 0xAB]))
+ except BTChipException as e:
+ if (e.sw == 0x6982):
+ return False
+ if (e.sw == 0x6A80):
+ return True
+ raise e
+
+ def supports_multi_output(self):
+ return self.multiOutputSupported
+
+ def supports_segwit(self):
+ return self.segwitSupported
+
+ def supports_native_segwit(self):
+ return self.nativeSegwitSupported
+
+ def perform_hw1_preflight(self):
+ try:
+ firmwareInfo = self.dongleObject.getFirmwareVersion()
+ firmware = firmwareInfo['version']
+ self.multiOutputSupported = versiontuple(firmware) >= versiontuple(MULTI_OUTPUT_SUPPORT)
+ self.nativeSegwitSupported = versiontuple(firmware) >= versiontuple(SEGWIT_SUPPORT)
+ self.segwitSupported = self.nativeSegwitSupported or (firmwareInfo['specialVersion'] == 0x20 and versiontuple(firmware) >= versiontuple(SEGWIT_SUPPORT_SPECIAL))
+
+ if not checkFirmware(firmwareInfo):
+ self.dongleObject.dongle.close()
+ raise Exception(MSG_NEEDS_FW_UPDATE_GENERIC)
+ try:
+ self.dongleObject.getOperationMode()
+ except BTChipException as e:
+ if (e.sw == 0x6985):
+ self.dongleObject.dongle.close()
+ self.handler.get_setup( )
+ # Acquire the new client on the next run
+ else:
+ raise e
+ if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject) and (self.handler is not None):
+ remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts()
+ if remaining_attempts != 1:
+ msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)
+ else:
+ msg = "Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped."
+ confirmed, p, pin = self.password_dialog(msg)
+ if not confirmed:
+ raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')
+ pin = pin.encode()
+ self.dongleObject.verifyPin(pin)
+ except BTChipException as e:
+ if (e.sw == 0x6faa):
+ raise Exception("Dongle is temporarily locked - please unplug it and replug it again")
+ if ((e.sw & 0xFFF0) == 0x63c0):
+ raise Exception("Invalid PIN - please unplug the dongle and plug it again before retrying")
+ if e.sw == 0x6f00 and e.message == 'Invalid channel':
+ # based on docs 0x6f00 might be a more general error, hence we also compare message to be sure
+ raise Exception("Invalid channel.\n"
+ "Please make sure that 'Browser support' is disabled on your device.")
+ raise e
+
+ def checkDevice(self):
+ if not self.preflightDone:
+ try:
+ self.perform_hw1_preflight()
+ except BTChipException as e:
+ if (e.sw == 0x6d00 or e.sw == 0x6700):
+ raise Exception(_("Device not in Bitcore mode")) from e
+ raise e
+ self.preflightDone = True
+
+ def password_dialog(self, msg=None):
+ response = self.handler.get_word(msg)
+ if response is None:
+ return False, None, None
+ return True, response, response
+
+
+class Ledger_KeyStore(Hardware_KeyStore):
+ hw_type = 'ledger'
+ device = 'Ledger'
+
+ def __init__(self, d):
+ Hardware_KeyStore.__init__(self, d)
+ # Errors and other user interaction is done through the wallet's
+ # handler. The handler is per-window and preserved across
+ # device reconnects
+ self.force_watching_only = False
+ self.signing = False
+ self.cfg = d.get('cfg', {'mode':0,'pair':''})
+
+ def dump(self):
+ obj = Hardware_KeyStore.dump(self)
+ obj['cfg'] = self.cfg
+ return obj
+
+ def get_derivation(self):
+ return self.derivation
+
+ def get_client(self):
+ return self.plugin.get_client(self).dongleObject
+
+ def get_client_electrum(self):
+ return self.plugin.get_client(self)
+
+ def give_error(self, message, clear_client = False):
+ print_error(message)
+ if not self.signing:
+ self.handler.show_error(message)
+ else:
+ self.signing = False
+ if clear_client:
+ self.client = None
+ raise Exception(message)
+
+ def set_and_unset_signing(func):
+ """Function decorator to set and unset self.signing."""
+ def wrapper(self, *args, **kwargs):
+ try:
+ self.signing = True
+ return func(self, *args, **kwargs)
+ finally:
+ self.signing = False
+ return wrapper
+
+ def address_id_stripped(self, address):
+ # Strip the leading "m/"
+ change, index = self.get_address_index(address)
+ derivation = self.derivation
+ address_path = "%s/%d/%d"%(derivation, change, index)
+ return address_path[2:]
+
+ def decrypt_message(self, pubkey, message, password):
+ raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
+
+ @test_pin_unlocked
+ @set_and_unset_signing
+ def sign_message(self, sequence, message, password):
+ message = message.encode('utf8')
+ message_hash = hashlib.sha256(message).hexdigest().upper()
+ # prompt for the PIN before displaying the dialog if necessary
+ client = self.get_client()
+ address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
+ self.handler.show_message("Signing message ...\r\nMessage hash: "+message_hash)
+ try:
+ info = self.get_client().signMessagePrepare(address_path, message)
+ pin = ""
+ if info['confirmationNeeded']:
+ pin = self.handler.get_auth( info ) # does the authenticate dialog and returns pin
+ if not pin:
+ raise UserWarning(_('Cancelled by user'))
+ pin = str(pin).encode()
+ signature = self.get_client().signMessageSign(pin)
+ except BTChipException as e:
+ if e.sw == 0x6a80:
+ 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.")
+ elif e.sw == 0x6985: # cancelled by user
+ return b''
+ elif e.sw == 0x6982:
+ raise # pin lock. decorator will catch it
+ else:
+ self.give_error(e, True)
+ except UserWarning:
+ self.handler.show_error(_('Cancelled by user'))
+ return b''
+ except Exception as e:
+ self.give_error(e, True)
+ finally:
+ self.handler.finished()
+ # Parse the ASN.1 signature
+ rLength = signature[3]
+ r = signature[4 : 4 + rLength]
+ sLength = signature[4 + rLength + 1]
+ s = signature[4 + rLength + 2:]
+ if rLength == 33:
+ r = r[1:]
+ if sLength == 33:
+ s = s[1:]
+ # And convert it
+ return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s
+
+ @test_pin_unlocked
+ @set_and_unset_signing
+ def sign_transaction(self, tx, password):
+ if tx.is_complete():
+ return
+ client = self.get_client()
+ inputs = []
+ inputsPaths = []
+ pubKeys = []
+ chipInputs = []
+ redeemScripts = []
+ signatures = []
+ preparedTrustedInputs = []
+ changePath = ""
+ output = None
+ p2shTransaction = False
+ segwitTransaction = False
+ pin = ""
+ self.get_client() # prompt for the PIN before displaying the dialog if necessary
+
+ # Fetch inputs of the transaction to sign
+ derivations = self.get_tx_derivations(tx)
+ for txin in tx.inputs():
+ if txin['type'] == 'coinbase':
+ self.give_error("Coinbase not supported") # should never happen
+
+ if txin['type'] in ['p2sh']:
+ p2shTransaction = True
+
+ if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
+ if not self.get_client_electrum().supports_segwit():
+ self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
+ segwitTransaction = True
+
+ if txin['type'] in ['p2wpkh', 'p2wsh']:
+ if not self.get_client_electrum().supports_native_segwit():
+ self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
+ segwitTransaction = True
+
+ pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
+ for i, x_pubkey in enumerate(x_pubkeys):
+ if x_pubkey in derivations:
+ signingPos = i
+ s = derivations.get(x_pubkey)
+ hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1])
+ break
+ else:
+ self.give_error("No matching x_key for sign_transaction") # should never happen
+
+ redeemScript = Transaction.get_preimage_script(txin)
+ txin_prev_tx = txin.get('prev_tx')
+ if txin_prev_tx is None and not Transaction.is_segwit_input(txin):
+ raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
+ txin_prev_tx_raw = txin_prev_tx.raw if txin_prev_tx else None
+ inputs.append([txin_prev_tx_raw,
+ txin['prevout_n'],
+ redeemScript,
+ txin['prevout_hash'],
+ signingPos,
+ txin.get('sequence', 0xffffffff - 1),
+ txin.get('value')])
+ inputsPaths.append(hwAddress)
+ pubKeys.append(pubkeys)
+
+ # Sanity check
+ if p2shTransaction:
+ for txin in tx.inputs():
+ if txin['type'] != 'p2sh':
+ self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen
+
+ txOutput = var_int(len(tx.outputs()))
+ for txout in tx.outputs():
+ output_type, addr, amount = txout
+ txOutput += int_to_hex(amount, 8)
+ script = tx.pay_script(output_type, addr)
+ txOutput += var_int(len(script)//2)
+ txOutput += script
+ txOutput = bfh(txOutput)
+
+ # Recognize outputs
+ # - only one output and one change is authorized (for hw.1 and nano)
+ # - at most one output can bypass confirmation (~change) (for all)
+ if not p2shTransaction:
+ if not self.get_client_electrum().supports_multi_output():
+ if len(tx.outputs()) > 2:
+ self.give_error("Transaction with more than 2 outputs not supported")
+ has_change = False
+ any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
+ for o in tx.outputs():
+ assert o.type == TYPE_ADDRESS
+ info = tx.output_info.get(o.address)
+ if (info is not None) and len(tx.outputs()) > 1 \
+ and not has_change:
+ index = info.address_index
+ on_change_branch = index[0] == 1
+ # prioritise hiding outputs on the 'change' branch from user
+ # because no more than one change address allowed
+ if on_change_branch == any_output_on_change_branch:
+ changePath = self.get_derivation()[2:] + "/%d/%d"%index
+ has_change = True
+ else:
+ output = o.address
+ else:
+ output = o.address
+
+ self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
+ try:
+ # Get trusted inputs from the original transactions
+ for utxo in inputs:
+ sequence = int_to_hex(utxo[5], 4)
+ if segwitTransaction:
+ tmp = bfh(utxo[3])[::-1]
+ tmp += bfh(int_to_hex(utxo[1], 4))
+ tmp += bfh(int_to_hex(utxo[6], 8)) # txin['value']
+ chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence})
+ redeemScripts.append(bfh(utxo[2]))
+ elif not p2shTransaction:
+ txtmp = bitcoinTransaction(bfh(utxo[0]))
+ trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1])
+ trustedInput['sequence'] = sequence
+ chipInputs.append(trustedInput)
+ redeemScripts.append(txtmp.outputs[utxo[1]].script)
+ else:
+ tmp = bfh(utxo[3])[::-1]
+ tmp += bfh(int_to_hex(utxo[1], 4))
+ chipInputs.append({'value' : tmp, 'sequence' : sequence})
+ redeemScripts.append(bfh(utxo[2]))
+
+ # Sign all inputs
+ firstTransaction = True
+ inputIndex = 0
+ rawTx = tx.serialize_to_network()
+ self.get_client().enableAlternate2fa(False)
+ if segwitTransaction:
+ self.get_client().startUntrustedTransaction(True, inputIndex,
+ chipInputs, redeemScripts[inputIndex])
+ if changePath:
+ # we don't set meaningful outputAddress, amount and fees
+ # as we only care about the alternateEncoding==True branch
+ outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx))
+ else:
+ outputData = self.get_client().finalizeInputFull(txOutput)
+ outputData['outputData'] = txOutput
+ transactionOutput = outputData['outputData']
+ if outputData['confirmationNeeded']:
+ outputData['address'] = output
+ self.handler.finished()
+ pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
+ if not pin:
+ raise UserWarning()
+ if pin != 'paired':
+ self.handler.show_message(_("Confirmed. Signing Transaction..."))
+ while inputIndex < len(inputs):
+ singleInput = [ chipInputs[inputIndex] ]
+ self.get_client().startUntrustedTransaction(False, 0,
+ singleInput, redeemScripts[inputIndex])
+ inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
+ inputSignature[0] = 0x30 # force for 1.4.9+
+ signatures.append(inputSignature)
+ inputIndex = inputIndex + 1
+ else:
+ while inputIndex < len(inputs):
+ self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
+ chipInputs, redeemScripts[inputIndex])
+ if changePath:
+ # we don't set meaningful outputAddress, amount and fees
+ # as we only care about the alternateEncoding==True branch
+ outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx))
+ else:
+ outputData = self.get_client().finalizeInputFull(txOutput)
+ outputData['outputData'] = txOutput
+ if firstTransaction:
+ transactionOutput = outputData['outputData']
+ if outputData['confirmationNeeded']:
+ outputData['address'] = output
+ self.handler.finished()
+ pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
+ if not pin:
+ raise UserWarning()
+ if pin != 'paired':
+ self.handler.show_message(_("Confirmed. Signing Transaction..."))
+ else:
+ # Sign input with the provided PIN
+ inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
+ inputSignature[0] = 0x30 # force for 1.4.9+
+ signatures.append(inputSignature)
+ inputIndex = inputIndex + 1
+ if pin != 'paired':
+ firstTransaction = False
+ except UserWarning:
+ self.handler.show_error(_('Cancelled by user'))
+ return
+ except BTChipException as e:
+ if e.sw == 0x6985: # cancelled by user
+ return
+ elif e.sw == 0x6982:
+ raise # pin lock. decorator will catch it
+ else:
+ traceback.print_exc(file=sys.stderr)
+ self.give_error(e, True)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ self.give_error(e, True)
+ finally:
+ self.handler.finished()
+
+ for i, txin in enumerate(tx.inputs()):
+ signingPos = inputs[i][4]
+ tx.add_signature_to_txin(i, signingPos, bh2u(signatures[i]))
+ tx.raw = tx.serialize()
+
+ @test_pin_unlocked
+ @set_and_unset_signing
+ def show_address(self, sequence, txin_type):
+ client = self.get_client()
+ address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
+ self.handler.show_message(_("Showing address ..."))
+ segwit = Transaction.is_segwit_inputtype(txin_type)
+ segwitNative = txin_type == 'p2wpkh'
+ try:
+ client.getWalletPublicKey(address_path, showOnScreen=True, segwit=segwit, segwitNative=segwitNative)
+ except BTChipException as e:
+ if e.sw == 0x6985: # cancelled by user
+ pass
+ elif e.sw == 0x6982:
+ raise # pin lock. decorator will catch it
+ elif e.sw == 0x6b00: # hw.1 raises this
+ self.handler.show_error('{}\n{}\n{}'.format(
+ _('Error showing address') + ':',
+ e,
+ _('Your device might not have support for this functionality.')))
+ else:
+ traceback.print_exc(file=sys.stderr)
+ self.handler.show_error(e)
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ self.handler.show_error(e)
+ finally:
+ self.handler.finished()
+
+class LedgerPlugin(HW_PluginBase):
+ libraries_available = BTCHIP
+ keystore_class = Ledger_KeyStore
+ client = None
+ DEVICE_IDS = [
+ (0x2581, 0x1807), # HW.1 legacy btchip
+ (0x2581, 0x2b7c), # HW.1 transitional production
+ (0x2581, 0x3b7c), # HW.1 ledger production
+ (0x2581, 0x4b7c), # HW.1 ledger test
+ (0x2c97, 0x0000), # Blue
+ (0x2c97, 0x0001) # Nano-S
+ ]
+ SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
+
+ def __init__(self, parent, config, name):
+ self.segwit = config.get("segwit")
+ HW_PluginBase.__init__(self, parent, config, name)
+ if self.libraries_available:
+ self.device_manager().register_devices(self.DEVICE_IDS)
+
+ def get_btchip_device(self, device):
+ ledger = False
+ if device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c:
+ ledger = True
+ if device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c:
+ ledger = True
+ if device.product_key[0] == 0x2c97:
+ if device.interface_number == 0 or device.usage_page == 0xffa0:
+ ledger = True
+ else:
+ return None # non-compatible interface of a Nano S or Blue
+ dev = hid.device()
+ dev.open_path(device.path)
+ dev.set_nonblocking(True)
+ return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
+
+ def create_client(self, device, handler):
+ if handler:
+ self.handler = handler
+
+ client = self.get_btchip_device(device)
+ if client is not None:
+ client = Ledger_Client(client)
+ return client
+
+ def setup_device(self, device_info, wizard, purpose):
+ devmgr = self.device_manager()
+ device_id = device_info.device.id_
+ client = devmgr.client_by_id(device_id)
+ if client is None:
+ raise Exception(_('Failed to create a client for this device.') + '\n' +
+ _('Make sure it is in the correct state.'))
+ client.handler = self.create_handler(wizard)
+ client.get_xpub("m/44'/160'", 'standard') # TODO replace by direct derivation once Nano S > 1.1
+
+ def get_xpub(self, device_id, derivation, xtype, wizard):
+ if xtype not in self.SUPPORTED_XTYPES:
+ raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device))
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ client.handler = self.create_handler(wizard)
+ client.checkDevice()
+ xpub = client.get_xpub(derivation, xtype)
+ return xpub
+
+ def get_client(self, keystore, force_pair=True):
+ # All client interaction should not be in the main GUI thread
+ devmgr = self.device_manager()
+ handler = keystore.handler
+ with devmgr.hid_lock:
+ client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
+ # returns the client for a given keystore. can use xpub
+ #if client:
+ # client.used()
+ if client is not None:
+ client.checkDevice()
+ return client
+
+ def show_address(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not self.show_address_helper(wallet, address, keystore):
+ return
+ if type(wallet) is not Standard_Wallet:
+ keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device))
+ return
+ sequence = wallet.get_address_index(address)
+ txin_type = wallet.get_txin_type(address)
+ keystore.show_address(sequence, txin_type)
diff --git a/electrum/plugins/ledger/qt.py b/electrum/plugins/ledger/qt.py
new file mode 100644
index 000000000..d49bcb8d6
--- /dev/null
+++ b/electrum/plugins/ledger/qt.py
@@ -0,0 +1,81 @@
+#from btchip.btchipPersoWizard import StartBTChipPersoDialog
+
+from electrum.i18n import _
+from electrum.plugin import hook
+from electrum.wallet import Standard_Wallet
+from electrum.gui.qt.util import *
+
+from .ledger import LedgerPlugin
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+
+
+class Plugin(LedgerPlugin, QtPluginBase):
+ icon_unpaired = ":icons/ledger_unpaired.png"
+ icon_paired = ":icons/ledger.png"
+
+ def create_handler(self, window):
+ return Ledger_Handler(window)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if type(wallet) is not Standard_Wallet:
+ return
+ keystore = wallet.get_keystore()
+ if type(keystore) == self.keystore_class and len(addrs) == 1:
+ def show_address():
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
+ menu.addAction(_("Show on Ledger"), show_address)
+
+class Ledger_Handler(QtHandlerBase):
+ setup_signal = pyqtSignal()
+ auth_signal = pyqtSignal(object)
+
+ def __init__(self, win):
+ super(Ledger_Handler, self).__init__(win, 'Ledger')
+ self.setup_signal.connect(self.setup_dialog)
+ self.auth_signal.connect(self.auth_dialog)
+
+ def word_dialog(self, msg):
+ response = QInputDialog.getText(self.top_level_window(), "Ledger Wallet Authentication", msg, QLineEdit.Password)
+ if not response[1]:
+ self.word = None
+ else:
+ self.word = str(response[0])
+ self.done.set()
+
+ def message_dialog(self, msg):
+ self.clear_dialog()
+ self.dialog = dialog = WindowModalDialog(self.top_level_window(), _("Ledger Status"))
+ l = QLabel(msg)
+ vbox = QVBoxLayout(dialog)
+ vbox.addWidget(l)
+ dialog.show()
+
+ def auth_dialog(self, data):
+ try:
+ from .auth2fa import LedgerAuthDialog
+ except ImportError as e:
+ self.message_dialog(str(e))
+ return
+ dialog = LedgerAuthDialog(self, data)
+ dialog.exec_()
+ self.word = dialog.pin
+ self.done.set()
+
+ def get_auth(self, data):
+ self.done.clear()
+ self.auth_signal.emit(data)
+ self.done.wait()
+ return self.word
+
+ def get_setup(self):
+ self.done.clear()
+ self.setup_signal.emit()
+ self.done.wait()
+ return
+
+ def setup_dialog(self):
+ self.show_error(_('Initialization of Ledger HW devices is currently disabled.'))
+ return
+ dialog = StartBTChipPersoDialog()
+ dialog.exec_()
diff --git a/electrum/plugins/revealer/DejaVuSansMono-Bold.ttf b/electrum/plugins/revealer/DejaVuSansMono-Bold.ttf
new file mode 100644
index 000000000..cbcdd31d6
Binary files /dev/null and b/electrum/plugins/revealer/DejaVuSansMono-Bold.ttf differ
diff --git a/electrum/plugins/revealer/LICENSE_DEJAVU.txt b/electrum/plugins/revealer/LICENSE_DEJAVU.txt
new file mode 100644
index 000000000..254e2cc42
--- /dev/null
+++ b/electrum/plugins/revealer/LICENSE_DEJAVU.txt
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/electrum/plugins/revealer/SIL Open Font License.txt b/electrum/plugins/revealer/SIL Open Font License.txt
new file mode 100644
index 000000000..295975a5d
--- /dev/null
+++ b/electrum/plugins/revealer/SIL Open Font License.txt
@@ -0,0 +1,43 @@
+Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
+
+5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
\ No newline at end of file
diff --git a/electrum/plugins/revealer/SourceSansPro-Bold.otf b/electrum/plugins/revealer/SourceSansPro-Bold.otf
new file mode 100644
index 000000000..98dbee74d
Binary files /dev/null and b/electrum/plugins/revealer/SourceSansPro-Bold.otf differ
diff --git a/electrum/plugins/revealer/__init__.py b/electrum/plugins/revealer/__init__.py
new file mode 100644
index 000000000..dcc8c31fd
--- /dev/null
+++ b/electrum/plugins/revealer/__init__.py
@@ -0,0 +1,16 @@
+from electrum.i18n import _
+
+fullname = _('Revealer')
+description = ''.join([" ",
+ ""+_("Do you have something to hide ?")+" ", ' ', ' ',
+ _("Revealer is a seed phrase back-up solution. It allows you to create a cold, analog, multi-factor backup of your wallet seeds, or of any arbitrary secret."), ' ', ' ',
+ _("Using a Revealer is better than writing your seed phrases on paper: a revealer is invulnerable to physical access and allows creation of trustless redundancy."), ' ', ' ',
+ _("This plug-in allows you to generate a pdf file of your secret phrase encrypted visually for your physical Revealer. You can print it trustlessly - it can only be decrypted optically with your Revealer."), ' ', ' ',
+ _("The plug-in also allows you to generate a digital Revealer file and print it yourself on a transparent overhead foil."), ' ', ' ',
+ _("Once activated you can access the plug-in through the icon at the seed dialog."), ' ', ' ',
+ _("For more information, visit"),
+ " https://revealer.cc ", ' ', ' ',
+])
+available_for = ['qt']
+
+
diff --git a/electrum/plugins/revealer/hmac_drbg.py b/electrum/plugins/revealer/hmac_drbg.py
new file mode 100644
index 000000000..ca5334695
--- /dev/null
+++ b/electrum/plugins/revealer/hmac_drbg.py
@@ -0,0 +1,51 @@
+'''
+Copyright (c) 2014 David Lazar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+'''
+
+import hashlib
+import hmac
+
+class DRBG(object):
+ def __init__(self, seed):
+ self.key = b'\x00' * 64
+ self.val = b'\x01' * 64
+ self.reseed(seed)
+
+ def hmac(self, key, val):
+ return hmac.new(key, val, hashlib.sha512).digest()
+
+ def reseed(self, data=b''):
+ self.key = self.hmac(self.key, self.val + b'\x00' + data)
+ self.val = self.hmac(self.key, self.val)
+
+ if data:
+ self.key = self.hmac(self.key, self.val + b'\x01' + data)
+ self.val = self.hmac(self.key, self.val)
+
+ def generate(self, n):
+ xs = b''
+ while len(xs) < n:
+ self.val = self.hmac(self.key, self.val)
+ xs += self.val
+
+ self.reseed()
+
+ return xs[:n]
diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py
new file mode 100644
index 000000000..d64dc585e
--- /dev/null
+++ b/electrum/plugins/revealer/qt.py
@@ -0,0 +1,752 @@
+'''
+
+Revealer
+So you have something to hide?
+
+plug-in for the electrum wallet.
+
+Features:
+ - Deep Cold multi-factor backup solution
+ - Safety - One time pad security
+ - Redundancy - Trustless printing & distribution
+ - Encrypt your seedphrase or any secret you want for your revealer
+ - Based on crypto by legendary cryptographers Naor and Shamir
+
+Tiago Romagnani Silveira, 2017
+
+'''
+
+import os
+import random
+import qrcode
+import traceback
+from hashlib import sha256
+from decimal import Decimal
+import binascii
+
+from PyQt5.QtPrintSupport import QPrinter
+
+from electrum.plugin import BasePlugin, hook
+from electrum.i18n import _
+from electrum.util import to_bytes, make_dir
+from electrum.gui.qt.util import *
+from electrum.gui.qt.qrtextedit import ScanQRTextEdit
+
+from .hmac_drbg import DRBG
+
+class Plugin(BasePlugin):
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ self.base_dir = config.electrum_path()+'/revealer/'
+
+ if self.config.get('calibration_h') is None:
+ self.config.set_key('calibration_h', 0)
+ if self.config.get('calibration_v') is None:
+ self.config.set_key('calibration_v', 0)
+
+ self.calibration_h = self.config.get('calibration_h')
+ self.calibration_v = self.config.get('calibration_v')
+
+ self.version = '1'
+ self.size = (159, 97)
+ self.f_size = QSize(1014*2, 642*2)
+ self.abstand_h = 21
+ self.abstand_v = 34
+ self.calibration_noise = int('10' * 128)
+ self.rawnoise = False
+ make_dir(self.base_dir)
+
+ @hook
+ def set_seed(self, seed, has_extension, parent):
+ self.cseed = seed.upper()
+ self.has_extension = has_extension
+ parent.addButton(':icons/revealer.png', partial(self.setup_dialog, parent), "Revealer"+_(" secret backup utility"))
+
+ def requires_settings(self):
+ return True
+
+ def settings_widget(self, window):
+ return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window))
+
+ def setup_dialog(self, window):
+ self.update_wallet_name(window.parent().parent().wallet)
+ self.user_input = False
+ self.noise_seed = False
+ self.d = WindowModalDialog(window, "Revealer")
+ self.d.setMinimumWidth(420)
+ vbox = QVBoxLayout(self.d)
+ vbox.addSpacing(21)
+ logo = QLabel()
+ vbox.addWidget(logo)
+ logo.setPixmap(QPixmap(':icons/revealer.png'))
+ logo.setAlignment(Qt.AlignCenter)
+ vbox.addSpacing(42)
+
+ self.load_noise = ScanQRTextEdit()
+ self.load_noise.setTabChangesFocus(True)
+ self.load_noise.textChanged.connect(self.on_edit)
+ self.load_noise.setMaximumHeight(33)
+
+ vbox.addWidget(WWLabel(""+_("Enter your physical revealer code:")+""))
+ vbox.addWidget(self.load_noise)
+ vbox.addSpacing(11)
+
+ self.next_button = QPushButton(_("Next"), self.d)
+ self.next_button.setDefault(True)
+ self.next_button.setEnabled(False)
+ vbox.addLayout(Buttons(self.next_button))
+ self.next_button.clicked.connect(self.d.close)
+ self.next_button.clicked.connect(partial(self.cypherseed_dialog, window))
+ vbox.addSpacing(21)
+
+ vbox.addWidget(WWLabel(_("or, alternatively: ")))
+ bcreate = QPushButton(_("Create a digital Revealer"))
+
+ def mk_digital():
+ try:
+ self.make_digital(self.d)
+ except Exception:
+ traceback.print_exc(file=sys.stdout)
+ else:
+ self.cypherseed_dialog(window)
+
+ bcreate.clicked.connect(mk_digital)
+
+ vbox.addWidget(bcreate)
+ vbox.addSpacing(11)
+ vbox.addWidget(QLabel(''.join([ ""+_("WARNING")+ " :" + _("Printing a revealer and encrypted seed"), ' ',
+ _("on the same printer is not trustless towards the printer."), ' ',
+ ])))
+ vbox.addSpacing(11)
+ vbox.addLayout(Buttons(CloseButton(self.d)))
+
+ return bool(self.d.exec_())
+
+ def get_noise(self):
+ text = self.load_noise.text()
+ return ''.join(text.split()).lower()
+
+ def on_edit(self):
+ s = self.get_noise()
+ b = self.is_noise(s)
+ if b:
+ self.noise_seed = s[1:-3]
+ self.user_input = True
+ self.next_button.setEnabled(b)
+
+ def code_hashid(self, txt):
+ x = to_bytes(txt, 'utf8')
+ hash = sha256(x).hexdigest()
+ return hash[-3:].upper()
+
+ def is_noise(self, txt):
+ if (len(txt) >= 34):
+ try:
+ int(txt, 16)
+ except:
+ self.user_input = False
+ return False
+ else:
+ id = self.code_hashid(txt[:-3])
+ if (txt[-3:].upper() == id.upper()):
+ self.code_id = id
+ self.user_input = True
+ return True
+ else:
+ return False
+ else:
+
+ if (len(txt)>0 and txt[0]=='0'):
+ self.d.show_message(''.join(["",_("Warning: "), " ", _("Revealers starting with 0 had a vulnerability and are not supported.")]))
+ self.user_input = False
+ return False
+
+ def make_digital(self, dialog):
+ self.make_rawnoise(True)
+ self.bdone(dialog)
+ self.d.close()
+
+ def bcrypt(self, dialog):
+ self.rawnoise = False
+ dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at:").format(self.was, self.version, self.code_id),
+ " ","", self.base_dir+ self.filename+self.version+"_"+self.code_id," "]))
+ dialog.close()
+
+ def ext_warning(self, dialog):
+ dialog.show_message(''.join(["",_("Warning: "), " ", _("your seed extension will not be included in the encrypted backup.")]))
+ dialog.close()
+
+ def bdone(self, dialog):
+ dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(self.version, self.code_id),
+ " ","", self.base_dir + 'revealer_' +self.version + '_'+ self.code_id, ' ']))
+
+
+ def customtxt_limits(self):
+ txt = self.text.text()
+ self.max_chars.setVisible(False)
+ self.char_count.setText("("+str(len(txt))+"/216)")
+ if len(txt)>0:
+ self.ctext.setEnabled(True)
+ if len(txt) > 216:
+ self.text.setPlainText(self.text.toPlainText()[:216])
+ self.max_chars.setVisible(True)
+
+ def t(self):
+ self.txt = self.text.text()
+ self.seed_img(is_seed=False)
+
+ def cypherseed_dialog(self, window):
+
+ d = WindowModalDialog(window, "Revealer")
+ d.setMinimumWidth(420)
+
+ self.c_dialog = d
+
+ self.vbox = QVBoxLayout(d)
+ self.vbox.addSpacing(21)
+
+ logo = QLabel()
+ self.vbox.addWidget(logo)
+ logo.setPixmap(QPixmap(':icons/revealer.png'))
+ logo.setAlignment(Qt.AlignCenter)
+ self.vbox.addSpacing(42)
+
+ grid = QGridLayout()
+ self.vbox.addLayout(grid)
+
+ cprint = QPushButton(_("Generate encrypted seed backup"))
+ cprint.clicked.connect(partial(self.seed_img, True))
+ self.vbox.addWidget(cprint)
+ self.vbox.addSpacing(14)
+
+ self.vbox.addWidget(WWLabel(_("OR type any secret below:")))
+ self.text = ScanQRTextEdit()
+ self.text.setTabChangesFocus(True)
+ self.text.setMaximumHeight(70)
+ self.text.textChanged.connect(self.customtxt_limits)
+ self.vbox.addWidget(self.text)
+
+ self.char_count = WWLabel("")
+ self.char_count.setAlignment(Qt.AlignRight)
+ self.vbox.addWidget(self.char_count)
+
+ self.max_chars = WWLabel("" + _("This version supports a maximum of 216 characters.")+" ")
+ self.vbox.addWidget(self.max_chars)
+ self.max_chars.setVisible(False)
+
+ self.ctext = QPushButton(_("Generate custom secret encrypted backup"))
+ self.ctext.clicked.connect(self.t)
+
+ self.vbox.addWidget(self.ctext)
+ self.ctext.setEnabled(False)
+
+ self.vbox.addSpacing(11)
+ self.vbox.addWidget(
+ QLabel(''.join(["" + _("WARNING") + " : " + _("Revealer is a one-time-pad and should be used only once."), ' ',
+ _("Multiple secrets encrypted for the same Revealer can be attacked."), ' ',
+ ])))
+ self.vbox.addSpacing(11)
+
+ self.vbox.addSpacing(21)
+ self.vbox.addLayout(Buttons(CloseButton(d)))
+ return bool(d.exec_())
+
+
+ def update_wallet_name (self, name):
+ self.wallet_name = str(name)
+ self.base_name = self.base_dir + self.wallet_name
+
+ def seed_img(self, is_seed = True):
+
+ if not self.cseed and self.txt == False:
+ return
+
+ if is_seed:
+ txt = self.cseed
+ else:
+ txt = self.txt.upper()
+
+ img = QImage(self.size[0],self.size[1], QImage.Format_Mono)
+ bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
+ bitmap.fill(Qt.white)
+ painter = QPainter()
+ painter.begin(bitmap)
+ QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf') )
+ if len(txt) < 102 :
+ fontsize = 15
+ linespace = 15
+ max_letters = 17
+ max_lines = 6
+ max_words = 3
+ else:
+ fontsize = 12
+ linespace = 10
+ max_letters = 23
+ max_lines = 9
+ max_words = int(max_letters/4)
+
+ font = QFont('Source Sans Pro', fontsize, QFont.Bold)
+ font.setLetterSpacing(QFont.PercentageSpacing, 100)
+ font.setPixelSize(fontsize)
+ painter.setFont(font)
+ seed_array = txt.split(' ')
+
+ for n in range(max_lines):
+ nwords = max_words
+ temp_seed = seed_array[:nwords]
+ while len(' '.join(map(str, temp_seed))) > max_letters:
+ nwords = nwords - 1
+ temp_seed = seed_array[:nwords]
+ painter.drawText(QRect(0, linespace*n , self.size[0], self.size[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
+ del seed_array[:nwords]
+
+ painter.end()
+ img = bitmap.toImage()
+ if (self.rawnoise == False):
+ self.make_rawnoise()
+
+ self.make_cypherseed(img, self.rawnoise, False, is_seed)
+ return img
+
+ def make_rawnoise(self, create_revealer=False):
+ w = self.size[0]
+ h = self.size[1]
+ rawnoise = QImage(w, h, QImage.Format_Mono)
+
+ if(self.noise_seed == False):
+ self.noise_seed = random.SystemRandom().getrandbits(128)
+ self.hex_noise = format(self.noise_seed, '032x')
+ self.hex_noise = self.version + str(self.hex_noise)
+
+ if (self.user_input == True):
+ self.noise_seed = int(self.noise_seed, 16)
+ self.hex_noise = self.version + str(format(self.noise_seed, '032x'))
+
+ self.code_id = self.code_hashid(self.hex_noise)
+ self.hex_noise = ' '.join(self.hex_noise[i:i+4] for i in range(0,len(self.hex_noise),4))
+
+ entropy = binascii.unhexlify(str(format(self.noise_seed, '032x')))
+ code_id = binascii.unhexlify(self.version + self.code_id)
+
+ drbg = DRBG(entropy + code_id)
+ noise_array=bin(int.from_bytes(drbg.generate(1929), 'big'))[2:]
+
+ i=0
+ for x in range(w):
+ for y in range(h):
+ rawnoise.setPixel(x,y,int(noise_array[i]))
+ i+=1
+
+ self.rawnoise = rawnoise
+ if create_revealer==True:
+ self.make_revealer()
+ self.noise_seed = False
+
+ def make_calnoise(self):
+ random.seed(self.calibration_noise)
+ w = self.size[0]
+ h = self.size[1]
+ rawnoise = QImage(w, h, QImage.Format_Mono)
+ for x in range(w):
+ for y in range(h):
+ rawnoise.setPixel(x,y,random.randint(0, 1))
+ self.calnoise = self.pixelcode_2x2(rawnoise)
+
+ def make_revealer(self):
+ revealer = self.pixelcode_2x2(self.rawnoise)
+ revealer.invertPixels()
+ revealer = QBitmap.fromImage(revealer)
+ revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio)
+ revealer = self.overlay_marks(revealer)
+
+ self.filename = 'Revealer - '
+ revealer.save(self.base_dir + self.filename + self.version+'_'+self.code_id + '.png')
+ self.toPdf(QImage(revealer))
+ QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.abspath(self.base_dir + self.filename + self.version+'_'+ self.code_id + '.pdf')))
+
+ def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True):
+ img = img.convertToFormat(QImage.Format_Mono)
+ p = QPainter()
+ p.begin(img)
+ p.setCompositionMode(26) #xor
+ p.drawImage(0, 0, rawnoise)
+ p.end()
+ cypherseed = self.pixelcode_2x2(img)
+ cypherseed = QBitmap.fromImage(cypherseed)
+ cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio)
+ cypherseed = self.overlay_marks(cypherseed, True, calibration)
+
+ if not is_seed:
+ self.filename = _('custom_secret')+'_'
+ self.was = _('Custom secret')
+ else:
+ self.filename = self.wallet_name+'_'+ _('seed')+'_'
+ self.was = self.wallet_name +' ' + _('seed')
+
+ if self.has_extension:
+ self.ext_warning(self.c_dialog)
+
+ if not calibration:
+ self.toPdf(QImage(cypherseed))
+ QDesktopServices.openUrl (QUrl.fromLocalFile(os.path.abspath(self.base_dir+self.filename+self.version+'_'+self.code_id+'.pdf')))
+ cypherseed.save(self.base_dir + self.filename +self.version + '_'+ self.code_id + '.png')
+ self.bcrypt(self.c_dialog)
+ return cypherseed
+
+ def calibration(self):
+ img = QImage(self.size[0],self.size[1], QImage.Format_Mono)
+ bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
+ bitmap.fill(Qt.black)
+ self.make_calnoise()
+ img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True)
+ self.calibration_pdf(img)
+ QDesktopServices.openUrl (QUrl.fromLocalFile(os.path.abspath(self.base_dir+_('calibration')+'.pdf')))
+ return img
+
+ def toPdf(self, image):
+ printer = QPrinter()
+ printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
+ printer.setResolution(600)
+ printer.setOutputFormat(QPrinter.PdfFormat)
+ printer.setOutputFileName(self.base_dir+self.filename+self.version + '_'+self.code_id+'.pdf')
+ printer.setPageMargins(0,0,0,0,6)
+ painter = QPainter()
+ painter.begin(printer)
+
+ delta_h = round(image.width()/self.abstand_v)
+ delta_v = round(image.height()/self.abstand_h)
+
+ size_h = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2)
+ size_v = 1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))/2)
+
+ image = image.scaled(size_h, size_v)
+
+ painter.drawImage(553,533, image)
+ wpath = QPainterPath()
+ wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19)
+ painter.setPen(QPen(Qt.black, 1))
+ painter.drawPath(wpath)
+ painter.end()
+
+ def calibration_pdf(self, image):
+ printer = QPrinter()
+ printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
+ printer.setResolution(600)
+ printer.setOutputFormat(QPrinter.PdfFormat)
+ printer.setOutputFileName(self.base_dir+_('calibration')+'.pdf')
+ printer.setPageMargins(0,0,0,0,6)
+
+ painter = QPainter()
+ painter.begin(printer)
+ painter.drawImage(553,533, image)
+ font = QFont('Source Sans Pro', 10, QFont.Bold)
+ painter.setFont(font)
+ painter.drawText(254,277, _("Calibration sheet"))
+ font = QFont('Source Sans Pro', 7, QFont.Bold)
+ painter.setFont(font)
+ painter.drawText(600,2077, _("Instructions:"))
+ font = QFont('Source Sans Pro', 7, QFont.Normal)
+ painter.setFont(font)
+ painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface."))
+ painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left."))
+ painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best "
+ "match on the opposite sides. "))
+ painter.drawText(700, 2477, _("4. Type the numbers in the software"))
+ painter.end()
+
+ def pixelcode_2x2(self, img):
+ result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32 )
+ white = qRgba(255,255,255,0)
+ black = qRgba(0,0,0,255)
+
+ for x in range(img.width()):
+ for y in range(img.height()):
+ c = img.pixel(QPoint(x,y))
+ colors = QColor(c).getRgbF()
+ if colors[0]:
+ result.setPixel(x*2+1,y*2+1, black)
+ result.setPixel(x*2,y*2+1, white)
+ result.setPixel(x*2+1,y*2, white)
+ result.setPixel(x*2, y*2, black)
+
+ else:
+ result.setPixel(x*2+1,y*2+1, white)
+ result.setPixel(x*2,y*2+1, black)
+ result.setPixel(x*2+1,y*2, black)
+ result.setPixel(x*2, y*2, white)
+ return result
+
+ def overlay_marks(self, img, is_cseed=False, calibration_sheet=False):
+ border_color = Qt.white
+ base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32)
+ base_img.fill(border_color)
+ img = QImage(img)
+
+ painter = QPainter()
+ painter.begin(base_img)
+
+ total_distance_h = round(base_img.width() / self.abstand_v)
+ dist_v = round(total_distance_h) / 2
+ dist_h = round(total_distance_h) / 2
+
+ img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h)))
+ painter.drawImage(total_distance_h,
+ total_distance_h,
+ img)
+
+ #frame around image
+ pen = QPen(Qt.black, 2)
+ painter.setPen(pen)
+
+ #horz
+ painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h)
+ painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h))
+ #vert
+ painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height())
+ painter.drawLine(base_img.width()-(total_distance_h), 0, base_img.width()-(total_distance_h), base_img.height())
+
+ #border around img
+ border_thick = 6
+ Rpath = QPainterPath()
+ Rpath.addRect(QRectF((total_distance_h)+(border_thick/2),
+ (total_distance_h)+(border_thick/2),
+ base_img.width()-((total_distance_h)*2)-((border_thick)-1),
+ (base_img.height()-((total_distance_h))*2)-((border_thick)-1)))
+ pen = QPen(Qt.black, border_thick)
+ pen.setJoinStyle (Qt.MiterJoin)
+
+ painter.setPen(pen)
+ painter.drawPath(Rpath)
+
+ Bpath = QPainterPath()
+ Bpath.addRect(QRectF((total_distance_h), (total_distance_h),
+ base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2)))
+ pen = QPen(Qt.black, 1)
+ painter.setPen(pen)
+ painter.drawPath(Bpath)
+
+ pen = QPen(Qt.black, 1)
+ painter.setPen(pen)
+ painter.drawLine(0, base_img.height()/2, total_distance_h, base_img.height()/2)
+ painter.drawLine(base_img.width()/2, 0, base_img.width()/2, total_distance_h)
+
+ painter.drawLine(base_img.width()-total_distance_h, base_img.height()/2, base_img.width(), base_img.height()/2)
+ painter.drawLine(base_img.width()/2, base_img.height(), base_img.width()/2, base_img.height() - total_distance_h)
+
+ #print code
+ f_size = 37
+ QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf'))
+ font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold)
+ font.setPixelSize(35)
+ painter.setFont(font)
+
+ if not calibration_sheet:
+ if is_cseed: #its a secret
+ painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
+ painter.drawLine(0, dist_v, base_img.width(), dist_v)
+ painter.drawLine(dist_h, 0, dist_h, base_img.height())
+ painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
+ painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
+
+ painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11,
+ QImage(':icons/electrumb.png').scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation))
+
+ painter.setPen(QPen(Qt.white, border_thick*8))
+ painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2,
+ (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2,
+ base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77,
+ (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2)
+ painter.setPen(QColor(0,0,0,255))
+ painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11,
+ base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.version + '_'+self.code_id)
+ painter.end()
+
+ else: # revealer
+
+ painter.setPen(QPen(border_color, 17))
+ painter.drawLine(0, dist_v, base_img.width(), dist_v)
+ painter.drawLine(dist_h, 0, dist_h, base_img.height())
+ painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
+ painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
+
+ painter.setPen(QPen(Qt.black, 2))
+ painter.drawLine(0, dist_v, base_img.width(), dist_v)
+ painter.drawLine(dist_h, 0, dist_h, base_img.height())
+ painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
+ painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
+ logo = QImage(':icons/revealer_c.png').scaledToWidth(1.3*(total_distance_h))
+ painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation)
+
+ #frame around logo
+ painter.setPen(QPen(Qt.black, border_thick))
+ painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2),
+ total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2))
+ painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick),
+ total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick))
+
+ #frame around code/qr
+ qr_size = 179
+
+ painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size,
+ (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2,
+ (base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size,
+ (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2)
+
+ painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size,
+ (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2,
+ base_img.width()/2 + (total_distance_h/2)-border_thick-(border_thick*8)/2-qr_size,
+ ((base_img.height()-((total_distance_h)))-(border_thick/2)-2))
+
+ painter.setPen(QPen(Qt.white, border_thick * 8))
+ painter.drawLine(
+ base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2,
+ (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2,
+ base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size,
+ (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2)
+
+ painter.setPen(QColor(0,0,0,255))
+ painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107,
+ base_img.width()-total_distance_h - border_thick -93,
+ base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.hex_noise.upper())
+ painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size,
+ base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.code_id)
+
+ # draw qr code
+ qr_qt = self.paintQR(self.hex_noise.upper() +self.code_id)
+ target = QRectF(base_img.width()-65-qr_size,
+ base_img.height()-65-qr_size,
+ qr_size, qr_size )
+ painter.drawImage(target, qr_qt)
+ painter.setPen(QPen(Qt.black, 4))
+ painter.drawLine(base_img.width()-65-qr_size,
+ base_img.height()-65-qr_size,
+ base_img.width() - 65 - qr_size,
+ (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4
+ )
+ painter.drawLine(base_img.width()-65-qr_size,
+ base_img.height()-65-qr_size,
+ base_img.width() - 65,
+ base_img.height()-65-qr_size
+ )
+ painter.end()
+
+ else: # calibration only
+ painter.end()
+ cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100,
+ QImage.Format_ARGB32)
+ cal_img.fill(Qt.white)
+
+ cal_painter = QPainter()
+ cal_painter.begin(cal_img)
+ cal_painter.drawImage(0,0, base_img)
+
+ #black lines in the middle of border top left only
+ cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
+ cal_painter.drawLine(0, dist_v, base_img.width(), dist_v)
+ cal_painter.drawLine(dist_h, 0, dist_h, base_img.height())
+
+ pen = QPen(Qt.black, 2, Qt.DashDotDotLine)
+ cal_painter.setPen(pen)
+ n=15
+
+ cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold))
+ for x in range(-n,n):
+ #lines on bottom (vertical calibration)
+ cal_painter.drawLine((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13,
+ x+2+base_img.height()-(dist_v),
+ (((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13,
+ x+2+base_img.height()-(dist_v))
+
+ num_pos = 9
+ if x > 9 : num_pos = 17
+ if x < 0 : num_pos = 20
+ if x < -9: num_pos = 27
+
+ cal_painter.drawText((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos,
+ 50+base_img.height()-(dist_v),
+ str(x))
+
+ #lines on the right (horizontal calibrations)
+
+ cal_painter.drawLine(x+2+(base_img.width()-(dist_h)),
+ ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13,
+ x+2+(base_img.width()-(dist_h)),
+ ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13)
+
+
+ cal_painter.drawText(30+(base_img.width()-(dist_h)),
+ ((base_img.height()/(2*n)) *(x))+ (base_img.height()/2)+13, str(x))
+
+ cal_painter.end()
+ base_img = cal_img
+
+ return base_img
+
+ def paintQR(self, data):
+ if not data:
+ return
+ qr = qrcode.QRCode()
+ qr.add_data(data)
+ matrix = qr.get_matrix()
+ k = len(matrix)
+ border_color = Qt.white
+ base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32)
+ base_img.fill(border_color)
+ qrpainter = QPainter()
+ qrpainter.begin(base_img)
+ boxsize = 5
+ size = k * boxsize
+ left = (base_img.width() - size)/2
+ top = (base_img.height() - size)/2
+ qrpainter.setBrush(Qt.black)
+ qrpainter.setPen(Qt.black)
+
+ for r in range(k):
+ for c in range(k):
+ if matrix[r][c]:
+ qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)
+ qrpainter.end()
+ return base_img
+
+ def calibration_dialog(self, window):
+ d = WindowModalDialog(window, _("Revealer - Printer calibration settings"))
+
+ d.setMinimumSize(100, 200)
+
+ vbox = QVBoxLayout(d)
+ vbox.addWidget(QLabel(''.join([" ", _("If you have an old printer, or want optimal precision")," ",
+ _("print the calibration pdf and follow the instructions "), " "," ",
+ ])))
+ self.calibration_h = self.config.get('calibration_h')
+ self.calibration_v = self.config.get('calibration_v')
+ cprint = QPushButton(_("Open calibration pdf"))
+ cprint.clicked.connect(self.calibration)
+ vbox.addWidget(cprint)
+
+ vbox.addWidget(QLabel(_('Calibration values:')))
+ grid = QGridLayout()
+ vbox.addLayout(grid)
+ grid.addWidget(QLabel(_('Right side')), 0, 0)
+ horizontal = QLineEdit()
+ horizontal.setText(str(self.calibration_h))
+ grid.addWidget(horizontal, 0, 1)
+
+ grid.addWidget(QLabel(_('Bottom')), 1, 0)
+ vertical = QLineEdit()
+ vertical.setText(str(self.calibration_v))
+ grid.addWidget(vertical, 1, 1)
+
+ vbox.addStretch()
+ vbox.addSpacing(13)
+ vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
+
+ if not d.exec_():
+ return
+
+ self.calibration_h = int(Decimal(horizontal.text()))
+ self.config.set_key('calibration_h', self.calibration_h)
+ self.calibration_v = int(Decimal(vertical.text()))
+ self.config.set_key('calibration_v', self.calibration_v)
+
+
diff --git a/electrum/plugins/safe_t/__init__.py b/electrum/plugins/safe_t/__init__.py
new file mode 100644
index 000000000..9bfb2d9bb
--- /dev/null
+++ b/electrum/plugins/safe_t/__init__.py
@@ -0,0 +1,8 @@
+from electrum.i18n import _
+
+fullname = 'Safe-T mini Wallet'
+description = _('Provides support for Safe-T mini hardware wallet')
+requires = [('safetlib','github.com/archos-safe-t/python-safet')]
+registers_keystore = ('hardware', 'safe_t', _("Safe-T mini wallet"))
+available_for = ['qt', 'cmdline']
+
diff --git a/electrum/plugins/safe_t/client.py b/electrum/plugins/safe_t/client.py
new file mode 100644
index 000000000..568f753bb
--- /dev/null
+++ b/electrum/plugins/safe_t/client.py
@@ -0,0 +1,11 @@
+from safetlib.client import proto, BaseClient, ProtocolMixin
+from .clientbase import SafeTClientBase
+
+class SafeTClient(SafeTClientBase, ProtocolMixin, BaseClient):
+ def __init__(self, transport, handler, plugin):
+ BaseClient.__init__(self, transport=transport)
+ ProtocolMixin.__init__(self, transport=transport)
+ SafeTClientBase.__init__(self, handler, plugin, proto)
+
+
+SafeTClientBase.wrap_methods(SafeTClient)
diff --git a/electrum/plugins/safe_t/clientbase.py b/electrum/plugins/safe_t/clientbase.py
new file mode 100644
index 000000000..ef000065e
--- /dev/null
+++ b/electrum/plugins/safe_t/clientbase.py
@@ -0,0 +1,238 @@
+import time
+from struct import pack
+
+from electrum.i18n import _
+from electrum.util import PrintError, UserCancelled
+from electrum.keystore import bip39_normalize_passphrase
+from electrum.bitcoin import serialize_xpub, convert_bip32_path_to_list_of_uint32
+
+
+class GuiMixin(object):
+ # Requires: self.proto, self.device
+
+ # ref: https://github.com/trezor/trezor-common/blob/44dfb07cfaafffada4b2ce0d15ba1d90d17cf35e/protob/types.proto#L89
+ messages = {
+ 3: _("Confirm the transaction output on your {} device"),
+ 4: _("Confirm internal entropy on your {} device to begin"),
+ 5: _("Write down the seed word shown on your {}"),
+ 6: _("Confirm on your {} that you want to wipe it clean"),
+ 7: _("Confirm on your {} device the message to sign"),
+ 8: _("Confirm the total amount spent and the transaction fee on your "
+ "{} device"),
+ 10: _("Confirm wallet address on your {} device"),
+ 14: _("Choose on your {} device where to enter your passphrase"),
+ 'default': _("Check your {} device to continue"),
+ }
+
+ def callback_Failure(self, msg):
+ # BaseClient's unfortunate call() implementation forces us to
+ # raise exceptions on failure in order to unwind the stack.
+ # However, making the user acknowledge they cancelled
+ # gets old very quickly, so we suppress those. The NotInitialized
+ # one is misnamed and indicates a passphrase request was cancelled.
+ if msg.code in (self.types.FailureType.PinCancelled,
+ self.types.FailureType.ActionCancelled,
+ self.types.FailureType.NotInitialized):
+ raise UserCancelled()
+ raise RuntimeError(msg.message)
+
+ def callback_ButtonRequest(self, msg):
+ message = self.msg
+ if not message:
+ message = self.messages.get(msg.code, self.messages['default'])
+ self.handler.show_message(message.format(self.device), self.cancel)
+ return self.proto.ButtonAck()
+
+ def callback_PinMatrixRequest(self, msg):
+ if msg.type == 2:
+ msg = _("Enter a new PIN for your {}:")
+ elif msg.type == 3:
+ msg = (_("Re-enter the new PIN for your {}.\n\n"
+ "NOTE: the positions of the numbers have changed!"))
+ else:
+ msg = _("Enter your current {} PIN:")
+ pin = self.handler.get_pin(msg.format(self.device))
+ if len(pin) > 9:
+ self.handler.show_error(_('The PIN cannot be longer than 9 characters.'))
+ pin = '' # to cancel below
+ if not pin:
+ return self.proto.Cancel()
+ return self.proto.PinMatrixAck(pin=pin)
+
+ def callback_PassphraseRequest(self, req):
+ if req and hasattr(req, 'on_device') and req.on_device is True:
+ return self.proto.PassphraseAck()
+
+ if self.creating_wallet:
+ msg = _("Enter a passphrase to generate this wallet. Each time "
+ "you use this wallet your {} will prompt you for the "
+ "passphrase. If you forget the passphrase you cannot "
+ "access the bitcores in the wallet.").format(self.device)
+ else:
+ msg = _("Enter the passphrase to unlock this wallet:")
+ passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
+ if passphrase is None:
+ return self.proto.Cancel()
+ passphrase = bip39_normalize_passphrase(passphrase)
+
+ ack = self.proto.PassphraseAck(passphrase=passphrase)
+ length = len(ack.passphrase)
+ if length > 50:
+ self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length))
+ return self.proto.Cancel()
+ return ack
+
+ def callback_PassphraseStateRequest(self, msg):
+ return self.proto.PassphraseStateAck()
+
+ def callback_WordRequest(self, msg):
+ self.step += 1
+ msg = _("Step {}/24. Enter seed word as explained on "
+ "your {}:").format(self.step, self.device)
+ word = self.handler.get_word(msg)
+ # Unfortunately the device can't handle self.proto.Cancel()
+ return self.proto.WordAck(word=word)
+
+
+class SafeTClientBase(GuiMixin, PrintError):
+
+ def __init__(self, handler, plugin, proto):
+ assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
+ self.proto = proto
+ self.device = plugin.device
+ self.handler = handler
+ self.tx_api = plugin
+ self.types = plugin.types
+ self.msg = None
+ self.creating_wallet = False
+ self.used()
+
+ def __str__(self):
+ return "%s/%s" % (self.label(), self.features.device_id)
+
+ def label(self):
+ '''The name given by the user to the device.'''
+ return self.features.label
+
+ def is_initialized(self):
+ '''True if initialized, False if wiped.'''
+ return self.features.initialized
+
+ def is_pairable(self):
+ return not self.features.bootloader_mode
+
+ def has_usable_connection_with_device(self):
+ try:
+ res = self.ping("electrum pinging device")
+ assert res == "electrum pinging device"
+ except BaseException:
+ return False
+ return True
+
+ def used(self):
+ self.last_operation = time.time()
+
+ def prevent_timeouts(self):
+ self.last_operation = float('inf')
+
+ def timeout(self, cutoff):
+ '''Time out the client if the last operation was before cutoff.'''
+ if self.last_operation < cutoff:
+ self.print_error("timed out")
+ self.clear_session()
+
+ @staticmethod
+ def expand_path(n):
+ return convert_bip32_path_to_list_of_uint32(n)
+
+ def cancel(self):
+ '''Provided here as in keepkeylib but not safetlib.'''
+ self.transport.write(self.proto.Cancel())
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def get_xpub(self, bip32_path, xtype):
+ address_n = self.expand_path(bip32_path)
+ creating = False
+ node = self.get_public_node(address_n, creating).node
+ return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
+
+ def toggle_passphrase(self):
+ if self.features.passphrase_protection:
+ self.msg = _("Confirm on your {} device to disable passphrases")
+ else:
+ self.msg = _("Confirm on your {} device to enable passphrases")
+ enabled = not self.features.passphrase_protection
+ self.apply_settings(use_passphrase=enabled)
+
+ def change_label(self, label):
+ self.msg = _("Confirm the new label on your {} device")
+ self.apply_settings(label=label)
+
+ def change_homescreen(self, homescreen):
+ self.msg = _("Confirm on your {} device to change your home screen")
+ self.apply_settings(homescreen=homescreen)
+
+ def set_pin(self, remove):
+ if remove:
+ self.msg = _("Confirm on your {} device to disable PIN protection")
+ elif self.features.pin_protection:
+ self.msg = _("Confirm on your {} device to change your PIN")
+ else:
+ self.msg = _("Confirm on your {} device to set a PIN")
+ self.change_pin(remove)
+
+ def clear_session(self):
+ '''Clear the session to force pin (and passphrase if enabled)
+ re-entry. Does not leak exceptions.'''
+ self.print_error("clear session:", self)
+ self.prevent_timeouts()
+ try:
+ super(SafeTClientBase, self).clear_session()
+ except BaseException as e:
+ # If the device was removed it has the same effect...
+ self.print_error("clear_session: ignoring error", str(e))
+
+ def get_public_node(self, address_n, creating):
+ self.creating_wallet = creating
+ return super(SafeTClientBase, self).get_public_node(address_n)
+
+ def close(self):
+ '''Called when Our wallet was closed or the device removed.'''
+ self.print_error("closing client")
+ self.clear_session()
+ # Release the device
+ self.transport.close()
+
+ def firmware_version(self):
+ f = self.features
+ return (f.major_version, f.minor_version, f.patch_version)
+
+ def atleast_version(self, major, minor=0, patch=0):
+ return self.firmware_version() >= (major, minor, patch)
+
+ @staticmethod
+ def wrapper(func):
+ '''Wrap methods to clear any message box they opened.'''
+
+ def wrapped(self, *args, **kwargs):
+ try:
+ self.prevent_timeouts()
+ return func(self, *args, **kwargs)
+ finally:
+ self.used()
+ self.handler.finished()
+ self.creating_wallet = False
+ self.msg = None
+
+ return wrapped
+
+ @staticmethod
+ def wrap_methods(cls):
+ for method in ['apply_settings', 'change_pin',
+ 'get_address', 'get_public_node',
+ 'load_device_by_mnemonic', 'load_device_by_xprv',
+ 'recovery_device', 'reset_device', 'sign_message',
+ 'sign_tx', 'wipe_device']:
+ setattr(cls, method, cls.wrapper(getattr(cls, method)))
diff --git a/electrum/plugins/safe_t/cmdline.py b/electrum/plugins/safe_t/cmdline.py
new file mode 100644
index 000000000..9c6346d3b
--- /dev/null
+++ b/electrum/plugins/safe_t/cmdline.py
@@ -0,0 +1,14 @@
+from electrum.plugin import hook
+from .safe_t import SafeTPlugin
+from ..hw_wallet import CmdLineHandler
+
+class Plugin(SafeTPlugin):
+ handler = CmdLineHandler()
+ @hook
+ def init_keystore(self, keystore):
+ if not isinstance(keystore, self.keystore_class):
+ return
+ keystore.handler = self.handler
+
+ def create_handler(self, window):
+ return self.handler
diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py
new file mode 100644
index 000000000..ee031ed37
--- /dev/null
+++ b/electrum/plugins/safe_t/qt.py
@@ -0,0 +1,492 @@
+from functools import partial
+import threading
+
+from PyQt5.Qt import Qt
+from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton
+from PyQt5.Qt import QVBoxLayout, QLabel
+
+from electrum.gui.qt.util import *
+from electrum.i18n import _
+from electrum.plugin import hook, DeviceMgr
+from electrum.util import PrintError, UserCancelled, bh2u
+from electrum.wallet import Wallet, Standard_Wallet
+
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
+
+
+PASSPHRASE_HELP_SHORT =_(
+ "Passphrases allow you to access new wallets, each "
+ "hidden behind a particular case-sensitive passphrase.")
+PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + " " + _(
+ "You need to create a separate Electrum wallet for each passphrase "
+ "you use as they each generate different addresses. Changing "
+ "your passphrase does not lose other wallets, each is still "
+ "accessible behind its own passphrase.")
+RECOMMEND_PIN = _(
+ "You should enable PIN protection. Your PIN is the only protection "
+ "for your bitcores if your device is lost or stolen.")
+PASSPHRASE_NOT_PIN = _(
+ "If you forget a passphrase you will be unable to access any "
+ "bitcores in the wallet behind it. A passphrase is not a PIN. "
+ "Only change this if you are sure you understand it.")
+
+
+class QtHandler(QtHandlerBase):
+
+ pin_signal = pyqtSignal(object)
+
+ def __init__(self, win, pin_matrix_widget_class, device):
+ super(QtHandler, self).__init__(win, device)
+ self.pin_signal.connect(self.pin_dialog)
+ self.pin_matrix_widget_class = pin_matrix_widget_class
+
+ def get_pin(self, msg):
+ self.done.clear()
+ self.pin_signal.emit(msg)
+ self.done.wait()
+ return self.response
+
+ def pin_dialog(self, msg):
+ # Needed e.g. when resetting a device
+ self.clear_dialog()
+ dialog = WindowModalDialog(self.top_level_window(), _("Enter PIN"))
+ matrix = self.pin_matrix_widget_class()
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(matrix)
+ vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
+ dialog.setLayout(vbox)
+ dialog.exec_()
+ self.response = str(matrix.get_value())
+ self.done.set()
+
+
+class QtPlugin(QtPluginBase):
+ # Derived classes must provide the following class-static variables:
+ # icon_file
+ # pin_matrix_widget_class
+
+ def create_handler(self, window):
+ return QtHandler(window, self.pin_matrix_widget_class(), self.device)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if len(addrs) != 1:
+ return
+ for keystore in wallet.get_keystores():
+ if type(keystore) == self.keystore_class:
+ def show_address(keystore=keystore):
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
+ device_name = "{} ({})".format(self.device, keystore.label)
+ menu.addAction(_("Show on {}").format(device_name), show_address)
+
+ def show_settings_dialog(self, window, keystore):
+ device_id = self.choose_device(window, keystore)
+ if device_id:
+ SettingsDialog(window, self, keystore, device_id).exec_()
+
+ def request_safe_t_init_settings(self, wizard, method, device):
+ vbox = QVBoxLayout()
+ next_enabled = True
+ label = QLabel(_("Enter a label to name your device:"))
+ name = QLineEdit()
+ hl = QHBoxLayout()
+ hl.addWidget(label)
+ hl.addWidget(name)
+ hl.addStretch(1)
+ vbox.addLayout(hl)
+
+ def clean_text(widget):
+ text = widget.toPlainText().strip()
+ return ' '.join(text.split())
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ gb = QGroupBox()
+ hbox1 = QHBoxLayout()
+ gb.setLayout(hbox1)
+ vbox.addWidget(gb)
+ gb.setTitle(_("Select your seed length:"))
+ bg = QButtonGroup()
+ for i, count in enumerate([12, 18, 24]):
+ rb = QRadioButton(gb)
+ rb.setText(_("%d words") % count)
+ bg.addButton(rb)
+ bg.setId(rb, i)
+ hbox1.addWidget(rb)
+ rb.setChecked(True)
+ cb_pin = QCheckBox(_('Enable PIN protection'))
+ cb_pin.setChecked(True)
+ else:
+ text = QTextEdit()
+ text.setMaximumHeight(60)
+ if method == TIM_MNEMONIC:
+ msg = _("Enter your BIP39 mnemonic:")
+ else:
+ msg = _("Enter the master private key beginning with xprv:")
+ def set_enabled():
+ from electrum.keystore import is_xprv
+ wizard.next_button.setEnabled(is_xprv(clean_text(text)))
+ text.textChanged.connect(set_enabled)
+ next_enabled = False
+
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(text)
+ pin = QLineEdit()
+ pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
+ pin.setMaximumWidth(100)
+ hbox_pin = QHBoxLayout()
+ hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
+ hbox_pin.addWidget(pin)
+ hbox_pin.addStretch(1)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ vbox.addWidget(WWLabel(RECOMMEND_PIN))
+ vbox.addWidget(cb_pin)
+ else:
+ vbox.addLayout(hbox_pin)
+
+ passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ cb_phrase = QCheckBox(_('Enable passphrases'))
+ cb_phrase.setChecked(False)
+ vbox.addWidget(passphrase_msg)
+ vbox.addWidget(passphrase_warning)
+ vbox.addWidget(cb_phrase)
+
+ wizard.exec_layout(vbox, next_enabled=next_enabled)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ item = bg.checkedId()
+ pin = cb_pin.isChecked()
+ else:
+ item = ' '.join(str(clean_text(text)).split())
+ pin = str(pin.text())
+
+ return (item, name.text(), pin, cb_phrase.isChecked())
+
+
+class Plugin(SafeTPlugin, QtPlugin):
+ icon_unpaired = ":icons/safe-t_unpaired.png"
+ icon_paired = ":icons/safe-t.png"
+
+ @classmethod
+ def pin_matrix_widget_class(self):
+ from safetlib.qt.pinmatrix import PinMatrixWidget
+ return PinMatrixWidget
+
+
+class SettingsDialog(WindowModalDialog):
+ '''This dialog doesn't require a device be paired with a wallet.
+ We want users to be able to wipe a device even if they've forgotten
+ their PIN.'''
+
+ def __init__(self, window, plugin, keystore, device_id):
+ title = _("{} Settings").format(plugin.device)
+ super(SettingsDialog, self).__init__(window, title)
+ self.setMaximumWidth(540)
+
+ devmgr = plugin.device_manager()
+ config = devmgr.config
+ handler = keystore.handler
+ thread = keystore.thread
+ hs_rows, hs_cols = (64, 128)
+
+ def invoke_client(method, *args, **kw_args):
+ unpair_after = kw_args.pop('unpair_after', False)
+
+ def task():
+ client = devmgr.client_by_id(device_id)
+ if not client:
+ raise RuntimeError("Device not connected")
+ if method:
+ getattr(client, method)(*args, **kw_args)
+ if unpair_after:
+ devmgr.unpair_id(device_id)
+ return client.features
+
+ thread.add(task, on_success=update)
+
+ def update(features):
+ self.features = features
+ set_label_enabled()
+ if features.bootloader_hash:
+ bl_hash = bh2u(features.bootloader_hash)
+ bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
+ else:
+ bl_hash = "N/A"
+ noyes = [_("No"), _("Yes")]
+ endis = [_("Enable Passphrases"), _("Disable Passphrases")]
+ disen = [_("Disabled"), _("Enabled")]
+ setchange = [_("Set a PIN"), _("Change PIN")]
+
+ version = "%d.%d.%d" % (features.major_version,
+ features.minor_version,
+ features.patch_version)
+
+ device_label.setText(features.label)
+ pin_set_label.setText(noyes[features.pin_protection])
+ passphrases_label.setText(disen[features.passphrase_protection])
+ bl_hash_label.setText(bl_hash)
+ label_edit.setText(features.label)
+ device_id_label.setText(features.device_id)
+ initialized_label.setText(noyes[features.initialized])
+ version_label.setText(version)
+ clear_pin_button.setVisible(features.pin_protection)
+ clear_pin_warning.setVisible(features.pin_protection)
+ pin_button.setText(setchange[features.pin_protection])
+ pin_msg.setVisible(not features.pin_protection)
+ passphrase_button.setText(endis[features.passphrase_protection])
+ language_label.setText(features.language)
+
+ def set_label_enabled():
+ label_apply.setEnabled(label_edit.text() != self.features.label)
+
+ def rename():
+ invoke_client('change_label', label_edit.text())
+
+ def toggle_passphrase():
+ title = _("Confirm Toggle Passphrase Protection")
+ currently_enabled = self.features.passphrase_protection
+ if currently_enabled:
+ msg = _("After disabling passphrases, you can only pair this "
+ "Electrum wallet if it had an empty passphrase. "
+ "If its passphrase was not empty, you will need to "
+ "create a new wallet with the install wizard. You "
+ "can use this wallet again at any time by re-enabling "
+ "passphrases and entering its passphrase.")
+ else:
+ msg = _("Your current Electrum wallet can only be used with "
+ "an empty passphrase. You must create a separate "
+ "wallet with the install wizard for other passphrases "
+ "as each one generates a new set of addresses.")
+ msg += "\n\n" + _("Are you sure you want to proceed?")
+ if not self.question(msg, title=title):
+ return
+ invoke_client('toggle_passphrase', unpair_after=currently_enabled)
+
+ def change_homescreen():
+ dialog = QFileDialog(self, _("Choose Homescreen"))
+ filename, __ = dialog.getOpenFileName()
+ if not filename:
+ return # user cancelled
+
+ if filename.endswith('.toif'):
+ img = open(filename, 'rb').read()
+ if img[:8] != b'TOIf\x90\x00\x90\x00':
+ handler.show_error('File is not a TOIF file with size of 144x144')
+ return
+ else:
+ from PIL import Image # FIXME
+ im = Image.open(filename)
+ if im.size != (128, 64):
+ handler.show_error('Image must be 128 x 64 pixels')
+ return
+ im = im.convert('1')
+ pix = im.load()
+ img = bytearray(1024)
+ for j in range(64):
+ for i in range(128):
+ if pix[i, j]:
+ o = (i + j * 128)
+ img[o // 8] |= (1 << (7 - o % 8))
+ img = bytes(img)
+ invoke_client('change_homescreen', img)
+
+ def clear_homescreen():
+ invoke_client('change_homescreen', b'\x00')
+
+ def set_pin():
+ invoke_client('set_pin', remove=False)
+
+ def clear_pin():
+ invoke_client('set_pin', remove=True)
+
+ def wipe_device():
+ wallet = window.wallet
+ if wallet and sum(wallet.get_balance()):
+ title = _("Confirm Device Wipe")
+ msg = _("Are you SURE you want to wipe the device?\n"
+ "Your wallet still has bitcores in it!")
+ if not self.question(msg, title=title,
+ icon=QMessageBox.Critical):
+ return
+ invoke_client('wipe_device', unpair_after=True)
+
+ def slider_moved():
+ mins = timeout_slider.sliderPosition()
+ timeout_minutes.setText(_("%2d minutes") % mins)
+
+ def slider_released():
+ config.set_session_timeout(timeout_slider.sliderPosition() * 60)
+
+ # Information tab
+ info_tab = QWidget()
+ info_layout = QVBoxLayout(info_tab)
+ info_glayout = QGridLayout()
+ info_glayout.setColumnStretch(2, 1)
+ device_label = QLabel()
+ pin_set_label = QLabel()
+ passphrases_label = QLabel()
+ version_label = QLabel()
+ device_id_label = QLabel()
+ bl_hash_label = QLabel()
+ bl_hash_label.setWordWrap(True)
+ language_label = QLabel()
+ initialized_label = QLabel()
+ rows = [
+ (_("Device Label"), device_label),
+ (_("PIN set"), pin_set_label),
+ (_("Passphrases"), passphrases_label),
+ (_("Firmware Version"), version_label),
+ (_("Device ID"), device_id_label),
+ (_("Bootloader Hash"), bl_hash_label),
+ (_("Language"), language_label),
+ (_("Initialized"), initialized_label),
+ ]
+ for row_num, (label, widget) in enumerate(rows):
+ info_glayout.addWidget(QLabel(label), row_num, 0)
+ info_glayout.addWidget(widget, row_num, 1)
+ info_layout.addLayout(info_glayout)
+
+ # Settings tab
+ settings_tab = QWidget()
+ settings_layout = QVBoxLayout(settings_tab)
+ settings_glayout = QGridLayout()
+
+ # Settings tab - Label
+ label_msg = QLabel(_("Name this {}. If you have multiple devices "
+ "their labels help distinguish them.")
+ .format(plugin.device))
+ label_msg.setWordWrap(True)
+ label_label = QLabel(_("Device Label"))
+ label_edit = QLineEdit()
+ label_edit.setMinimumWidth(150)
+ label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
+ label_apply = QPushButton(_("Apply"))
+ label_apply.clicked.connect(rename)
+ label_edit.textChanged.connect(set_label_enabled)
+ settings_glayout.addWidget(label_label, 0, 0)
+ settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
+ settings_glayout.addWidget(label_apply, 0, 3)
+ settings_glayout.addWidget(label_msg, 1, 1, 1, -1)
+
+ # Settings tab - PIN
+ pin_label = QLabel(_("PIN Protection"))
+ pin_button = QPushButton()
+ pin_button.clicked.connect(set_pin)
+ settings_glayout.addWidget(pin_label, 2, 0)
+ settings_glayout.addWidget(pin_button, 2, 1)
+ pin_msg = QLabel(_("PIN protection is strongly recommended. "
+ "A PIN is your only protection against someone "
+ "stealing your bitcores if they obtain physical "
+ "access to your {}.").format(plugin.device))
+ pin_msg.setWordWrap(True)
+ pin_msg.setStyleSheet("color: red")
+ settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)
+
+ # Settings tab - Homescreen
+ homescreen_label = QLabel(_("Homescreen"))
+ homescreen_change_button = QPushButton(_("Change..."))
+ homescreen_clear_button = QPushButton(_("Reset"))
+ homescreen_change_button.clicked.connect(change_homescreen)
+ try:
+ import PIL
+ except ImportError:
+ homescreen_change_button.setDisabled(True)
+ homescreen_change_button.setToolTip(
+ _("Required package 'PIL' is not available - Please install it.")
+ )
+ homescreen_clear_button.clicked.connect(clear_homescreen)
+ homescreen_msg = QLabel(_("You can set the homescreen on your "
+ "device to personalize it. You must "
+ "choose a {} x {} monochrome black and "
+ "white image.").format(hs_rows, hs_cols))
+ homescreen_msg.setWordWrap(True)
+ settings_glayout.addWidget(homescreen_label, 4, 0)
+ settings_glayout.addWidget(homescreen_change_button, 4, 1)
+ settings_glayout.addWidget(homescreen_clear_button, 4, 2)
+ settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)
+
+ # Settings tab - Session Timeout
+ timeout_label = QLabel(_("Session Timeout"))
+ timeout_minutes = QLabel()
+ timeout_slider = QSlider(Qt.Horizontal)
+ timeout_slider.setRange(1, 60)
+ timeout_slider.setSingleStep(1)
+ timeout_slider.setTickInterval(5)
+ timeout_slider.setTickPosition(QSlider.TicksBelow)
+ timeout_slider.setTracking(True)
+ timeout_msg = QLabel(
+ _("Clear the session after the specified period "
+ "of inactivity. Once a session has timed out, "
+ "your PIN and passphrase (if enabled) must be "
+ "re-entered to use the device."))
+ timeout_msg.setWordWrap(True)
+ timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
+ slider_moved()
+ timeout_slider.valueChanged.connect(slider_moved)
+ timeout_slider.sliderReleased.connect(slider_released)
+ settings_glayout.addWidget(timeout_label, 6, 0)
+ settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
+ settings_glayout.addWidget(timeout_minutes, 6, 4)
+ settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
+ settings_layout.addLayout(settings_glayout)
+ settings_layout.addStretch(1)
+
+ # Advanced tab
+ advanced_tab = QWidget()
+ advanced_layout = QVBoxLayout(advanced_tab)
+ advanced_glayout = QGridLayout()
+
+ # Advanced tab - clear PIN
+ clear_pin_button = QPushButton(_("Disable PIN"))
+ clear_pin_button.clicked.connect(clear_pin)
+ clear_pin_warning = QLabel(
+ _("If you disable your PIN, anyone with physical access to your "
+ "{} device can spend your bitcores.").format(plugin.device))
+ clear_pin_warning.setWordWrap(True)
+ clear_pin_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(clear_pin_button, 0, 2)
+ advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)
+
+ # Advanced tab - toggle passphrase protection
+ passphrase_button = QPushButton()
+ passphrase_button.clicked.connect(toggle_passphrase)
+ passphrase_msg = WWLabel(PASSPHRASE_HELP)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(passphrase_button, 3, 2)
+ advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
+ advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)
+
+ # Advanced tab - wipe device
+ wipe_device_button = QPushButton(_("Wipe Device"))
+ wipe_device_button.clicked.connect(wipe_device)
+ wipe_device_msg = QLabel(
+ _("Wipe the device, removing all data from it. The firmware "
+ "is left unchanged."))
+ wipe_device_msg.setWordWrap(True)
+ wipe_device_warning = QLabel(
+ _("Only wipe a device if you have the recovery seed written down "
+ "and the device wallet(s) are empty, otherwise the bitcores "
+ "will be lost forever."))
+ wipe_device_warning.setWordWrap(True)
+ wipe_device_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(wipe_device_button, 6, 2)
+ advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
+ advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
+ advanced_layout.addLayout(advanced_glayout)
+ advanced_layout.addStretch(1)
+
+ tabs = QTabWidget(self)
+ tabs.addTab(info_tab, _("Information"))
+ tabs.addTab(settings_tab, _("Settings"))
+ tabs.addTab(advanced_tab, _("Advanced"))
+ dialog_vbox = QVBoxLayout(self)
+ dialog_vbox.addWidget(tabs)
+ dialog_vbox.addLayout(Buttons(CloseButton(self)))
+
+ # Update information
+ invoke_client(None)
diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
new file mode 100644
index 000000000..06891f60f
--- /dev/null
+++ b/electrum/plugins/safe_t/safe_t.py
@@ -0,0 +1,484 @@
+from binascii import hexlify, unhexlify
+import traceback
+import sys
+
+from electrum.util import bfh, bh2u, versiontuple, UserCancelled
+from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey, deserialize_xpub,
+ TYPE_ADDRESS, TYPE_SCRIPT, is_address)
+from electrum import constants
+from electrum.i18n import _
+from electrum.plugin import BasePlugin, Device
+from electrum.transaction import deserialize, Transaction
+from electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey
+from electrum.base_wizard import ScriptTypeNotSupported
+
+from ..hw_wallet import HW_PluginBase
+from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
+
+
+# Safe-T mini initialization methods
+TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
+
+
+class SafeTKeyStore(Hardware_KeyStore):
+ hw_type = 'safe_t'
+ device = 'Safe-T mini'
+
+ def get_derivation(self):
+ return self.derivation
+
+ def get_client(self, force_pair=True):
+ return self.plugin.get_client(self, force_pair)
+
+ def decrypt_message(self, sequence, message, password):
+ raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
+
+ def sign_message(self, sequence, message, password):
+ client = self.get_client()
+ address_path = self.get_derivation() + "/%d/%d"%sequence
+ address_n = client.expand_path(address_path)
+ msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)
+ return msg_sig.signature
+
+ def sign_transaction(self, tx, password):
+ if tx.is_complete():
+ return
+ # previous transactions used as inputs
+ prev_tx = {}
+ # path of the xpubs that are involved
+ xpub_path = {}
+ for txin in tx.inputs():
+ pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
+ tx_hash = txin['prevout_hash']
+ if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
+ raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
+ prev_tx[tx_hash] = txin['prev_tx']
+ for x_pubkey in x_pubkeys:
+ if not is_xpubkey(x_pubkey):
+ continue
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub == self.get_master_public_key():
+ xpub_path[xpub] = self.get_derivation()
+
+ self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
+
+
+class SafeTPlugin(HW_PluginBase):
+ # Derived classes provide:
+ #
+ # class-static variables: client_class, firmware_URL, handler_class,
+ # libraries_available, libraries_URL, minimum_firmware,
+ # wallet_class, types
+
+ firmware_URL = 'https://safe-t.io'
+ libraries_URL = 'https://github.com/archos-safe-t/python-safet'
+ minimum_firmware = (1, 0, 5)
+ keystore_class = SafeTKeyStore
+ minimum_library = (0, 1, 0)
+ SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
+
+ MAX_LABEL_LEN = 32
+
+ def __init__(self, parent, config, name):
+ HW_PluginBase.__init__(self, parent, config, name)
+
+ self.libraries_available = self.check_libraries_available()
+ if not self.libraries_available:
+ return
+
+ from . import client
+ from . import transport
+ import safetlib.messages
+ self.client_class = client.SafeTClient
+ self.types = safetlib.messages
+ self.DEVICE_IDS = ('Safe-T mini',)
+
+ self.transport_handler = transport.SafeTTransport()
+ self.device_manager().register_enumerate_func(self.enumerate)
+
+ def get_library_version(self):
+ import safetlib
+ try:
+ return safetlib.__version__
+ except AttributeError:
+ return 'unknown'
+
+ def enumerate(self):
+ devices = self.transport_handler.enumerate_devices()
+ return [Device(d.get_path(), -1, d.get_path(), 'Safe-T mini', 0) for d in devices]
+
+ def create_client(self, device, handler):
+ try:
+ self.print_error("connecting to device at", device.path)
+ transport = self.transport_handler.get_transport(device.path)
+ except BaseException as e:
+ self.print_error("cannot connect at", device.path, str(e))
+ return None
+
+ if not transport:
+ self.print_error("cannot connect at", device.path)
+ return
+
+ self.print_error("connected to device at", device.path)
+ client = self.client_class(transport, handler, self)
+
+ # Try a ping for device sanity
+ try:
+ client.ping('t')
+ except BaseException as e:
+ self.print_error("ping failed", str(e))
+ return None
+
+ if not client.atleast_version(*self.minimum_firmware):
+ msg = (_('Outdated {} firmware for device labelled {}. Please '
+ 'download the updated firmware from {}')
+ .format(self.device, client.label(), self.firmware_URL))
+ self.print_error(msg)
+ if handler:
+ handler.show_error(msg)
+ else:
+ raise Exception(msg)
+ return None
+
+ return client
+
+ def get_client(self, keystore, force_pair=True):
+ devmgr = self.device_manager()
+ handler = keystore.handler
+ with devmgr.hid_lock:
+ client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
+ # returns the client for a given keystore. can use xpub
+ if client:
+ client.used()
+ return client
+
+ def get_coin_name(self):
+ return "Testnet" if constants.net.TESTNET else "Bitcore"
+
+ def initialize_device(self, device_id, wizard, handler):
+ # Initialization method
+ msg = _("Choose how you want to initialize your {}.\n\n"
+ "The first two methods are secure as no secret information "
+ "is entered into your computer.\n\n"
+ "For the last two methods you input secrets on your keyboard "
+ "and upload them to your {}, and so you should "
+ "only do those on a computer you know to be trustworthy "
+ "and free of malware."
+ ).format(self.device, self.device)
+ choices = [
+ # Must be short as QT doesn't word-wrap radio button text
+ (TIM_NEW, _("Let the device generate a completely new seed randomly")),
+ (TIM_RECOVER, _("Recover from a seed you have previously written down")),
+ (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
+ (TIM_PRIVKEY, _("Upload a master private key"))
+ ]
+ def f(method):
+ import threading
+ settings = self.request_safe_t_init_settings(wizard, method, self.device)
+ t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
+ t.setDaemon(True)
+ t.start()
+ exit_code = wizard.loop.exec_()
+ if exit_code != 0:
+ # this method (initialize_device) was called with the expectation
+ # of leaving the device in an initialized state when finishing.
+ # signal that this is not the case:
+ raise UserCancelled()
+ wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)
+
+ def _initialize_device_safe(self, settings, method, device_id, wizard, handler):
+ exit_code = 0
+ try:
+ self._initialize_device(settings, method, device_id, wizard, handler)
+ except UserCancelled:
+ exit_code = 1
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ handler.show_error(str(e))
+ exit_code = 1
+ finally:
+ wizard.loop.exit(exit_code)
+
+ def _initialize_device(self, settings, method, device_id, wizard, handler):
+ item, label, pin_protection, passphrase_protection = settings
+
+ if method == TIM_RECOVER:
+ handler.show_error(_(
+ "You will be asked to enter 24 words regardless of your "
+ "seed's actual length. If you enter a word incorrectly or "
+ "misspell it, you cannot change it or go back - you will need "
+ "to start again from the beginning.\n\nSo please enter "
+ "the words carefully!"),
+ blocking=True)
+
+ language = 'english'
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+
+ if method == TIM_NEW:
+ strength = 64 * (item + 2) # 128, 192 or 256
+ u2f_counter = 0
+ skip_backup = False
+ client.reset_device(True, strength, passphrase_protection,
+ pin_protection, label, language,
+ u2f_counter, skip_backup)
+ elif method == TIM_RECOVER:
+ word_count = 6 * (item + 2) # 12, 18 or 24
+ client.step = 0
+ client.recovery_device(word_count, passphrase_protection,
+ pin_protection, label, language)
+ elif method == TIM_MNEMONIC:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_mnemonic(str(item), pin,
+ passphrase_protection,
+ label, language)
+ else:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_xprv(item, pin, passphrase_protection,
+ label, language)
+
+ def _make_node_path(self, xpub, address_n):
+ _, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
+ node = self.types.HDNodeType(
+ depth=depth,
+ fingerprint=int.from_bytes(fingerprint, 'big'),
+ child_num=int.from_bytes(child_num, 'big'),
+ chain_code=chain_code,
+ public_key=key,
+ )
+ return self.types.HDNodePathType(node=node, address_n=address_n)
+
+ def setup_device(self, device_info, wizard, purpose):
+ devmgr = self.device_manager()
+ device_id = device_info.device.id_
+ client = devmgr.client_by_id(device_id)
+ if client is None:
+ raise Exception(_('Failed to create a client for this device.') + '\n' +
+ _('Make sure it is in the correct state.'))
+ # fixme: we should use: client.handler = wizard
+ client.handler = self.create_handler(wizard)
+ if not device_info.initialized:
+ self.initialize_device(device_id, wizard, client.handler)
+ client.get_xpub('m', 'standard')
+ client.used()
+
+ def get_xpub(self, device_id, derivation, xtype, wizard):
+ if xtype not in self.SUPPORTED_XTYPES:
+ raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device))
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ client.handler = wizard
+ xpub = client.get_xpub(derivation, xtype)
+ client.used()
+ return xpub
+
+ def get_safet_input_script_type(self, electrum_txin_type: str):
+ if electrum_txin_type in ('p2wpkh', 'p2wsh'):
+ return self.types.InputScriptType.SPENDWITNESS
+ if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
+ return self.types.InputScriptType.SPENDP2SHWITNESS
+ if electrum_txin_type in ('p2pkh', ):
+ return self.types.InputScriptType.SPENDADDRESS
+ if electrum_txin_type in ('p2sh', ):
+ return self.types.InputScriptType.SPENDMULTISIG
+ raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
+
+ def get_safet_output_script_type(self, electrum_txin_type: str):
+ if electrum_txin_type in ('p2wpkh', 'p2wsh'):
+ return self.types.OutputScriptType.PAYTOWITNESS
+ if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
+ return self.types.OutputScriptType.PAYTOP2SHWITNESS
+ if electrum_txin_type in ('p2pkh', ):
+ return self.types.OutputScriptType.PAYTOADDRESS
+ if electrum_txin_type in ('p2sh', ):
+ return self.types.OutputScriptType.PAYTOMULTISIG
+ raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
+
+ def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
+ self.prev_tx = prev_tx
+ self.xpub_path = xpub_path
+ client = self.get_client(keystore)
+ inputs = self.tx_inputs(tx, True)
+ outputs = self.tx_outputs(keystore.get_derivation(), tx)
+ signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
+ signatures = [(bh2u(x) + '01') for x in signatures]
+ tx.update_signatures(signatures)
+
+ def show_address(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not self.show_address_helper(wallet, address, keystore):
+ return
+ client = self.get_client(keystore)
+ if not client.atleast_version(1, 0):
+ keystore.handler.show_error(_("Your device firmware is too old"))
+ return
+ change, index = wallet.get_address_index(address)
+ derivation = keystore.derivation
+ address_path = "%s/%d/%d"%(derivation, change, index)
+ address_n = client.expand_path(address_path)
+ xpubs = wallet.get_master_public_keys()
+ if len(xpubs) == 1:
+ script_type = self.get_safet_input_script_type(wallet.txin_type)
+ client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)
+ else:
+ def f(xpub):
+ return self._make_node_path(xpub, [change, index])
+ pubkeys = wallet.get_public_keys(address)
+ # sort xpubs using the order of pubkeys
+ sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))
+ pubkeys = list(map(f, sorted_xpubs))
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=[b''] * wallet.n,
+ m=wallet.m,
+ )
+ script_type = self.get_safet_input_script_type(wallet.txin_type)
+ client.get_address(self.get_coin_name(), address_n, True, multisig=multisig, script_type=script_type)
+
+ def tx_inputs(self, tx, for_sig=False):
+ inputs = []
+ for txin in tx.inputs():
+ txinputtype = self.types.TxInputType()
+ if txin['type'] == 'coinbase':
+ prev_hash = b"\x00"*32
+ prev_index = 0xffffffff # signed int -1
+ else:
+ if for_sig:
+ x_pubkeys = txin['x_pubkeys']
+ if len(x_pubkeys) == 1:
+ x_pubkey = x_pubkeys[0]
+ xpub, s = parse_xpubkey(x_pubkey)
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype._extend_address_n(xpub_n + s)
+ txinputtype.script_type = self.get_safet_input_script_type(txin['type'])
+ else:
+ def f(x_pubkey):
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ else:
+ xpub = xpub_from_pubkey(0, bfh(x_pubkey))
+ s = []
+ return self._make_node_path(xpub, s)
+ pubkeys = list(map(f, x_pubkeys))
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=list(map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))),
+ m=txin.get('num_sig'),
+ )
+ script_type = self.get_safet_input_script_type(txin['type'])
+ txinputtype = self.types.TxInputType(
+ script_type=script_type,
+ multisig=multisig
+ )
+ # find which key is mine
+ for x_pubkey in x_pubkeys:
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub in self.xpub_path:
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype._extend_address_n(xpub_n + s)
+ break
+
+ prev_hash = unhexlify(txin['prevout_hash'])
+ prev_index = txin['prevout_n']
+
+ if 'value' in txin:
+ txinputtype.amount = txin['value']
+ txinputtype.prev_hash = prev_hash
+ txinputtype.prev_index = prev_index
+
+ if txin.get('scriptSig') is not None:
+ script_sig = bfh(txin['scriptSig'])
+ txinputtype.script_sig = script_sig
+
+ txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)
+
+ inputs.append(txinputtype)
+
+ return inputs
+
+ def tx_outputs(self, derivation, tx):
+
+ def create_output_by_derivation():
+ script_type = self.get_safet_output_script_type(info.script_type)
+ if len(xpubs) == 1:
+ address_n = self.client_class.expand_path(derivation + "/%d/%d" % index)
+ txoutputtype = self.types.TxOutputType(
+ amount=amount,
+ script_type=script_type,
+ address_n=address_n,
+ )
+ else:
+ address_n = self.client_class.expand_path("/%d/%d" % index)
+ pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs]
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=[b''] * len(pubkeys),
+ m=m)
+ txoutputtype = self.types.TxOutputType(
+ multisig=multisig,
+ amount=amount,
+ address_n=self.client_class.expand_path(derivation + "/%d/%d" % index),
+ script_type=script_type)
+ return txoutputtype
+
+ def create_output_by_address():
+ txoutputtype = self.types.TxOutputType()
+ txoutputtype.amount = amount
+ if _type == TYPE_SCRIPT:
+ txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
+ txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
+ elif _type == TYPE_ADDRESS:
+ txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
+ txoutputtype.address = address
+ return txoutputtype
+
+ outputs = []
+ has_change = False
+ any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
+
+ for o in tx.outputs():
+ _type, address, amount = o.type, o.address, o.value
+ use_create_by_derivation = False
+
+ info = tx.output_info.get(address)
+ if info is not None and not has_change:
+ index, xpubs, m = info.address_index, info.sorted_xpubs, info.num_sig
+ on_change_branch = index[0] == 1
+ # prioritise hiding outputs on the 'change' branch from user
+ # because no more than one change address allowed
+ # note: ^ restriction can be removed once we require fw
+ # that has https://github.com/trezor/trezor-mcu/pull/306
+ if on_change_branch == any_output_on_change_branch:
+ use_create_by_derivation = True
+ has_change = True
+
+ if use_create_by_derivation:
+ txoutputtype = create_output_by_derivation()
+ else:
+ txoutputtype = create_output_by_address()
+ outputs.append(txoutputtype)
+
+ return outputs
+
+ def electrum_tx_to_txtype(self, tx):
+ t = self.types.TransactionType()
+ if tx is None:
+ # probably for segwit input and we don't need this prev txn
+ return t
+ d = deserialize(tx.raw)
+ t.version = d['version']
+ t.lock_time = d['lockTime']
+ inputs = self.tx_inputs(tx)
+ t._extend_inputs(inputs)
+ for vout in d['outputs']:
+ o = t._add_bin_outputs()
+ o.amount = vout['value']
+ o.script_pubkey = bfh(vout['scriptPubKey'])
+ return t
+
+ # This function is called from the TREZOR libraries (via tx_api)
+ def get_tx(self, tx_hash):
+ tx = self.prev_tx[tx_hash]
+ return self.electrum_tx_to_txtype(tx)
diff --git a/electrum/plugins/safe_t/transport.py b/electrum/plugins/safe_t/transport.py
new file mode 100644
index 000000000..3753434dc
--- /dev/null
+++ b/electrum/plugins/safe_t/transport.py
@@ -0,0 +1,95 @@
+from electrum.util import PrintError
+
+
+class SafeTTransport(PrintError):
+
+ @staticmethod
+ def all_transports():
+ """Reimplemented safetlib.transport.all_transports so that we can
+ enable/disable specific transports.
+ """
+ try:
+ # only to detect safetlib version
+ from safetlib.transport import all_transports
+ except ImportError:
+ # old safetlib. compat for safetlib < 0.9.2
+ transports = []
+ #try:
+ # from safetlib.transport_bridge import BridgeTransport
+ # transports.append(BridgeTransport)
+ #except BaseException:
+ # pass
+ try:
+ from safetlib.transport_hid import HidTransport
+ transports.append(HidTransport)
+ except BaseException:
+ pass
+ #try:
+ # from safetlib.transport_udp import UdpTransport
+ # transports.append(UdpTransport)
+ #except BaseException:
+ # pass
+ try:
+ from safetlib.transport_webusb import WebUsbTransport
+ transports.append(WebUsbTransport)
+ except BaseException:
+ pass
+ else:
+ # new safetlib.
+ transports = []
+ #try:
+ # from safetlib.transport.bridge import BridgeTransport
+ # transports.append(BridgeTransport)
+ #except BaseException:
+ # pass
+ try:
+ from safetlib.transport.hid import HidTransport
+ transports.append(HidTransport)
+ except BaseException:
+ pass
+ #try:
+ # from safetlib.transport.udp import UdpTransport
+ # transports.append(UdpTransport)
+ #except BaseException:
+ # pass
+ try:
+ from safetlib.transport.webusb import WebUsbTransport
+ transports.append(WebUsbTransport)
+ except BaseException:
+ pass
+ return transports
+ return transports
+
+ def enumerate_devices(self):
+ """Just like safetlib.transport.enumerate_devices,
+ but with exception catching, so that transports can fail separately.
+ """
+ devices = []
+ for transport in self.all_transports():
+ try:
+ new_devices = transport.enumerate()
+ except BaseException as e:
+ self.print_error('enumerate failed for {}. error {}'
+ .format(transport.__name__, str(e)))
+ else:
+ devices.extend(new_devices)
+ return devices
+
+ def get_transport(self, path=None):
+ """Reimplemented safetlib.transport.get_transport,
+ (1) for old safetlib
+ (2) to be able to disable specific transports
+ (3) to call our own enumerate_devices that catches exceptions
+ """
+ if path is None:
+ try:
+ return self.enumerate_devices()[0]
+ except IndexError:
+ raise Exception("No Safe-T mini found") from None
+
+ def match_prefix(a, b):
+ return a.startswith(b) or b.startswith(a)
+ transports = [t for t in self.all_transports() if match_prefix(path, t.PATH_PREFIX)]
+ if transports:
+ return transports[0].find_by_path(path)
+ raise Exception("Unknown path prefix '%s'" % path)
diff --git a/electrum/plugins/trezor/__init__.py b/electrum/plugins/trezor/__init__.py
new file mode 100644
index 000000000..e3b08ed65
--- /dev/null
+++ b/electrum/plugins/trezor/__init__.py
@@ -0,0 +1,8 @@
+from electrum.i18n import _
+
+fullname = 'TREZOR Wallet'
+description = _('Provides support for TREZOR hardware wallet')
+requires = [('trezorlib','github.com/trezor/python-trezor')]
+registers_keystore = ('hardware', 'trezor', _("TREZOR wallet"))
+available_for = ['qt', 'cmdline']
+
diff --git a/electrum/plugins/trezor/client.py b/electrum/plugins/trezor/client.py
new file mode 100644
index 000000000..89b5c2927
--- /dev/null
+++ b/electrum/plugins/trezor/client.py
@@ -0,0 +1,11 @@
+from trezorlib.client import proto, BaseClient, ProtocolMixin
+from .clientbase import TrezorClientBase
+
+class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient):
+ def __init__(self, transport, handler, plugin):
+ BaseClient.__init__(self, transport=transport)
+ ProtocolMixin.__init__(self, transport=transport)
+ TrezorClientBase.__init__(self, handler, plugin, proto)
+
+
+TrezorClientBase.wrap_methods(TrezorClient)
diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py
new file mode 100644
index 000000000..553c74399
--- /dev/null
+++ b/electrum/plugins/trezor/clientbase.py
@@ -0,0 +1,251 @@
+import time
+from struct import pack
+
+from electrum.i18n import _
+from electrum.util import PrintError, UserCancelled
+from electrum.keystore import bip39_normalize_passphrase
+from electrum.bitcoin import serialize_xpub, convert_bip32_path_to_list_of_uint32
+
+
+class GuiMixin(object):
+ # Requires: self.proto, self.device
+
+ # ref: https://github.com/trezor/trezor-common/blob/44dfb07cfaafffada4b2ce0d15ba1d90d17cf35e/protob/types.proto#L89
+ messages = {
+ 3: _("Confirm the transaction output on your {} device"),
+ 4: _("Confirm internal entropy on your {} device to begin"),
+ 5: _("Write down the seed word shown on your {}"),
+ 6: _("Confirm on your {} that you want to wipe it clean"),
+ 7: _("Confirm on your {} device the message to sign"),
+ 8: _("Confirm the total amount spent and the transaction fee on your "
+ "{} device"),
+ 10: _("Confirm wallet address on your {} device"),
+ 14: _("Choose on your {} device where to enter your passphrase"),
+ 'default': _("Check your {} device to continue"),
+ }
+
+ def callback_Failure(self, msg):
+ # BaseClient's unfortunate call() implementation forces us to
+ # raise exceptions on failure in order to unwind the stack.
+ # However, making the user acknowledge they cancelled
+ # gets old very quickly, so we suppress those. The NotInitialized
+ # one is misnamed and indicates a passphrase request was cancelled.
+ if msg.code in (self.types.FailureType.PinCancelled,
+ self.types.FailureType.ActionCancelled,
+ self.types.FailureType.NotInitialized):
+ raise UserCancelled()
+ raise RuntimeError(msg.message)
+
+ def callback_ButtonRequest(self, msg):
+ message = self.msg
+ if not message:
+ message = self.messages.get(msg.code, self.messages['default'])
+ self.handler.show_message(message.format(self.device), self.cancel)
+ return self.proto.ButtonAck()
+
+ def callback_PinMatrixRequest(self, msg):
+ if msg.type == 2:
+ msg = _("Enter a new PIN for your {}:")
+ elif msg.type == 3:
+ msg = (_("Re-enter the new PIN for your {}.\n\n"
+ "NOTE: the positions of the numbers have changed!"))
+ else:
+ msg = _("Enter your current {} PIN:")
+ pin = self.handler.get_pin(msg.format(self.device))
+ if len(pin) > 9:
+ self.handler.show_error(_('The PIN cannot be longer than 9 characters.'))
+ pin = '' # to cancel below
+ if not pin:
+ return self.proto.Cancel()
+ return self.proto.PinMatrixAck(pin=pin)
+
+ def callback_PassphraseRequest(self, req):
+ if req and hasattr(req, 'on_device') and req.on_device is True:
+ return self.proto.PassphraseAck()
+
+ if self.creating_wallet:
+ msg = _("Enter a passphrase to generate this wallet. Each time "
+ "you use this wallet your {} will prompt you for the "
+ "passphrase. If you forget the passphrase you cannot "
+ "access the bitcores in the wallet.").format(self.device)
+ else:
+ msg = _("Enter the passphrase to unlock this wallet:")
+ passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
+ if passphrase is None:
+ return self.proto.Cancel()
+ passphrase = bip39_normalize_passphrase(passphrase)
+
+ ack = self.proto.PassphraseAck(passphrase=passphrase)
+ length = len(ack.passphrase)
+ if length > 50:
+ self.handler.show_error(_("Too long passphrase ({} > 50 chars).").format(length))
+ return self.proto.Cancel()
+ return ack
+
+ def callback_PassphraseStateRequest(self, msg):
+ return self.proto.PassphraseStateAck()
+
+ def callback_WordRequest(self, msg):
+ if (msg.type is not None
+ and msg.type in (self.types.WordRequestType.Matrix9,
+ self.types.WordRequestType.Matrix6)):
+ num = 9 if msg.type == self.types.WordRequestType.Matrix9 else 6
+ char = self.handler.get_matrix(num)
+ if char == 'x':
+ return self.proto.Cancel()
+ return self.proto.WordAck(word=char)
+
+ self.step += 1
+ msg = _("Step {}/24. Enter seed word as explained on "
+ "your {}:").format(self.step, self.device)
+ word = self.handler.get_word(msg)
+ # Unfortunately the device can't handle self.proto.Cancel()
+ return self.proto.WordAck(word=word)
+
+
+class TrezorClientBase(GuiMixin, PrintError):
+
+ def __init__(self, handler, plugin, proto):
+ assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
+ self.proto = proto
+ self.device = plugin.device
+ self.handler = handler
+ self.tx_api = plugin
+ self.types = plugin.types
+ self.msg = None
+ self.creating_wallet = False
+ self.used()
+
+ def __str__(self):
+ return "%s/%s" % (self.label(), self.features.device_id)
+
+ def label(self):
+ '''The name given by the user to the device.'''
+ return self.features.label
+
+ def is_initialized(self):
+ '''True if initialized, False if wiped.'''
+ return self.features.initialized
+
+ def is_pairable(self):
+ return not self.features.bootloader_mode
+
+ def has_usable_connection_with_device(self):
+ try:
+ res = self.ping("electrum pinging device")
+ assert res == "electrum pinging device"
+ except BaseException:
+ return False
+ return True
+
+ def used(self):
+ self.last_operation = time.time()
+
+ def prevent_timeouts(self):
+ self.last_operation = float('inf')
+
+ def timeout(self, cutoff):
+ '''Time out the client if the last operation was before cutoff.'''
+ if self.last_operation < cutoff:
+ self.print_error("timed out")
+ self.clear_session()
+
+ @staticmethod
+ def expand_path(n):
+ return convert_bip32_path_to_list_of_uint32(n)
+
+ def cancel(self):
+ '''Provided here as in keepkeylib but not trezorlib.'''
+ self.transport.write(self.proto.Cancel())
+
+ def i4b(self, x):
+ return pack('>I', x)
+
+ def get_xpub(self, bip32_path, xtype):
+ address_n = self.expand_path(bip32_path)
+ creating = False
+ node = self.get_public_node(address_n, creating).node
+ return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
+
+ def toggle_passphrase(self):
+ if self.features.passphrase_protection:
+ self.msg = _("Confirm on your {} device to disable passphrases")
+ else:
+ self.msg = _("Confirm on your {} device to enable passphrases")
+ enabled = not self.features.passphrase_protection
+ self.apply_settings(use_passphrase=enabled)
+
+ def change_label(self, label):
+ self.msg = _("Confirm the new label on your {} device")
+ self.apply_settings(label=label)
+
+ def change_homescreen(self, homescreen):
+ self.msg = _("Confirm on your {} device to change your home screen")
+ self.apply_settings(homescreen=homescreen)
+
+ def set_pin(self, remove):
+ if remove:
+ self.msg = _("Confirm on your {} device to disable PIN protection")
+ elif self.features.pin_protection:
+ self.msg = _("Confirm on your {} device to change your PIN")
+ else:
+ self.msg = _("Confirm on your {} device to set a PIN")
+ self.change_pin(remove)
+
+ def clear_session(self):
+ '''Clear the session to force pin (and passphrase if enabled)
+ re-entry. Does not leak exceptions.'''
+ self.print_error("clear session:", self)
+ self.prevent_timeouts()
+ try:
+ super(TrezorClientBase, self).clear_session()
+ except BaseException as e:
+ # If the device was removed it has the same effect...
+ self.print_error("clear_session: ignoring error", str(e))
+
+ def get_public_node(self, address_n, creating):
+ self.creating_wallet = creating
+ return super(TrezorClientBase, self).get_public_node(address_n)
+
+ def close(self):
+ '''Called when Our wallet was closed or the device removed.'''
+ self.print_error("closing client")
+ self.clear_session()
+ # Release the device
+ self.transport.close()
+
+ def firmware_version(self):
+ f = self.features
+ return (f.major_version, f.minor_version, f.patch_version)
+
+ def atleast_version(self, major, minor=0, patch=0):
+ return self.firmware_version() >= (major, minor, patch)
+
+ def get_trezor_model(self):
+ """Returns '1' for Trezor One, 'T' for Trezor T."""
+ return self.features.model
+
+ @staticmethod
+ def wrapper(func):
+ '''Wrap methods to clear any message box they opened.'''
+
+ def wrapped(self, *args, **kwargs):
+ try:
+ self.prevent_timeouts()
+ return func(self, *args, **kwargs)
+ finally:
+ self.used()
+ self.handler.finished()
+ self.creating_wallet = False
+ self.msg = None
+
+ return wrapped
+
+ @staticmethod
+ def wrap_methods(cls):
+ for method in ['apply_settings', 'change_pin',
+ 'get_address', 'get_public_node',
+ 'load_device_by_mnemonic', 'load_device_by_xprv',
+ 'recovery_device', 'reset_device', 'sign_message',
+ 'sign_tx', 'wipe_device']:
+ setattr(cls, method, cls.wrapper(getattr(cls, method)))
diff --git a/electrum/plugins/trezor/cmdline.py b/electrum/plugins/trezor/cmdline.py
new file mode 100644
index 000000000..e435aad13
--- /dev/null
+++ b/electrum/plugins/trezor/cmdline.py
@@ -0,0 +1,14 @@
+from electrum.plugin import hook
+from .trezor import TrezorPlugin
+from ..hw_wallet import CmdLineHandler
+
+class Plugin(TrezorPlugin):
+ handler = CmdLineHandler()
+ @hook
+ def init_keystore(self, keystore):
+ if not isinstance(keystore, self.keystore_class):
+ return
+ keystore.handler = self.handler
+
+ def create_handler(self, window):
+ return self.handler
diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py
new file mode 100644
index 000000000..3711c636e
--- /dev/null
+++ b/electrum/plugins/trezor/qt.py
@@ -0,0 +1,613 @@
+from functools import partial
+import threading
+
+from PyQt5.Qt import Qt
+from PyQt5.Qt import QGridLayout, QInputDialog, QPushButton
+from PyQt5.Qt import QVBoxLayout, QLabel
+
+from electrum.gui.qt.util import *
+from electrum.i18n import _
+from electrum.plugin import hook, DeviceMgr
+from electrum.util import PrintError, UserCancelled, bh2u
+from electrum.wallet import Wallet, Standard_Wallet
+
+from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
+from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC,
+ RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX)
+
+
+PASSPHRASE_HELP_SHORT =_(
+ "Passphrases allow you to access new wallets, each "
+ "hidden behind a particular case-sensitive passphrase.")
+PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + " " + _(
+ "You need to create a separate Electrum wallet for each passphrase "
+ "you use as they each generate different addresses. Changing "
+ "your passphrase does not lose other wallets, each is still "
+ "accessible behind its own passphrase.")
+RECOMMEND_PIN = _(
+ "You should enable PIN protection. Your PIN is the only protection "
+ "for your bitcores if your device is lost or stolen.")
+PASSPHRASE_NOT_PIN = _(
+ "If you forget a passphrase you will be unable to access any "
+ "bitcores in the wallet behind it. A passphrase is not a PIN. "
+ "Only change this if you are sure you understand it.")
+MATRIX_RECOVERY = _(
+ "Enter the recovery words by pressing the buttons according to what "
+ "the device shows on its display. You can also use your NUMPAD.\n"
+ "Press BACKSPACE to go back a choice or word.\n")
+
+
+class MatrixDialog(WindowModalDialog):
+
+ def __init__(self, parent):
+ super(MatrixDialog, self).__init__(parent)
+ self.setWindowTitle(_("Trezor Matrix Recovery"))
+ self.num = 9
+ self.loop = QEventLoop()
+
+ vbox = QVBoxLayout(self)
+ vbox.addWidget(WWLabel(MATRIX_RECOVERY))
+
+ grid = QGridLayout()
+ grid.setSpacing(0)
+ self.char_buttons = []
+ for y in range(3):
+ for x in range(3):
+ button = QPushButton('?')
+ button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x))
+ grid.addWidget(button, 3 - y, x)
+ self.char_buttons.append(button)
+ vbox.addLayout(grid)
+
+ self.backspace_button = QPushButton("<=")
+ self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace))
+ self.cancel_button = QPushButton(_("Cancel"))
+ self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape))
+ buttons = Buttons(self.backspace_button, self.cancel_button)
+ vbox.addSpacing(40)
+ vbox.addLayout(buttons)
+ self.refresh()
+ self.show()
+
+ def refresh(self):
+ for y in range(3):
+ self.char_buttons[3 * y + 1].setEnabled(self.num == 9)
+
+ def is_valid(self, key):
+ return key >= ord('1') and key <= ord('9')
+
+ def process_key(self, key):
+ self.data = None
+ if key == Qt.Key_Backspace:
+ self.data = '\010'
+ elif key == Qt.Key_Escape:
+ self.data = 'x'
+ elif self.is_valid(key):
+ self.char_buttons[key - ord('1')].setFocus()
+ self.data = '%c' % key
+ if self.data:
+ self.loop.exit(0)
+
+ def keyPressEvent(self, event):
+ self.process_key(event.key())
+ if not self.data:
+ QDialog.keyPressEvent(self, event)
+
+ def get_matrix(self, num):
+ self.num = num
+ self.refresh()
+ self.loop.exec_()
+
+
+class QtHandler(QtHandlerBase):
+
+ pin_signal = pyqtSignal(object)
+ matrix_signal = pyqtSignal(object)
+ close_matrix_dialog_signal = pyqtSignal()
+
+ def __init__(self, win, pin_matrix_widget_class, device):
+ super(QtHandler, self).__init__(win, device)
+ self.pin_signal.connect(self.pin_dialog)
+ self.matrix_signal.connect(self.matrix_recovery_dialog)
+ self.close_matrix_dialog_signal.connect(self._close_matrix_dialog)
+ self.pin_matrix_widget_class = pin_matrix_widget_class
+ self.matrix_dialog = None
+
+ def get_pin(self, msg):
+ self.done.clear()
+ self.pin_signal.emit(msg)
+ self.done.wait()
+ return self.response
+
+ def get_matrix(self, msg):
+ self.done.clear()
+ self.matrix_signal.emit(msg)
+ self.done.wait()
+ data = self.matrix_dialog.data
+ if data == 'x':
+ self.close_matrix_dialog()
+ return data
+
+ def _close_matrix_dialog(self):
+ if self.matrix_dialog:
+ self.matrix_dialog.accept()
+ self.matrix_dialog = None
+
+ def close_matrix_dialog(self):
+ self.close_matrix_dialog_signal.emit()
+
+ def pin_dialog(self, msg):
+ # Needed e.g. when resetting a device
+ self.clear_dialog()
+ dialog = WindowModalDialog(self.top_level_window(), _("Enter PIN"))
+ matrix = self.pin_matrix_widget_class()
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(matrix)
+ vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
+ dialog.setLayout(vbox)
+ dialog.exec_()
+ self.response = str(matrix.get_value())
+ self.done.set()
+
+ def matrix_recovery_dialog(self, msg):
+ if not self.matrix_dialog:
+ self.matrix_dialog = MatrixDialog(self.top_level_window())
+ self.matrix_dialog.get_matrix(msg)
+ self.done.set()
+
+
+class QtPlugin(QtPluginBase):
+ # Derived classes must provide the following class-static variables:
+ # icon_file
+ # pin_matrix_widget_class
+
+ def create_handler(self, window):
+ return QtHandler(window, self.pin_matrix_widget_class(), self.device)
+
+ @hook
+ def receive_menu(self, menu, addrs, wallet):
+ if len(addrs) != 1:
+ return
+ for keystore in wallet.get_keystores():
+ if type(keystore) == self.keystore_class:
+ def show_address(keystore=keystore):
+ keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
+ device_name = "{} ({})".format(self.device, keystore.label)
+ menu.addAction(_("Show on {}").format(device_name), show_address)
+
+ def show_settings_dialog(self, window, keystore):
+ device_id = self.choose_device(window, keystore)
+ if device_id:
+ SettingsDialog(window, self, keystore, device_id).exec_()
+
+ def request_trezor_init_settings(self, wizard, method, model):
+ vbox = QVBoxLayout()
+ next_enabled = True
+ label = QLabel(_("Enter a label to name your device:"))
+ name = QLineEdit()
+ hl = QHBoxLayout()
+ hl.addWidget(label)
+ hl.addWidget(name)
+ hl.addStretch(1)
+ vbox.addLayout(hl)
+
+ def clean_text(widget):
+ text = widget.toPlainText().strip()
+ return ' '.join(text.split())
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ gb = QGroupBox()
+ hbox1 = QHBoxLayout()
+ gb.setLayout(hbox1)
+ vbox.addWidget(gb)
+ gb.setTitle(_("Select your seed length:"))
+ bg_numwords = QButtonGroup()
+ for i, count in enumerate([12, 18, 24]):
+ rb = QRadioButton(gb)
+ rb.setText(_("%d words") % count)
+ bg_numwords.addButton(rb)
+ bg_numwords.setId(rb, i)
+ hbox1.addWidget(rb)
+ rb.setChecked(True)
+ cb_pin = QCheckBox(_('Enable PIN protection'))
+ cb_pin.setChecked(True)
+ else:
+ text = QTextEdit()
+ text.setMaximumHeight(60)
+ if method == TIM_MNEMONIC:
+ msg = _("Enter your BIP39 mnemonic:")
+ else:
+ msg = _("Enter the master private key beginning with xprv:")
+ def set_enabled():
+ from electrum.keystore import is_xprv
+ wizard.next_button.setEnabled(is_xprv(clean_text(text)))
+ text.textChanged.connect(set_enabled)
+ next_enabled = False
+
+ vbox.addWidget(QLabel(msg))
+ vbox.addWidget(text)
+ pin = QLineEdit()
+ pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
+ pin.setMaximumWidth(100)
+ hbox_pin = QHBoxLayout()
+ hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
+ hbox_pin.addWidget(pin)
+ hbox_pin.addStretch(1)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ vbox.addWidget(WWLabel(RECOMMEND_PIN))
+ vbox.addWidget(cb_pin)
+ else:
+ vbox.addLayout(hbox_pin)
+
+ passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ cb_phrase = QCheckBox(_('Enable passphrases'))
+ cb_phrase.setChecked(False)
+ vbox.addWidget(passphrase_msg)
+ vbox.addWidget(passphrase_warning)
+ vbox.addWidget(cb_phrase)
+
+ # ask for recovery type (random word order OR matrix)
+ if method == TIM_RECOVER and not model == 'T':
+ gb_rectype = QGroupBox()
+ hbox_rectype = QHBoxLayout()
+ gb_rectype.setLayout(hbox_rectype)
+ vbox.addWidget(gb_rectype)
+ gb_rectype.setTitle(_("Select recovery type:"))
+ bg_rectype = QButtonGroup()
+
+ rb1 = QRadioButton(gb_rectype)
+ rb1.setText(_('Scrambled words'))
+ bg_rectype.addButton(rb1)
+ bg_rectype.setId(rb1, RECOVERY_TYPE_SCRAMBLED_WORDS)
+ hbox_rectype.addWidget(rb1)
+ rb1.setChecked(True)
+
+ rb2 = QRadioButton(gb_rectype)
+ rb2.setText(_('Matrix'))
+ bg_rectype.addButton(rb2)
+ bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX)
+ hbox_rectype.addWidget(rb2)
+ else:
+ bg_rectype = None
+
+ wizard.exec_layout(vbox, next_enabled=next_enabled)
+
+ if method in [TIM_NEW, TIM_RECOVER]:
+ item = bg_numwords.checkedId()
+ pin = cb_pin.isChecked()
+ recovery_type = bg_rectype.checkedId() if bg_rectype else None
+ else:
+ item = ' '.join(str(clean_text(text)).split())
+ pin = str(pin.text())
+ recovery_type = None
+
+ return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type)
+
+
+class Plugin(TrezorPlugin, QtPlugin):
+ icon_unpaired = ":icons/trezor_unpaired.png"
+ icon_paired = ":icons/trezor.png"
+
+ @classmethod
+ def pin_matrix_widget_class(self):
+ from trezorlib.qt.pinmatrix import PinMatrixWidget
+ return PinMatrixWidget
+
+
+class SettingsDialog(WindowModalDialog):
+ '''This dialog doesn't require a device be paired with a wallet.
+ We want users to be able to wipe a device even if they've forgotten
+ their PIN.'''
+
+ def __init__(self, window, plugin, keystore, device_id):
+ title = _("{} Settings").format(plugin.device)
+ super(SettingsDialog, self).__init__(window, title)
+ self.setMaximumWidth(540)
+
+ devmgr = plugin.device_manager()
+ config = devmgr.config
+ handler = keystore.handler
+ thread = keystore.thread
+ hs_rows, hs_cols = (64, 128)
+
+ def invoke_client(method, *args, **kw_args):
+ unpair_after = kw_args.pop('unpair_after', False)
+
+ def task():
+ client = devmgr.client_by_id(device_id)
+ if not client:
+ raise RuntimeError("Device not connected")
+ if method:
+ getattr(client, method)(*args, **kw_args)
+ if unpair_after:
+ devmgr.unpair_id(device_id)
+ return client.features
+
+ thread.add(task, on_success=update)
+
+ def update(features):
+ self.features = features
+ set_label_enabled()
+ if features.bootloader_hash:
+ bl_hash = bh2u(features.bootloader_hash)
+ bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
+ else:
+ bl_hash = "N/A"
+ noyes = [_("No"), _("Yes")]
+ endis = [_("Enable Passphrases"), _("Disable Passphrases")]
+ disen = [_("Disabled"), _("Enabled")]
+ setchange = [_("Set a PIN"), _("Change PIN")]
+
+ version = "%d.%d.%d" % (features.major_version,
+ features.minor_version,
+ features.patch_version)
+
+ device_label.setText(features.label)
+ pin_set_label.setText(noyes[features.pin_protection])
+ passphrases_label.setText(disen[features.passphrase_protection])
+ bl_hash_label.setText(bl_hash)
+ label_edit.setText(features.label)
+ device_id_label.setText(features.device_id)
+ initialized_label.setText(noyes[features.initialized])
+ version_label.setText(version)
+ clear_pin_button.setVisible(features.pin_protection)
+ clear_pin_warning.setVisible(features.pin_protection)
+ pin_button.setText(setchange[features.pin_protection])
+ pin_msg.setVisible(not features.pin_protection)
+ passphrase_button.setText(endis[features.passphrase_protection])
+ language_label.setText(features.language)
+
+ def set_label_enabled():
+ label_apply.setEnabled(label_edit.text() != self.features.label)
+
+ def rename():
+ invoke_client('change_label', label_edit.text())
+
+ def toggle_passphrase():
+ title = _("Confirm Toggle Passphrase Protection")
+ currently_enabled = self.features.passphrase_protection
+ if currently_enabled:
+ msg = _("After disabling passphrases, you can only pair this "
+ "Electrum wallet if it had an empty passphrase. "
+ "If its passphrase was not empty, you will need to "
+ "create a new wallet with the install wizard. You "
+ "can use this wallet again at any time by re-enabling "
+ "passphrases and entering its passphrase.")
+ else:
+ msg = _("Your current Electrum wallet can only be used with "
+ "an empty passphrase. You must create a separate "
+ "wallet with the install wizard for other passphrases "
+ "as each one generates a new set of addresses.")
+ msg += "\n\n" + _("Are you sure you want to proceed?")
+ if not self.question(msg, title=title):
+ return
+ invoke_client('toggle_passphrase', unpair_after=currently_enabled)
+
+ def change_homescreen():
+ dialog = QFileDialog(self, _("Choose Homescreen"))
+ filename, __ = dialog.getOpenFileName()
+ if not filename:
+ return # user cancelled
+
+ if filename.endswith('.toif'):
+ img = open(filename, 'rb').read()
+ if img[:8] != b'TOIf\x90\x00\x90\x00':
+ handler.show_error('File is not a TOIF file with size of 144x144')
+ return
+ else:
+ from PIL import Image # FIXME
+ im = Image.open(filename)
+ if im.size != (128, 64):
+ handler.show_error('Image must be 128 x 64 pixels')
+ return
+ im = im.convert('1')
+ pix = im.load()
+ img = bytearray(1024)
+ for j in range(64):
+ for i in range(128):
+ if pix[i, j]:
+ o = (i + j * 128)
+ img[o // 8] |= (1 << (7 - o % 8))
+ img = bytes(img)
+ invoke_client('change_homescreen', img)
+
+ def clear_homescreen():
+ invoke_client('change_homescreen', b'\x00')
+
+ def set_pin():
+ invoke_client('set_pin', remove=False)
+
+ def clear_pin():
+ invoke_client('set_pin', remove=True)
+
+ def wipe_device():
+ wallet = window.wallet
+ if wallet and sum(wallet.get_balance()):
+ title = _("Confirm Device Wipe")
+ msg = _("Are you SURE you want to wipe the device?\n"
+ "Your wallet still has bitcore in it!")
+ if not self.question(msg, title=title,
+ icon=QMessageBox.Critical):
+ return
+ invoke_client('wipe_device', unpair_after=True)
+
+ def slider_moved():
+ mins = timeout_slider.sliderPosition()
+ timeout_minutes.setText(_("%2d minutes") % mins)
+
+ def slider_released():
+ config.set_session_timeout(timeout_slider.sliderPosition() * 60)
+
+ # Information tab
+ info_tab = QWidget()
+ info_layout = QVBoxLayout(info_tab)
+ info_glayout = QGridLayout()
+ info_glayout.setColumnStretch(2, 1)
+ device_label = QLabel()
+ pin_set_label = QLabel()
+ passphrases_label = QLabel()
+ version_label = QLabel()
+ device_id_label = QLabel()
+ bl_hash_label = QLabel()
+ bl_hash_label.setWordWrap(True)
+ language_label = QLabel()
+ initialized_label = QLabel()
+ rows = [
+ (_("Device Label"), device_label),
+ (_("PIN set"), pin_set_label),
+ (_("Passphrases"), passphrases_label),
+ (_("Firmware Version"), version_label),
+ (_("Device ID"), device_id_label),
+ (_("Bootloader Hash"), bl_hash_label),
+ (_("Language"), language_label),
+ (_("Initialized"), initialized_label),
+ ]
+ for row_num, (label, widget) in enumerate(rows):
+ info_glayout.addWidget(QLabel(label), row_num, 0)
+ info_glayout.addWidget(widget, row_num, 1)
+ info_layout.addLayout(info_glayout)
+
+ # Settings tab
+ settings_tab = QWidget()
+ settings_layout = QVBoxLayout(settings_tab)
+ settings_glayout = QGridLayout()
+
+ # Settings tab - Label
+ label_msg = QLabel(_("Name this {}. If you have multiple devices "
+ "their labels help distinguish them.")
+ .format(plugin.device))
+ label_msg.setWordWrap(True)
+ label_label = QLabel(_("Device Label"))
+ label_edit = QLineEdit()
+ label_edit.setMinimumWidth(150)
+ label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
+ label_apply = QPushButton(_("Apply"))
+ label_apply.clicked.connect(rename)
+ label_edit.textChanged.connect(set_label_enabled)
+ settings_glayout.addWidget(label_label, 0, 0)
+ settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
+ settings_glayout.addWidget(label_apply, 0, 3)
+ settings_glayout.addWidget(label_msg, 1, 1, 1, -1)
+
+ # Settings tab - PIN
+ pin_label = QLabel(_("PIN Protection"))
+ pin_button = QPushButton()
+ pin_button.clicked.connect(set_pin)
+ settings_glayout.addWidget(pin_label, 2, 0)
+ settings_glayout.addWidget(pin_button, 2, 1)
+ pin_msg = QLabel(_("PIN protection is strongly recommended. "
+ "A PIN is your only protection against someone "
+ "stealing your bitcores if they obtain physical "
+ "access to your {}.").format(plugin.device))
+ pin_msg.setWordWrap(True)
+ pin_msg.setStyleSheet("color: red")
+ settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)
+
+ # Settings tab - Homescreen
+ homescreen_label = QLabel(_("Homescreen"))
+ homescreen_change_button = QPushButton(_("Change..."))
+ homescreen_clear_button = QPushButton(_("Reset"))
+ homescreen_change_button.clicked.connect(change_homescreen)
+ try:
+ import PIL
+ except ImportError:
+ homescreen_change_button.setDisabled(True)
+ homescreen_change_button.setToolTip(
+ _("Required package 'PIL' is not available - Please install it or use the Trezor website instead.")
+ )
+ homescreen_clear_button.clicked.connect(clear_homescreen)
+ homescreen_msg = QLabel(_("You can set the homescreen on your "
+ "device to personalize it. You must "
+ "choose a {} x {} monochrome black and "
+ "white image.").format(hs_rows, hs_cols))
+ homescreen_msg.setWordWrap(True)
+ settings_glayout.addWidget(homescreen_label, 4, 0)
+ settings_glayout.addWidget(homescreen_change_button, 4, 1)
+ settings_glayout.addWidget(homescreen_clear_button, 4, 2)
+ settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)
+
+ # Settings tab - Session Timeout
+ timeout_label = QLabel(_("Session Timeout"))
+ timeout_minutes = QLabel()
+ timeout_slider = QSlider(Qt.Horizontal)
+ timeout_slider.setRange(1, 60)
+ timeout_slider.setSingleStep(1)
+ timeout_slider.setTickInterval(5)
+ timeout_slider.setTickPosition(QSlider.TicksBelow)
+ timeout_slider.setTracking(True)
+ timeout_msg = QLabel(
+ _("Clear the session after the specified period "
+ "of inactivity. Once a session has timed out, "
+ "your PIN and passphrase (if enabled) must be "
+ "re-entered to use the device."))
+ timeout_msg.setWordWrap(True)
+ timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
+ slider_moved()
+ timeout_slider.valueChanged.connect(slider_moved)
+ timeout_slider.sliderReleased.connect(slider_released)
+ settings_glayout.addWidget(timeout_label, 6, 0)
+ settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
+ settings_glayout.addWidget(timeout_minutes, 6, 4)
+ settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
+ settings_layout.addLayout(settings_glayout)
+ settings_layout.addStretch(1)
+
+ # Advanced tab
+ advanced_tab = QWidget()
+ advanced_layout = QVBoxLayout(advanced_tab)
+ advanced_glayout = QGridLayout()
+
+ # Advanced tab - clear PIN
+ clear_pin_button = QPushButton(_("Disable PIN"))
+ clear_pin_button.clicked.connect(clear_pin)
+ clear_pin_warning = QLabel(
+ _("If you disable your PIN, anyone with physical access to your "
+ "{} device can spend your bitcores.").format(plugin.device))
+ clear_pin_warning.setWordWrap(True)
+ clear_pin_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(clear_pin_button, 0, 2)
+ advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)
+
+ # Advanced tab - toggle passphrase protection
+ passphrase_button = QPushButton()
+ passphrase_button.clicked.connect(toggle_passphrase)
+ passphrase_msg = WWLabel(PASSPHRASE_HELP)
+ passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
+ passphrase_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(passphrase_button, 3, 2)
+ advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
+ advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)
+
+ # Advanced tab - wipe device
+ wipe_device_button = QPushButton(_("Wipe Device"))
+ wipe_device_button.clicked.connect(wipe_device)
+ wipe_device_msg = QLabel(
+ _("Wipe the device, removing all data from it. The firmware "
+ "is left unchanged."))
+ wipe_device_msg.setWordWrap(True)
+ wipe_device_warning = QLabel(
+ _("Only wipe a device if you have the recovery seed written down "
+ "and the device wallet(s) are empty, otherwise the bitcores "
+ "will be lost forever."))
+ wipe_device_warning.setWordWrap(True)
+ wipe_device_warning.setStyleSheet("color: red")
+ advanced_glayout.addWidget(wipe_device_button, 6, 2)
+ advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
+ advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
+ advanced_layout.addLayout(advanced_glayout)
+ advanced_layout.addStretch(1)
+
+ tabs = QTabWidget(self)
+ tabs.addTab(info_tab, _("Information"))
+ tabs.addTab(settings_tab, _("Settings"))
+ tabs.addTab(advanced_tab, _("Advanced"))
+ dialog_vbox = QVBoxLayout(self)
+ dialog_vbox.addWidget(tabs)
+ dialog_vbox.addLayout(Buttons(CloseButton(self)))
+
+ # Update information
+ invoke_client(None)
diff --git a/electrum/plugins/trezor/transport.py b/electrum/plugins/trezor/transport.py
new file mode 100644
index 000000000..5ce686c69
--- /dev/null
+++ b/electrum/plugins/trezor/transport.py
@@ -0,0 +1,95 @@
+from electrum.util import PrintError
+
+
+class TrezorTransport(PrintError):
+
+ @staticmethod
+ def all_transports():
+ """Reimplemented trezorlib.transport.all_transports so that we can
+ enable/disable specific transports.
+ """
+ try:
+ # only to detect trezorlib version
+ from trezorlib.transport import all_transports
+ except ImportError:
+ # old trezorlib. compat for trezorlib < 0.9.2
+ transports = []
+ #try:
+ # from trezorlib.transport_bridge import BridgeTransport
+ # transports.append(BridgeTransport)
+ #except BaseException:
+ # pass
+ try:
+ from trezorlib.transport_hid import HidTransport
+ transports.append(HidTransport)
+ except BaseException:
+ pass
+ try:
+ from trezorlib.transport_udp import UdpTransport
+ transports.append(UdpTransport)
+ except BaseException:
+ pass
+ try:
+ from trezorlib.transport_webusb import WebUsbTransport
+ transports.append(WebUsbTransport)
+ except BaseException:
+ pass
+ else:
+ # new trezorlib.
+ transports = []
+ #try:
+ # from trezorlib.transport.bridge import BridgeTransport
+ # transports.append(BridgeTransport)
+ #except BaseException:
+ # pass
+ try:
+ from trezorlib.transport.hid import HidTransport
+ transports.append(HidTransport)
+ except BaseException:
+ pass
+ try:
+ from trezorlib.transport.udp import UdpTransport
+ transports.append(UdpTransport)
+ except BaseException:
+ pass
+ try:
+ from trezorlib.transport.webusb import WebUsbTransport
+ transports.append(WebUsbTransport)
+ except BaseException:
+ pass
+ return transports
+ return transports
+
+ def enumerate_devices(self):
+ """Just like trezorlib.transport.enumerate_devices,
+ but with exception catching, so that transports can fail separately.
+ """
+ devices = []
+ for transport in self.all_transports():
+ try:
+ new_devices = transport.enumerate()
+ except BaseException as e:
+ self.print_error('enumerate failed for {}. error {}'
+ .format(transport.__name__, str(e)))
+ else:
+ devices.extend(new_devices)
+ return devices
+
+ def get_transport(self, path=None):
+ """Reimplemented trezorlib.transport.get_transport,
+ (1) for old trezorlib
+ (2) to be able to disable specific transports
+ (3) to call our own enumerate_devices that catches exceptions
+ """
+ if path is None:
+ try:
+ return self.enumerate_devices()[0]
+ except IndexError:
+ raise Exception("No TREZOR device found") from None
+
+ def match_prefix(a, b):
+ return a.startswith(b) or b.startswith(a)
+ transports = [t for t in self.all_transports() if match_prefix(path, t.PATH_PREFIX)]
+ if transports:
+ return transports[0].find_by_path(path)
+ raise Exception("Unknown path prefix '%s'" % path)
diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
new file mode 100644
index 000000000..ce16e03ae
--- /dev/null
+++ b/electrum/plugins/trezor/trezor.py
@@ -0,0 +1,495 @@
+from binascii import hexlify, unhexlify
+import traceback
+import sys
+
+from electrum.util import bfh, bh2u, versiontuple, UserCancelled
+from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey, deserialize_xpub,
+ TYPE_ADDRESS, TYPE_SCRIPT, is_address)
+from electrum import constants
+from electrum.i18n import _
+from electrum.plugin import BasePlugin, Device
+from electrum.transaction import deserialize, Transaction
+from electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey
+from electrum.base_wizard import ScriptTypeNotSupported
+
+from ..hw_wallet import HW_PluginBase
+from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
+
+
+# TREZOR initialization methods
+TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
+RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(0, 2)
+
+
+class TrezorKeyStore(Hardware_KeyStore):
+ hw_type = 'trezor'
+ device = 'TREZOR'
+
+ def get_derivation(self):
+ return self.derivation
+
+ def get_client(self, force_pair=True):
+ return self.plugin.get_client(self, force_pair)
+
+ def decrypt_message(self, sequence, message, password):
+ raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
+
+ def sign_message(self, sequence, message, password):
+ client = self.get_client()
+ address_path = self.get_derivation() + "/%d/%d"%sequence
+ address_n = client.expand_path(address_path)
+ msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)
+ return msg_sig.signature
+
+ def sign_transaction(self, tx, password):
+ if tx.is_complete():
+ return
+ # previous transactions used as inputs
+ prev_tx = {}
+ # path of the xpubs that are involved
+ xpub_path = {}
+ for txin in tx.inputs():
+ pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
+ tx_hash = txin['prevout_hash']
+ if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
+ raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
+ prev_tx[tx_hash] = txin['prev_tx']
+ for x_pubkey in x_pubkeys:
+ if not is_xpubkey(x_pubkey):
+ continue
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub == self.get_master_public_key():
+ xpub_path[xpub] = self.get_derivation()
+
+ self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
+
+
+class TrezorPlugin(HW_PluginBase):
+ # Derived classes provide:
+ #
+ # class-static variables: client_class, firmware_URL, handler_class,
+ # libraries_available, libraries_URL, minimum_firmware,
+ # wallet_class, types
+
+ firmware_URL = 'https://wallet.trezor.io'
+ libraries_URL = 'https://github.com/trezor/python-trezor'
+ minimum_firmware = (1, 7, 1)
+ keystore_class = TrezorKeyStore
+ minimum_library = (0, 9, 0)
+ SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
+
+ MAX_LABEL_LEN = 32
+
+ def __init__(self, parent, config, name):
+ HW_PluginBase.__init__(self, parent, config, name)
+
+ self.libraries_available = self.check_libraries_available()
+ if not self.libraries_available:
+ return
+
+ from . import client
+ from . import transport
+ import trezorlib.messages
+ self.client_class = client.TrezorClient
+ self.types = trezorlib.messages
+ self.DEVICE_IDS = ('TREZOR',)
+
+ self.transport_handler = transport.TrezorTransport()
+ self.device_manager().register_enumerate_func(self.enumerate)
+
+ def get_library_version(self):
+ import trezorlib
+ try:
+ return trezorlib.__version__
+ except AttributeError:
+ return 'unknown'
+
+ def enumerate(self):
+ devices = self.transport_handler.enumerate_devices()
+ return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in devices]
+
+ def create_client(self, device, handler):
+ try:
+ self.print_error("connecting to device at", device.path)
+ transport = self.transport_handler.get_transport(device.path)
+ except BaseException as e:
+ self.print_error("cannot connect at", device.path, str(e))
+ return None
+
+ if not transport:
+ self.print_error("cannot connect at", device.path)
+ return
+
+ self.print_error("connected to device at", device.path)
+ client = self.client_class(transport, handler, self)
+
+ # Try a ping for device sanity
+ try:
+ client.ping('t')
+ except BaseException as e:
+ self.print_error("ping failed", str(e))
+ return None
+
+ if not client.atleast_version(*self.minimum_firmware):
+ msg = (_('Outdated {} firmware for device labelled {}. Please '
+ 'download the updated firmware from {}')
+ .format(self.device, client.label(), self.firmware_URL))
+ self.print_error(msg)
+ if handler:
+ handler.show_error(msg)
+ else:
+ raise Exception(msg)
+ return None
+
+ return client
+
+ def get_client(self, keystore, force_pair=True):
+ devmgr = self.device_manager()
+ handler = keystore.handler
+ with devmgr.hid_lock:
+ client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
+ # returns the client for a given keystore. can use xpub
+ if client:
+ client.used()
+ return client
+
+ def get_coin_name(self):
+ return "Testnet" if constants.net.TESTNET else "Bitcore"
+
+ def initialize_device(self, device_id, wizard, handler):
+ # Initialization method
+ msg = _("Choose how you want to initialize your {}.\n\n"
+ "The first two methods are secure as no secret information "
+ "is entered into your computer.\n\n"
+ "For the last two methods you input secrets on your keyboard "
+ "and upload them to your {}, and so you should "
+ "only do those on a computer you know to be trustworthy "
+ "and free of malware."
+ ).format(self.device, self.device)
+ choices = [
+ # Must be short as QT doesn't word-wrap radio button text
+ (TIM_NEW, _("Let the device generate a completely new seed randomly")),
+ (TIM_RECOVER, _("Recover from a seed you have previously written down")),
+ (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
+ (TIM_PRIVKEY, _("Upload a master private key"))
+ ]
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ model = client.get_trezor_model()
+ def f(method):
+ import threading
+ settings = self.request_trezor_init_settings(wizard, method, model)
+ t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
+ t.setDaemon(True)
+ t.start()
+ exit_code = wizard.loop.exec_()
+ if exit_code != 0:
+ # this method (initialize_device) was called with the expectation
+ # of leaving the device in an initialized state when finishing.
+ # signal that this is not the case:
+ raise UserCancelled()
+ wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)
+
+ def _initialize_device_safe(self, settings, method, device_id, wizard, handler):
+ exit_code = 0
+ try:
+ self._initialize_device(settings, method, device_id, wizard, handler)
+ except UserCancelled:
+ exit_code = 1
+ except BaseException as e:
+ traceback.print_exc(file=sys.stderr)
+ handler.show_error(str(e))
+ exit_code = 1
+ finally:
+ wizard.loop.exit(exit_code)
+
+ def _initialize_device(self, settings, method, device_id, wizard, handler):
+ item, label, pin_protection, passphrase_protection, recovery_type = settings
+
+ if method == TIM_RECOVER and recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
+ handler.show_error(_(
+ "You will be asked to enter 24 words regardless of your "
+ "seed's actual length. If you enter a word incorrectly or "
+ "misspell it, you cannot change it or go back - you will need "
+ "to start again from the beginning.\n\nSo please enter "
+ "the words carefully!"),
+ blocking=True)
+
+ language = 'english'
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+
+ if method == TIM_NEW:
+ strength = 64 * (item + 2) # 128, 192 or 256
+ u2f_counter = 0
+ skip_backup = False
+ client.reset_device(True, strength, passphrase_protection,
+ pin_protection, label, language,
+ u2f_counter, skip_backup)
+ elif method == TIM_RECOVER:
+ word_count = 6 * (item + 2) # 12, 18 or 24
+ client.step = 0
+ if recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
+ recovery_type_trezor = self.types.RecoveryDeviceType.ScrambledWords
+ else:
+ recovery_type_trezor = self.types.RecoveryDeviceType.Matrix
+ client.recovery_device(word_count, passphrase_protection,
+ pin_protection, label, language,
+ type=recovery_type_trezor)
+ if recovery_type == RECOVERY_TYPE_MATRIX:
+ handler.close_matrix_dialog()
+ elif method == TIM_MNEMONIC:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_mnemonic(str(item), pin,
+ passphrase_protection,
+ label, language)
+ else:
+ pin = pin_protection # It's the pin, not a boolean
+ client.load_device_by_xprv(item, pin, passphrase_protection,
+ label, language)
+
+ def _make_node_path(self, xpub, address_n):
+ _, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
+ node = self.types.HDNodeType(
+ depth=depth,
+ fingerprint=int.from_bytes(fingerprint, 'big'),
+ child_num=int.from_bytes(child_num, 'big'),
+ chain_code=chain_code,
+ public_key=key,
+ )
+ return self.types.HDNodePathType(node=node, address_n=address_n)
+
+ def setup_device(self, device_info, wizard, purpose):
+ devmgr = self.device_manager()
+ device_id = device_info.device.id_
+ client = devmgr.client_by_id(device_id)
+ if client is None:
+ raise Exception(_('Failed to create a client for this device.') + '\n' +
+ _('Make sure it is in the correct state.'))
+ # fixme: we should use: client.handler = wizard
+ client.handler = self.create_handler(wizard)
+ if not device_info.initialized:
+ self.initialize_device(device_id, wizard, client.handler)
+ client.get_xpub('m', 'standard')
+ client.used()
+
+ def get_xpub(self, device_id, derivation, xtype, wizard):
+ if xtype not in self.SUPPORTED_XTYPES:
+ raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device))
+ devmgr = self.device_manager()
+ client = devmgr.client_by_id(device_id)
+ client.handler = wizard
+ xpub = client.get_xpub(derivation, xtype)
+ client.used()
+ return xpub
+
+ def get_trezor_input_script_type(self, electrum_txin_type: str):
+ if electrum_txin_type in ('p2wpkh', 'p2wsh'):
+ return self.types.InputScriptType.SPENDWITNESS
+ if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
+ return self.types.InputScriptType.SPENDP2SHWITNESS
+ if electrum_txin_type in ('p2pkh', ):
+ return self.types.InputScriptType.SPENDADDRESS
+ if electrum_txin_type in ('p2sh', ):
+ return self.types.InputScriptType.SPENDMULTISIG
+ raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
+
+ def get_trezor_output_script_type(self, electrum_txin_type: str):
+ if electrum_txin_type in ('p2wpkh', 'p2wsh'):
+ return self.types.OutputScriptType.PAYTOWITNESS
+ if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'):
+ return self.types.OutputScriptType.PAYTOP2SHWITNESS
+ if electrum_txin_type in ('p2pkh', ):
+ return self.types.OutputScriptType.PAYTOADDRESS
+ if electrum_txin_type in ('p2sh', ):
+ return self.types.OutputScriptType.PAYTOMULTISIG
+ raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
+
+ def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
+ self.prev_tx = prev_tx
+ self.xpub_path = xpub_path
+ client = self.get_client(keystore)
+ inputs = self.tx_inputs(tx, True)
+ outputs = self.tx_outputs(keystore.get_derivation(), tx)
+ signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
+ signatures = [(bh2u(x) + '01') for x in signatures]
+ tx.update_signatures(signatures)
+
+ def show_address(self, wallet, address, keystore=None):
+ if keystore is None:
+ keystore = wallet.get_keystore()
+ if not self.show_address_helper(wallet, address, keystore):
+ return
+ client = self.get_client(keystore)
+ if not client.atleast_version(1, 3):
+ keystore.handler.show_error(_("Your device firmware is too old"))
+ return
+ change, index = wallet.get_address_index(address)
+ derivation = keystore.derivation
+ address_path = "%s/%d/%d"%(derivation, change, index)
+ address_n = client.expand_path(address_path)
+ xpubs = wallet.get_master_public_keys()
+ if len(xpubs) == 1:
+ script_type = self.get_trezor_input_script_type(wallet.txin_type)
+ client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)
+ else:
+ def f(xpub):
+ return self._make_node_path(xpub, [change, index])
+ pubkeys = wallet.get_public_keys(address)
+ # sort xpubs using the order of pubkeys
+ sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))
+ pubkeys = list(map(f, sorted_xpubs))
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=[b''] * wallet.n,
+ m=wallet.m,
+ )
+ script_type = self.get_trezor_input_script_type(wallet.txin_type)
+ client.get_address(self.get_coin_name(), address_n, True, multisig=multisig, script_type=script_type)
+
+ def tx_inputs(self, tx, for_sig=False):
+ inputs = []
+ for txin in tx.inputs():
+ txinputtype = self.types.TxInputType()
+ if txin['type'] == 'coinbase':
+ prev_hash = b"\x00"*32
+ prev_index = 0xffffffff # signed int -1
+ else:
+ if for_sig:
+ x_pubkeys = txin['x_pubkeys']
+ if len(x_pubkeys) == 1:
+ x_pubkey = x_pubkeys[0]
+ xpub, s = parse_xpubkey(x_pubkey)
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype._extend_address_n(xpub_n + s)
+ txinputtype.script_type = self.get_trezor_input_script_type(txin['type'])
+ else:
+ def f(x_pubkey):
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ else:
+ xpub = xpub_from_pubkey(0, bfh(x_pubkey))
+ s = []
+ return self._make_node_path(xpub, s)
+ pubkeys = list(map(f, x_pubkeys))
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=list(map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))),
+ m=txin.get('num_sig'),
+ )
+ script_type = self.get_trezor_input_script_type(txin['type'])
+ txinputtype = self.types.TxInputType(
+ script_type=script_type,
+ multisig=multisig
+ )
+ # find which key is mine
+ for x_pubkey in x_pubkeys:
+ if is_xpubkey(x_pubkey):
+ xpub, s = parse_xpubkey(x_pubkey)
+ if xpub in self.xpub_path:
+ xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
+ txinputtype._extend_address_n(xpub_n + s)
+ break
+
+ prev_hash = unhexlify(txin['prevout_hash'])
+ prev_index = txin['prevout_n']
+
+ if 'value' in txin:
+ txinputtype.amount = txin['value']
+ txinputtype.prev_hash = prev_hash
+ txinputtype.prev_index = prev_index
+
+ if txin.get('scriptSig') is not None:
+ script_sig = bfh(txin['scriptSig'])
+ txinputtype.script_sig = script_sig
+
+ txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)
+
+ inputs.append(txinputtype)
+
+ return inputs
+
+ def tx_outputs(self, derivation, tx):
+
+ def create_output_by_derivation():
+ script_type = self.get_trezor_output_script_type(info.script_type)
+ if len(xpubs) == 1:
+ address_n = self.client_class.expand_path(derivation + "/%d/%d" % index)
+ txoutputtype = self.types.TxOutputType(
+ amount=amount,
+ script_type=script_type,
+ address_n=address_n,
+ )
+ else:
+ address_n = self.client_class.expand_path("/%d/%d" % index)
+ pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs]
+ multisig = self.types.MultisigRedeemScriptType(
+ pubkeys=pubkeys,
+ signatures=[b''] * len(pubkeys),
+ m=m)
+ txoutputtype = self.types.TxOutputType(
+ multisig=multisig,
+ amount=amount,
+ address_n=self.client_class.expand_path(derivation + "/%d/%d" % index),
+ script_type=script_type)
+ return txoutputtype
+
+ def create_output_by_address():
+ txoutputtype = self.types.TxOutputType()
+ txoutputtype.amount = amount
+ if _type == TYPE_SCRIPT:
+ txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
+ txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
+ elif _type == TYPE_ADDRESS:
+ txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
+ txoutputtype.address = address
+ return txoutputtype
+
+ outputs = []
+ has_change = False
+ any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
+
+ for o in tx.outputs():
+ _type, address, amount = o.type, o.address, o.value
+ use_create_by_derivation = False
+
+ info = tx.output_info.get(address)
+ if info is not None and not has_change:
+ index, xpubs, m = info.address_index, info.sorted_xpubs, info.num_sig
+ on_change_branch = index[0] == 1
+ # prioritise hiding outputs on the 'change' branch from user
+ # because no more than one change address allowed
+ # note: ^ restriction can be removed once we require fw
+ # that has https://github.com/trezor/trezor-mcu/pull/306
+ if on_change_branch == any_output_on_change_branch:
+ use_create_by_derivation = True
+ has_change = True
+
+ if use_create_by_derivation:
+ txoutputtype = create_output_by_derivation()
+ else:
+ txoutputtype = create_output_by_address()
+ outputs.append(txoutputtype)
+
+ return outputs
+
+ def electrum_tx_to_txtype(self, tx):
+ t = self.types.TransactionType()
+ if tx is None:
+ # probably for segwit input and we don't need this prev txn
+ return t
+ d = deserialize(tx.raw)
+ t.version = d['version']
+ t.lock_time = d['lockTime']
+ inputs = self.tx_inputs(tx)
+ t._extend_inputs(inputs)
+ for vout in d['outputs']:
+ o = t._add_bin_outputs()
+ o.amount = vout['value']
+ o.script_pubkey = bfh(vout['scriptPubKey'])
+ return t
+
+ # This function is called from the TREZOR libraries (via tx_api)
+ def get_tx(self, tx_hash):
+ tx = self.prev_tx[tx_hash]
+ return self.electrum_tx_to_txtype(tx)
diff --git a/electrum/plugins/trustedcoin/__init__.py b/electrum/plugins/trustedcoin/__init__.py
new file mode 100644
index 000000000..81ec4f2ce
--- /dev/null
+++ b/electrum/plugins/trustedcoin/__init__.py
@@ -0,0 +1,11 @@
+from electrum.i18n import _
+
+fullname = _('Two Factor Authentication')
+description = ''.join([
+ _("This plugin adds two-factor authentication to your wallet."), ' ',
+ _("For more information, visit"),
+ " https://api.trustedcoin.com/#/electrum-help "
+])
+requires_wallet_type = ['2fa']
+registers_wallet_type = '2fa'
+available_for = ['qt', 'cmdline', 'kivy']
diff --git a/electrum/plugins/trustedcoin/cmdline.py b/electrum/plugins/trustedcoin/cmdline.py
new file mode 100644
index 000000000..261b70ec9
--- /dev/null
+++ b/electrum/plugins/trustedcoin/cmdline.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from electrum.i18n import _
+from electrum.plugin import hook
+from .trustedcoin import TrustedCoinPlugin
+
+
+class Plugin(TrustedCoinPlugin):
+
+ def prompt_user_for_otp(self, wallet, tx):
+ if not isinstance(wallet, self.wallet_class):
+ return
+ if not wallet.can_sign_without_server():
+ self.print_error("twofactor:sign_tx")
+ auth_code = None
+ if wallet.keystores['x3/'].get_tx_derivations(tx):
+ msg = _('Please enter your Google Authenticator code:')
+ auth_code = int(input(msg))
+ else:
+ self.print_error("twofactor: xpub3 not needed")
+ wallet.auth_code = auth_code
+
diff --git a/electrum/plugins/trustedcoin/kivy.py b/electrum/plugins/trustedcoin/kivy.py
new file mode 100644
index 000000000..4cd01fbe8
--- /dev/null
+++ b/electrum/plugins/trustedcoin/kivy.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from functools import partial
+from threading import Thread
+import re
+from decimal import Decimal
+
+from kivy.clock import Clock
+
+from electrum.i18n import _
+from electrum.plugin import hook
+from .trustedcoin import TrustedCoinPlugin, server, KIVY_DISCLAIMER, TrustedCoinException, ErrorConnectingServer
+
+
+
+class Plugin(TrustedCoinPlugin):
+
+ disclaimer_msg = KIVY_DISCLAIMER
+
+ def __init__(self, parent, config, name):
+ super().__init__(parent, config, name)
+
+ @hook
+ def load_wallet(self, wallet, window):
+ if not isinstance(wallet, self.wallet_class):
+ return
+ self.start_request_thread(wallet)
+
+ def go_online_dialog(self, wizard):
+ # we skip this step on android
+ wizard.run('accept_terms_of_use')
+
+ def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
+ from ...gui.kivy.uix.dialogs.label_dialog import LabelDialog
+ msg = _('Please enter your Google Authenticator code')
+ d = LabelDialog(msg, '', lambda otp: self.on_otp(wallet, tx, otp, on_success, on_failure))
+ d.open()
+
+ def on_otp(self, wallet, tx, otp, on_success, on_failure):
+ try:
+ wallet.on_otp(tx, otp)
+ except TrustedCoinException as e:
+ if e.status_code == 400: # invalid OTP
+ Clock.schedule_once(lambda dt: on_failure(_('Invalid one-time password.')))
+ else:
+ Clock.schedule_once(lambda dt, bound_e=e: on_failure(_('Error') + ':\n' + str(bound_e)))
+ except Exception as e:
+ Clock.schedule_once(lambda dt, bound_e=e: on_failure(_('Error') + ':\n' + str(bound_e)))
+ else:
+ on_success(tx)
+
+ def accept_terms_of_use(self, wizard):
+ def handle_error(msg, e):
+ wizard.show_error(msg + ':\n' + str(e))
+ wizard.terminate()
+ try:
+ tos = server.get_terms_of_service()
+ except ErrorConnectingServer as e:
+ Clock.schedule_once(lambda dt, bound_e=e: handle_error(_('Error connecting to server'), bound_e))
+ except Exception as e:
+ Clock.schedule_once(lambda dt, bound_e=e: handle_error(_('Error'), bound_e))
+ else:
+ f = lambda x: self.read_email(wizard)
+ wizard.tos_dialog(tos=tos, run_next=f)
+
+ def read_email(self, wizard):
+ f = lambda x: self.create_remote_key(x, wizard)
+ wizard.email_dialog(run_next=f)
+
+ def request_otp_dialog(self, wizard, short_id, otp_secret, xpub3):
+ f = lambda otp, reset: self.check_otp(wizard, short_id, otp_secret, xpub3, otp, reset)
+ wizard.otp_dialog(otp_secret=otp_secret, run_next=f)
+
+ @hook
+ def abort_send(self, window):
+ wallet = window.wallet
+ if not isinstance(wallet, self.wallet_class):
+ return
+ if wallet.can_sign_without_server():
+ return
+ if wallet.billing_info is None:
+ self.start_request_thread(wallet)
+ Clock.schedule_once(
+ lambda dt: window.show_error(_('Requesting account info from TrustedCoin server...') + '\n' +
+ _('Please try again.')))
+ return True
+ return False
diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py
new file mode 100644
index 000000000..414027e63
--- /dev/null
+++ b/electrum/plugins/trustedcoin/qt.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from functools import partial
+import threading
+from threading import Thread
+import re
+from decimal import Decimal
+
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+
+from electrum.gui.qt.util import *
+from electrum.gui.qt.qrcodewidget import QRCodeWidget
+from electrum.gui.qt.amountedit import AmountEdit
+from electrum.gui.qt.main_window import StatusBarButton
+from electrum.i18n import _
+from electrum.plugin import hook
+from electrum.util import PrintError, is_valid_email
+from .trustedcoin import TrustedCoinPlugin, server
+
+
+class TOS(QTextEdit):
+ tos_signal = pyqtSignal()
+ error_signal = pyqtSignal(object)
+
+
+class HandlerTwoFactor(QObject, PrintError):
+
+ def __init__(self, plugin, window):
+ super().__init__()
+ self.plugin = plugin
+ self.window = window
+
+ def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
+ if not isinstance(wallet, self.plugin.wallet_class):
+ return
+ if wallet.can_sign_without_server():
+ return
+ if not wallet.keystores['x3/'].get_tx_derivations(tx):
+ self.print_error("twofactor: xpub3 not needed")
+ return
+ window = self.window.top_level_window()
+ auth_code = self.plugin.auth_dialog(window)
+ try:
+ wallet.on_otp(tx, auth_code)
+ except:
+ on_failure(sys.exc_info())
+ return
+ on_success(tx)
+
+class Plugin(TrustedCoinPlugin):
+
+ def __init__(self, parent, config, name):
+ super().__init__(parent, config, name)
+
+ @hook
+ def on_new_window(self, window):
+ wallet = window.wallet
+ if not isinstance(wallet, self.wallet_class):
+ return
+ wallet.handler_2fa = HandlerTwoFactor(self, window)
+ if wallet.can_sign_without_server():
+ msg = ' '.join([
+ _('This wallet was restored from seed, and it contains two master private keys.'),
+ _('Therefore, two-factor authentication is disabled.')
+ ])
+ action = lambda: window.show_message(msg)
+ else:
+ action = partial(self.settings_dialog, window)
+ button = StatusBarButton(QIcon(":icons/trustedcoin-status.png"),
+ _("TrustedCoin"), action)
+ window.statusBar().addPermanentWidget(button)
+ self.start_request_thread(window.wallet)
+
+ def auth_dialog(self, window):
+ d = WindowModalDialog(window, _("Authorization"))
+ vbox = QVBoxLayout(d)
+ pw = AmountEdit(None, is_int = True)
+ msg = _('Please enter your Google Authenticator code')
+ vbox.addWidget(QLabel(msg))
+ grid = QGridLayout()
+ grid.setSpacing(8)
+ grid.addWidget(QLabel(_('Code')), 1, 0)
+ grid.addWidget(pw, 1, 1)
+ vbox.addLayout(grid)
+ msg = _('If you have lost your second factor, you need to restore your wallet from seed in order to request a new code.')
+ label = QLabel(msg)
+ label.setWordWrap(1)
+ vbox.addWidget(label)
+ vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
+ if not d.exec_():
+ return
+ return pw.get_amount()
+
+ def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
+ wallet.handler_2fa.prompt_user_for_otp(wallet, tx, on_success, on_failure)
+
+ def waiting_dialog(self, window, on_finished=None):
+ task = partial(self.request_billing_info, window.wallet)
+ return WaitingDialog(window, 'Getting billing information...', task,
+ on_finished)
+
+ @hook
+ def abort_send(self, window):
+ wallet = window.wallet
+ if not isinstance(wallet, self.wallet_class):
+ return
+ if wallet.can_sign_without_server():
+ return
+ if wallet.billing_info is None:
+ self.start_request_thread(wallet)
+ window.show_error(_('Requesting account info from TrustedCoin server...') + '\n' +
+ _('Please try again.'))
+ return True
+ return False
+
+ def settings_dialog(self, window):
+ self.waiting_dialog(window, partial(self.show_settings_dialog, window))
+
+ def show_settings_dialog(self, window, success):
+ if not success:
+ window.show_message(_('Server not reachable.'))
+ return
+
+ wallet = window.wallet
+ d = WindowModalDialog(window, _("TrustedCoin Information"))
+ d.setMinimumSize(500, 200)
+ vbox = QVBoxLayout(d)
+ hbox = QHBoxLayout()
+
+ logo = QLabel()
+ logo.setPixmap(QPixmap(":icons/trustedcoin-status.png"))
+ msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + ' '\
+ + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help "
+ label = QLabel(msg)
+ label.setOpenExternalLinks(1)
+
+ hbox.addStretch(10)
+ hbox.addWidget(logo)
+ hbox.addStretch(10)
+ hbox.addWidget(label)
+ hbox.addStretch(10)
+
+ vbox.addLayout(hbox)
+ vbox.addStretch(10)
+
+ 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 every time you run out of prepaid transactions.') + ' '
+ label = QLabel(msg)
+ label.setWordWrap(1)
+ vbox.addWidget(label)
+
+ vbox.addStretch(10)
+ grid = QGridLayout()
+ vbox.addLayout(grid)
+
+ price_per_tx = wallet.price_per_tx
+ n_prepay = wallet.num_prepay(self.config)
+ i = 0
+ for k, v in sorted(price_per_tx.items()):
+ if k == 1:
+ continue
+ grid.addWidget(QLabel("Pay every %d transactions:"%k), i, 0)
+ grid.addWidget(QLabel(window.format_amount(v/k) + ' ' + window.base_unit() + "/tx"), i, 1)
+ b = QRadioButton()
+ b.setChecked(k == n_prepay)
+ b.clicked.connect(lambda b, k=k: self.config.set_key('trustedcoin_prepay', k, True))
+ grid.addWidget(b, i, 2)
+ i += 1
+
+ n = wallet.billing_info.get('tx_remaining', 0)
+ grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0)
+ vbox.addLayout(Buttons(CloseButton(d)))
+ d.exec_()
+
+ def on_buy(self, window, k, v, d):
+ d.close()
+ if window.pluginsdialog:
+ window.pluginsdialog.close()
+ wallet = window.wallet
+ uri = "bitcore:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
+ wallet.is_billing = True
+ window.pay_to_URI(uri)
+ window.payto_e.setFrozen(True)
+ window.message_e.setFrozen(True)
+ window.amount_e.setFrozen(True)
+
+ def go_online_dialog(self, wizard):
+ msg = [
+ _("Your wallet file is: {}.").format(os.path.abspath(wizard.storage.path)),
+ _("You need to be online in order to complete the creation of "
+ "your wallet. If you generated your seed on an offline "
+ 'computer, click on "{}" to close this window, move your '
+ "wallet file to an online computer, and reopen it with "
+ "Electrum.").format(_('Cancel')),
+ _('If you are online, click on "{}" to continue.').format(_('Next'))
+ ]
+ msg = '\n\n'.join(msg)
+ wizard.stack = []
+ wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('accept_terms_of_use'))
+
+ def accept_terms_of_use(self, window):
+ vbox = QVBoxLayout()
+ vbox.addWidget(QLabel(_("Terms of Service")))
+
+ tos_e = TOS()
+ tos_e.setReadOnly(True)
+ vbox.addWidget(tos_e)
+ tos_received = False
+
+ vbox.addWidget(QLabel(_("Please enter your e-mail address")))
+ email_e = QLineEdit()
+ vbox.addWidget(email_e)
+
+ next_button = window.next_button
+ prior_button_text = next_button.text()
+ next_button.setText(_('Accept'))
+
+ def request_TOS():
+ try:
+ tos = server.get_terms_of_service()
+ except Exception as e:
+ import traceback
+ traceback.print_exc(file=sys.stderr)
+ tos_e.error_signal.emit(_('Could not retrieve Terms of Service:')
+ + '\n' + str(e))
+ return
+ self.TOS = tos
+ tos_e.tos_signal.emit()
+
+ def on_result():
+ tos_e.setText(self.TOS)
+ nonlocal tos_received
+ tos_received = True
+ set_enabled()
+
+ def on_error(msg):
+ window.show_error(str(msg))
+ window.terminate()
+
+ def set_enabled():
+ next_button.setEnabled(tos_received and is_valid_email(email_e.text()))
+
+ tos_e.tos_signal.connect(on_result)
+ tos_e.error_signal.connect(on_error)
+ t = Thread(target=request_TOS)
+ t.setDaemon(True)
+ t.start()
+ email_e.textChanged.connect(set_enabled)
+ email_e.setFocus(True)
+ window.exec_layout(vbox, next_enabled=False)
+ next_button.setText(prior_button_text)
+ email = str(email_e.text())
+ self.create_remote_key(email, window)
+
+ def request_otp_dialog(self, window, short_id, otp_secret, xpub3):
+ vbox = QVBoxLayout()
+ if otp_secret is not None:
+ uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
+ l = QLabel("Please scan the following QR code in Google Authenticator. You may as well use the following key: %s"%otp_secret)
+ l.setWordWrap(True)
+ vbox.addWidget(l)
+ qrw = QRCodeWidget(uri)
+ vbox.addWidget(qrw, 1)
+ msg = _('Then, enter your Google Authenticator code:')
+ else:
+ label = QLabel(
+ "This wallet is already registered with TrustedCoin. "
+ "To finalize wallet creation, please enter your Google Authenticator Code. "
+ )
+ label.setWordWrap(1)
+ vbox.addWidget(label)
+ msg = _('Google Authenticator code:')
+ hbox = QHBoxLayout()
+ hbox.addWidget(WWLabel(msg))
+ pw = AmountEdit(None, is_int = True)
+ pw.setFocus(True)
+ pw.setMaximumWidth(50)
+ hbox.addWidget(pw)
+ vbox.addLayout(hbox)
+ cb_lost = QCheckBox(_("I have lost my Google Authenticator account"))
+ cb_lost.setToolTip(_("Check this box to request a new secret. You will need to retype your seed."))
+ vbox.addWidget(cb_lost)
+ cb_lost.setVisible(otp_secret is None)
+ def set_enabled():
+ b = True if cb_lost.isChecked() else len(pw.text()) == 6
+ window.next_button.setEnabled(b)
+ pw.textChanged.connect(set_enabled)
+ cb_lost.toggled.connect(set_enabled)
+ window.exec_layout(vbox, next_enabled=False, raise_on_cancel=False)
+ self.check_otp(window, short_id, otp_secret, xpub3, pw.get_amount(), cb_lost.isChecked())
diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py
new file mode 100644
index 000000000..ccd24e332
--- /dev/null
+++ b/electrum/plugins/trustedcoin/trustedcoin.py
@@ -0,0 +1,673 @@
+#!/usr/bin/env python
+#
+# Electrum - Lightweight Bitcoin Client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import socket
+import os
+import requests
+import json
+import base64
+from urllib.parse import urljoin
+from urllib.parse import quote
+
+from electrum import bitcoin, ecc, constants, keystore, version
+from electrum.bitcoin import *
+from electrum.transaction import TxOutput
+from electrum.mnemonic import Mnemonic
+from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
+from electrum.i18n import _
+from electrum.plugin import BasePlugin, hook
+from electrum.util import NotEnoughFunds
+from electrum.storage import STO_EV_USER_PW
+
+# signing_xpub is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server
+def get_signing_xpub():
+ if constants.net.TESTNET:
+ return "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY"
+ else:
+ return "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
+
+def get_billing_xpub():
+ if constants.net.TESTNET:
+ return "tpubD6NzVbkrYhZ4X11EJFTJujsYbUmVASAYY7gXsEt4sL97AMBdypiH1E9ZVTpdXXEy3Kj9Eqd1UkxdGtvDt5z23DKsh6211CfNJo8bLLyem5r"
+ else:
+ return "xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU"
+
+SEED_PREFIX = version.SEED_PREFIX_2FA
+
+DISCLAIMER = [
+ _("Two-factor authentication is a service provided by TrustedCoin. "
+ "It uses a multi-signature wallet, where you own 2 of 3 keys. "
+ "The third key is stored on a remote server that signs transactions on "
+ "your behalf. To use this service, you will need a smartphone with "
+ "Google Authenticator installed."),
+ _("A small fee will be charged on each transaction that uses the "
+ "remote server. You may check and modify your billing preferences "
+ "once the installation is complete."),
+ _("Note that your coins are not locked in this service. You may withdraw "
+ "your funds at any time and at no cost, without the remote server, by "
+ "using the 'restore wallet' option with your wallet seed."),
+ _("The next step will generate the seed of your wallet. This seed will "
+ "NOT be saved in your computer, and it must be stored on paper. "
+ "To be safe from malware, you may want to do this on an offline "
+ "computer, and move your wallet later to an online computer."),
+]
+
+KIVY_DISCLAIMER = [
+ _("Two-factor authentication is a service provided by TrustedCoin. "
+ "To use it, you must have a separate device with Google Authenticator."),
+ _("This service uses a multi-signature wallet, where you own 2 of 3 keys. "
+ "The third key is stored on a remote server that signs transactions on "
+ "your behalf. A small fee will be charged on each transaction that uses the "
+ "remote server."),
+ _("Note that your coins are not locked in this service. You may withdraw "
+ "your funds at any time and at no cost, without the remote server, by "
+ "using the 'restore wallet' option with your wallet seed."),
+]
+RESTORE_MSG = _("Enter the seed for your 2-factor wallet:")
+
+class TrustedCoinException(Exception):
+ def __init__(self, message, status_code=0):
+ Exception.__init__(self, message)
+ self.status_code = status_code
+
+
+class ErrorConnectingServer(Exception):
+ pass
+
+
+class TrustedCoinCosignerClient(object):
+ def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/'):
+ self.base_url = base_url
+ self.debug = False
+ self.user_agent = user_agent
+
+ def send_request(self, method, relative_url, data=None):
+ kwargs = {'headers': {}}
+ if self.user_agent:
+ kwargs['headers']['user-agent'] = self.user_agent
+ if method == 'get' and data:
+ kwargs['params'] = data
+ elif method == 'post' and data:
+ kwargs['data'] = json.dumps(data)
+ kwargs['headers']['content-type'] = 'application/json'
+ url = urljoin(self.base_url, relative_url)
+ if self.debug:
+ print('%s %s %s' % (method, url, data))
+ try:
+ response = requests.request(method, url, **kwargs)
+ except Exception as e:
+ raise ErrorConnectingServer(e)
+ if self.debug:
+ print(response.text)
+ if response.status_code != 200:
+ message = str(response.text)
+ if response.headers.get('content-type') == 'application/json':
+ r = response.json()
+ if 'message' in r:
+ message = r['message']
+ raise TrustedCoinException(message, response.status_code)
+ if response.headers.get('content-type') == 'application/json':
+ return response.json()
+ else:
+ return response.text
+
+ def get_terms_of_service(self, billing_plan='electrum-per-tx-otp'):
+ """
+ Returns the TOS for the given billing plan as a plain/text unicode string.
+ :param billing_plan: the plan to return the terms for
+ """
+ payload = {'billing_plan': billing_plan}
+ return self.send_request('get', 'tos', payload)
+
+ def create(self, xpubkey1, xpubkey2, email, billing_plan='electrum-per-tx-otp'):
+ """
+ Creates a new cosigner resource.
+ :param xpubkey1: a bip32 extended public key (customarily the hot key)
+ :param xpubkey2: a bip32 extended public key (customarily the cold key)
+ :param email: a contact email
+ :param billing_plan: the billing plan for the cosigner
+ """
+ payload = {
+ 'email': email,
+ 'xpubkey1': xpubkey1,
+ 'xpubkey2': xpubkey2,
+ 'billing_plan': billing_plan,
+ }
+ return self.send_request('post', 'cosigner', payload)
+
+ def auth(self, id, otp):
+ """
+ Attempt to authenticate for a particular cosigner.
+ :param id: the id of the cosigner
+ :param otp: the one time password
+ """
+ payload = {'otp': otp}
+ return self.send_request('post', 'cosigner/%s/auth' % quote(id), payload)
+
+ def get(self, id):
+ """ Get billing info """
+ return self.send_request('get', 'cosigner/%s' % quote(id))
+
+ def get_challenge(self, id):
+ """ Get challenge to reset Google Auth secret """
+ return self.send_request('get', 'cosigner/%s/otp_secret' % quote(id))
+
+ def reset_auth(self, id, challenge, signatures):
+ """ Reset Google Auth secret """
+ payload = {'challenge':challenge, 'signatures':signatures}
+ return self.send_request('post', 'cosigner/%s/otp_secret' % quote(id), payload)
+
+ def sign(self, id, transaction, otp):
+ """
+ Attempt to authenticate for a particular cosigner.
+ :param id: the id of the cosigner
+ :param transaction: the hex encoded [partially signed] compact transaction to sign
+ :param otp: the one time password
+ """
+ payload = {
+ 'otp': otp,
+ 'transaction': transaction
+ }
+ return self.send_request('post', 'cosigner/%s/sign' % quote(id), payload)
+
+ def transfer_credit(self, id, recipient, otp, signature_callback):
+ """
+ Transfer a cosigner's credits to another cosigner.
+ :param id: the id of the sending cosigner
+ :param recipient: the id of the recipient cosigner
+ :param otp: the one time password (of the sender)
+ :param signature_callback: a callback that signs a text message using xpubkey1/0/0 returning a compact sig
+ """
+ payload = {
+ 'otp': otp,
+ 'recipient': recipient,
+ 'timestamp': int(time.time()),
+
+ }
+ relative_url = 'cosigner/%s/transfer' % quote(id)
+ full_url = urljoin(self.base_url, relative_url)
+ headers = {
+ 'x-signature': signature_callback(full_url + '\n' + json.dumps(payload))
+ }
+ return self.send_request('post', relative_url, payload, headers)
+
+
+server = TrustedCoinCosignerClient(user_agent="Electrum/" + version.ELECTRUM_VERSION)
+
+class Wallet_2fa(Multisig_Wallet):
+
+ wallet_type = '2fa'
+
+ def __init__(self, storage):
+ self.m, self.n = 2, 3
+ Deterministic_Wallet.__init__(self, storage)
+ self.is_billing = False
+ self.billing_info = None
+ self._load_billing_addresses()
+
+ def _load_billing_addresses(self):
+ billing_addresses = self.storage.get('trustedcoin_billing_addresses', {})
+ self._billing_addresses = {} # index -> addr
+ # convert keys from str to int
+ for index, addr in list(billing_addresses.items()):
+ self._billing_addresses[int(index)] = addr
+ self._billing_addresses_set = set(self._billing_addresses.values()) # set of addrs
+
+ def can_sign_without_server(self):
+ return not self.keystores['x2/'].is_watching_only()
+
+ def get_user_id(self):
+ return get_user_id(self.storage)
+
+ def min_prepay(self):
+ return min(self.price_per_tx.keys())
+
+ def num_prepay(self, config):
+ default = self.min_prepay()
+ n = config.get('trustedcoin_prepay', default)
+ if n not in self.price_per_tx:
+ n = default
+ return n
+
+ def extra_fee(self, config):
+ if self.can_sign_without_server():
+ return 0
+ if self.billing_info is None:
+ self.plugin.start_request_thread(self)
+ return 0
+ if self.billing_info.get('tx_remaining'):
+ return 0
+ if self.is_billing:
+ return 0
+ n = self.num_prepay(config)
+ price = int(self.price_per_tx[n])
+ if price > 100000 * n:
+ raise Exception('too high trustedcoin fee ({} for {} txns)'.format(price, n))
+ return price
+
+ def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
+ change_addr=None, is_sweep=False):
+ mk_tx = lambda o: Multisig_Wallet.make_unsigned_transaction(
+ self, coins, o, config, fixed_fee, change_addr)
+ fee = self.extra_fee(config) if not is_sweep else 0
+ if fee:
+ address = self.billing_info['billing_address']
+ fee_output = TxOutput(TYPE_ADDRESS, address, fee)
+ try:
+ tx = mk_tx(outputs + [fee_output])
+ except NotEnoughFunds:
+ # TrustedCoin won't charge if the total inputs is
+ # lower than their fee
+ tx = mk_tx(outputs)
+ if tx.input_value() >= fee:
+ raise
+ self.print_error("not charging for this tx")
+ else:
+ tx = mk_tx(outputs)
+ return tx
+
+ def on_otp(self, tx, otp):
+ if not otp:
+ self.print_error("sign_transaction: no auth code")
+ return
+ otp = int(otp)
+ long_user_id, short_id = self.get_user_id()
+ raw_tx = tx.serialize_to_network()
+ r = server.sign(short_id, raw_tx, otp)
+ if r:
+ raw_tx = r.get('transaction')
+ tx.update(raw_tx)
+ self.print_error("twofactor: is complete", tx.is_complete())
+ # reset billing_info
+ self.billing_info = None
+ self.plugin.start_request_thread(self)
+
+ def add_new_billing_address(self, billing_index: int, address: str):
+ saved_addr = self._billing_addresses.get(billing_index)
+ if saved_addr is not None:
+ if saved_addr == address:
+ return # already saved this address
+ else:
+ raise Exception('trustedcoin billing address inconsistency.. '
+ 'for index {}, already saved {}, now got {}'
+ .format(billing_index, saved_addr, address))
+ # do we have all prior indices? (are we synced?)
+ largest_index_we_have = max(self._billing_addresses) if self._billing_addresses else -1
+ if largest_index_we_have + 1 < billing_index: # need to sync
+ for i in range(largest_index_we_have + 1, billing_index):
+ addr = make_billing_address(self, i)
+ self._billing_addresses[i] = addr
+ self._billing_addresses_set.add(addr)
+ # save this address; and persist to disk
+ self._billing_addresses[billing_index] = address
+ self._billing_addresses_set.add(address)
+ self.storage.put('trustedcoin_billing_addresses', self._billing_addresses)
+ # FIXME this often runs in a daemon thread, where storage.write will fail
+ self.storage.write()
+
+ def is_billing_address(self, addr: str) -> bool:
+ return addr in self._billing_addresses_set
+
+
+# Utility functions
+
+def get_user_id(storage):
+ def make_long_id(xpub_hot, xpub_cold):
+ return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
+ xpub1 = storage.get('x1/')['xpub']
+ xpub2 = storage.get('x2/')['xpub']
+ long_id = make_long_id(xpub1, xpub2)
+ short_id = hashlib.sha256(long_id).hexdigest()
+ return long_id, short_id
+
+def make_xpub(xpub, s):
+ version, _, _, _, c, cK = deserialize_xpub(xpub)
+ cK2, c2 = bitcoin._CKD_pub(cK, c, s)
+ return bitcoin.serialize_xpub(version, c2, cK2)
+
+def make_billing_address(wallet, num):
+ long_id, short_id = wallet.get_user_id()
+ xpub = make_xpub(get_billing_xpub(), long_id)
+ version, _, _, _, c, cK = deserialize_xpub(xpub)
+ cK, c = bitcoin.CKD_pub(cK, c, num)
+ return bitcoin.public_key_to_p2pkh(cK)
+
+
+class TrustedCoinPlugin(BasePlugin):
+ wallet_class = Wallet_2fa
+ disclaimer_msg = DISCLAIMER
+
+ def __init__(self, parent, config, name):
+ BasePlugin.__init__(self, parent, config, name)
+ self.wallet_class.plugin = self
+ self.requesting = False
+
+ @staticmethod
+ def is_valid_seed(seed):
+ return bitcoin.is_new_seed(seed, SEED_PREFIX)
+
+ def is_available(self):
+ return True
+
+ def is_enabled(self):
+ return True
+
+ def can_user_disable(self):
+ return False
+
+ @hook
+ def tc_sign_wrapper(self, wallet, tx, on_success, on_failure):
+ if not isinstance(wallet, self.wallet_class):
+ return
+ if tx.is_complete():
+ return
+ if wallet.can_sign_without_server():
+ return
+ if not wallet.keystores['x3/'].get_tx_derivations(tx):
+ self.print_error("twofactor: xpub3 not needed")
+ return
+ def wrapper(tx):
+ self.prompt_user_for_otp(wallet, tx, on_success, on_failure)
+ return wrapper
+
+ @hook
+ def get_tx_extra_fee(self, wallet, tx):
+ if type(wallet) != Wallet_2fa:
+ return
+ for o in tx.outputs():
+ if o.type == TYPE_ADDRESS and wallet.is_billing_address(o.address):
+ return o.address, o.value
+
+ def finish_requesting(func):
+ def f(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ self.requesting = False
+ return f
+
+ @finish_requesting
+ def request_billing_info(self, wallet):
+ if wallet.can_sign_without_server():
+ return
+ self.print_error("request billing info")
+ try:
+ billing_info = server.get(wallet.get_user_id()[1])
+ except ErrorConnectingServer as e:
+ self.print_error('cannot connect to TrustedCoin server: {}'.format(e))
+ return
+ billing_index = billing_info['billing_index']
+ billing_address = make_billing_address(wallet, billing_index)
+ if billing_address != billing_info['billing_address']:
+ raise Exception('unexpected trustedcoin billing address: expected {}, received {}'
+ .format(billing_address, billing_info['billing_address']))
+ wallet.add_new_billing_address(billing_index, billing_address)
+ wallet.billing_info = billing_info
+ wallet.price_per_tx = dict(billing_info['price_per_tx'])
+ wallet.price_per_tx.pop(1, None)
+ return True
+
+ def start_request_thread(self, wallet):
+ from threading import Thread
+ if self.requesting is False:
+ self.requesting = True
+ t = Thread(target=self.request_billing_info, args=(wallet,))
+ t.setDaemon(True)
+ t.start()
+ return t
+
+ def make_seed(self):
+ return Mnemonic('english').make_seed(seed_type='2fa', num_bits=128)
+
+ @hook
+ def do_clear(self, window):
+ window.wallet.is_billing = False
+
+ def show_disclaimer(self, wizard):
+ wizard.set_icon(':icons/trustedcoin-wizard.png')
+ wizard.stack = []
+ wizard.confirm_dialog(title='Disclaimer', message='\n\n'.join(self.disclaimer_msg), run_next = lambda x: wizard.run('choose_seed'))
+
+ def choose_seed(self, wizard):
+ title = _('Create or restore')
+ message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
+ choices = [
+ ('create_seed', _('Create a new seed')),
+ ('restore_wallet', _('I already have a seed')),
+ ]
+ wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)
+
+ def create_seed(self, wizard):
+ seed = self.make_seed()
+ f = lambda x: wizard.request_passphrase(seed, x)
+ wizard.show_seed_dialog(run_next=f, seed_text=seed)
+
+ @classmethod
+ def get_xkeys(self, seed, passphrase, derivation):
+ from electrum.mnemonic import Mnemonic
+ from electrum.keystore import bip32_root, bip32_private_derivation
+ bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
+ xprv, xpub = bip32_root(bip32_seed, 'standard')
+ xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
+ return xprv, xpub
+
+ @classmethod
+ def xkeys_from_seed(self, seed, passphrase):
+ words = seed.split()
+ n = len(words)
+ # old version use long seed phrases
+ if n >= 20:
+ # note: pre-2.7 2fa seeds were typically 24-25 words, however they
+ # could probabilistically be arbitrarily shorter due to a bug. (see #3611)
+ # the probability of it being < 20 words is about 2^(-(256+12-19*11)) = 2^(-59)
+ if passphrase != '':
+ raise Exception('old 2fa seed cannot have passphrase')
+ xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', "m/")
+ xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), '', "m/")
+ elif n==12:
+ xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/")
+ xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/")
+ else:
+ raise Exception('unrecognized seed length: {} words'.format(n))
+ return xprv1, xpub1, xprv2, xpub2
+
+ def create_keystore(self, wizard, seed, passphrase):
+ # this overloads the wizard's method
+ xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
+ k1 = keystore.from_xprv(xprv1)
+ k2 = keystore.from_xpub(xpub2)
+ wizard.request_password(run_next=lambda pw, encrypt: self.on_password(wizard, pw, encrypt, k1, k2))
+
+ def on_password(self, wizard, password, encrypt_storage, k1, k2):
+ k1.update_password(None, password)
+ wizard.storage.set_keystore_encryption(bool(password))
+ if encrypt_storage:
+ wizard.storage.set_password(password, enc_version=STO_EV_USER_PW)
+ wizard.storage.put('x1/', k1.dump())
+ wizard.storage.put('x2/', k2.dump())
+ wizard.storage.write()
+ self.go_online_dialog(wizard)
+
+ def restore_wallet(self, wizard):
+ wizard.opt_bip39 = False
+ wizard.opt_ext = True
+ title = _("Restore two-factor Wallet")
+ f = lambda seed, is_bip39, is_ext: wizard.run('on_restore_seed', seed, is_ext)
+ wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)
+
+ def on_restore_seed(self, wizard, seed, is_ext):
+ f = lambda x: self.restore_choice(wizard, seed, x)
+ wizard.passphrase_dialog(run_next=f) if is_ext else f('')
+
+ def restore_choice(self, wizard, seed, passphrase):
+ wizard.set_icon(':icons/trustedcoin-wizard.png')
+ wizard.stack = []
+ title = _('Restore 2FA wallet')
+ msg = ' '.join([
+ 'You are going to restore a wallet protected with two-factor authentication.',
+ 'Do you want to keep using two-factor authentication with this wallet,',
+ 'or do you want to disable it, and have two master private keys in your wallet?'
+ ])
+ choices = [('keep', 'Keep'), ('disable', 'Disable')]
+ f = lambda x: self.on_choice(wizard, seed, passphrase, x)
+ wizard.choice_dialog(choices=choices, message=msg, title=title, run_next=f)
+
+ def on_choice(self, wizard, seed, passphrase, x):
+ if x == 'disable':
+ f = lambda pw, encrypt: wizard.run('on_restore_pw', seed, passphrase, pw, encrypt)
+ wizard.request_password(run_next=f)
+ else:
+ self.create_keystore(wizard, seed, passphrase)
+
+ def on_restore_pw(self, wizard, seed, passphrase, password, encrypt_storage):
+ storage = wizard.storage
+ xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
+ k1 = keystore.from_xprv(xprv1)
+ k2 = keystore.from_xprv(xprv2)
+ k1.add_seed(seed)
+ k1.update_password(None, password)
+ k2.update_password(None, password)
+ storage.put('x1/', k1.dump())
+ storage.put('x2/', k2.dump())
+ long_user_id, short_id = get_user_id(storage)
+ xpub3 = make_xpub(get_signing_xpub(), long_user_id)
+ k3 = keystore.from_xpub(xpub3)
+ storage.put('x3/', k3.dump())
+
+ storage.set_keystore_encryption(bool(password))
+ if encrypt_storage:
+ storage.set_password(password, enc_version=STO_EV_USER_PW)
+
+ wizard.wallet = Wallet_2fa(storage)
+ wizard.create_addresses()
+
+
+ def create_remote_key(self, email, wizard):
+ xpub1 = wizard.storage.get('x1/')['xpub']
+ xpub2 = wizard.storage.get('x2/')['xpub']
+ # Generate third key deterministically.
+ long_user_id, short_id = get_user_id(wizard.storage)
+ xpub3 = make_xpub(get_signing_xpub(), long_user_id)
+ # secret must be sent by the server
+ try:
+ r = server.create(xpub1, xpub2, email)
+ except (socket.error, ErrorConnectingServer):
+ wizard.show_message('Server not reachable, aborting')
+ wizard.terminate()
+ return
+ except TrustedCoinException as e:
+ if e.status_code == 409:
+ r = None
+ else:
+ wizard.show_message(str(e))
+ return
+ if r is None:
+ otp_secret = None
+ else:
+ otp_secret = r.get('otp_secret')
+ if not otp_secret:
+ wizard.show_message(_('Error'))
+ return
+ _xpub3 = r['xpubkey_cosigner']
+ _id = r['id']
+ if short_id != _id:
+ wizard.show_message("unexpected trustedcoin short_id: expected {}, received {}"
+ .format(short_id, _id))
+ return
+ if xpub3 != _xpub3:
+ wizard.show_message("unexpected trustedcoin xpub3: expected {}, received {}"
+ .format(xpub3, _xpub3))
+ return
+ self.request_otp_dialog(wizard, short_id, otp_secret, xpub3)
+
+ def check_otp(self, wizard, short_id, otp_secret, xpub3, otp, reset):
+ if otp:
+ self.do_auth(wizard, short_id, otp, xpub3)
+ elif reset:
+ wizard.opt_bip39 = False
+ wizard.opt_ext = True
+ f = lambda seed, is_bip39, is_ext: wizard.run('on_reset_seed', short_id, seed, is_ext, xpub3)
+ wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)
+
+ def on_reset_seed(self, wizard, short_id, seed, is_ext, xpub3):
+ f = lambda passphrase: wizard.run('on_reset_auth', short_id, seed, passphrase, xpub3)
+ wizard.passphrase_dialog(run_next=f) if is_ext else f('')
+
+ def do_auth(self, wizard, short_id, otp, xpub3):
+ try:
+ server.auth(short_id, otp)
+ except TrustedCoinException as e:
+ if e.status_code == 400: # invalid OTP
+ wizard.show_message(_('Invalid one-time password.'))
+ # ask again for otp
+ self.request_otp_dialog(wizard, short_id, None, xpub3)
+ else:
+ wizard.show_message(str(e))
+ wizard.terminate()
+ except Exception as e:
+ wizard.show_message(str(e))
+ wizard.terminate()
+ else:
+ k3 = keystore.from_xpub(xpub3)
+ wizard.storage.put('x3/', k3.dump())
+ wizard.storage.put('use_trustedcoin', True)
+ wizard.storage.write()
+ wizard.wallet = Wallet_2fa(wizard.storage)
+ wizard.run('create_addresses')
+
+ def on_reset_auth(self, wizard, short_id, seed, passphrase, xpub3):
+ xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
+ if (wizard.storage.get('x1/')['xpub'] != xpub1 or
+ wizard.storage.get('x2/')['xpub'] != xpub2):
+ wizard.show_message(_('Incorrect seed'))
+ return
+ r = server.get_challenge(short_id)
+ challenge = r.get('challenge')
+ message = 'TRUSTEDCOIN CHALLENGE: ' + challenge
+ def f(xprv):
+ _, _, _, _, c, k = deserialize_xprv(xprv)
+ pk = bip32_private_key([0, 0], k, c)
+ key = ecc.ECPrivkey(pk)
+ sig = key.sign_message(message, True)
+ return base64.b64encode(sig).decode()
+
+ signatures = [f(x) for x in [xprv1, xprv2]]
+ r = server.reset_auth(short_id, challenge, signatures)
+ new_secret = r.get('otp_secret')
+ if not new_secret:
+ wizard.show_message(_('Request rejected by server'))
+ return
+ self.request_otp_dialog(wizard, short_id, new_secret, xpub3)
+
+ @hook
+ def get_action(self, storage):
+ if storage.get('wallet_type') != '2fa':
+ return
+ if not storage.get('x1/'):
+ return self, 'show_disclaimer'
+ if not storage.get('x2/'):
+ return self, 'show_disclaimer'
+ if not storage.get('x3/'):
+ return self, 'accept_terms_of_use'
diff --git a/electrum/plugins/virtualkeyboard/__init__.py b/electrum/plugins/virtualkeyboard/__init__.py
new file mode 100644
index 000000000..c24f19b1a
--- /dev/null
+++ b/electrum/plugins/virtualkeyboard/__init__.py
@@ -0,0 +1,5 @@
+from electrum.i18n import _
+
+fullname = 'Virtual Keyboard'
+description = '%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."))
+available_for = ['qt']
diff --git a/electrum/plugins/virtualkeyboard/qt.py b/electrum/plugins/virtualkeyboard/qt.py
new file mode 100644
index 000000000..3ce78af33
--- /dev/null
+++ b/electrum/plugins/virtualkeyboard/qt.py
@@ -0,0 +1,61 @@
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import (QVBoxLayout, QGridLayout, QPushButton)
+from electrum.plugin import BasePlugin, hook
+from electrum.i18n import _
+import random
+
+
+class Plugin(BasePlugin):
+ vkb = None
+ vkb_index = 0
+
+ @hook
+ def password_dialog(self, pw, grid, pos):
+ vkb_button = QPushButton(_("+"))
+ vkb_button.setFixedWidth(20)
+ vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw))
+ grid.addWidget(vkb_button, pos, 2)
+ self.kb_pos = 2
+ self.vkb = None
+
+ def toggle_vkb(self, grid, pw):
+ if self.vkb:
+ grid.removeItem(self.vkb)
+ self.vkb = self.virtual_keyboard(self.vkb_index, pw)
+ grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3)
+ self.vkb_index += 1
+
+ def virtual_keyboard(self, i, pw):
+ i = i % 3
+ if i == 0:
+ chars = 'abcdefghijklmnopqrstuvwxyz '
+ elif i == 1:
+ chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ '
+ elif i == 2:
+ chars = '1234567890!?.,;:/%&()[]{}+-'
+
+ n = len(chars)
+ s = []
+ for i in range(n):
+ while True:
+ k = random.randint(0, n - 1)
+ if k not in s:
+ s.append(k)
+ break
+
+ def add_target(t):
+ return lambda: pw.setText(str(pw.text()) + t)
+
+ vbox = QVBoxLayout()
+ grid = QGridLayout()
+ grid.setSpacing(2)
+ for i in range(n):
+ l_button = QPushButton(chars[s[i]])
+ l_button.setFixedWidth(25)
+ l_button.setFixedHeight(25)
+ l_button.clicked.connect(add_target(chars[s[i]]))
+ grid.addWidget(l_button, i // 6, i % 6)
+
+ vbox.addLayout(grid)
+
+ return vbox
diff --git a/electrum/qrscanner.py b/electrum/qrscanner.py
new file mode 100644
index 000000000..463d5ec6f
--- /dev/null
+++ b/electrum/qrscanner.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import sys
+import ctypes
+
+if sys.platform == 'darwin':
+ name = 'libzbar.dylib'
+elif sys.platform in ('windows', 'win32'):
+ name = 'libzbar-0.dll'
+else:
+ name = 'libzbar.so.0'
+
+try:
+ libzbar = ctypes.cdll.LoadLibrary(name)
+except BaseException:
+ libzbar = None
+
+
+def scan_barcode(device='', timeout=-1, display=True, threaded=False, try_again=True):
+ if libzbar is None:
+ raise RuntimeError("Cannot start QR scanner; zbar not available.")
+ libzbar.zbar_symbol_get_data.restype = ctypes.c_char_p
+ libzbar.zbar_processor_create.restype = ctypes.POINTER(ctypes.c_int)
+ libzbar.zbar_processor_get_results.restype = ctypes.POINTER(ctypes.c_int)
+ libzbar.zbar_symbol_set_first_symbol.restype = ctypes.POINTER(ctypes.c_int)
+ proc = libzbar.zbar_processor_create(threaded)
+ libzbar.zbar_processor_request_size(proc, 640, 480)
+ if libzbar.zbar_processor_init(proc, device.encode('utf-8'), display) != 0:
+ if try_again:
+ # workaround for a bug in "ZBar for Windows"
+ # libzbar.zbar_processor_init always seem to fail the first time around
+ return scan_barcode(device, timeout, display, threaded, try_again=False)
+ raise RuntimeError("Can not start QR scanner; initialization failed.")
+ libzbar.zbar_processor_set_visible(proc)
+ if libzbar.zbar_process_one(proc, timeout):
+ symbols = libzbar.zbar_processor_get_results(proc)
+ else:
+ symbols = None
+ libzbar.zbar_processor_destroy(proc)
+ if symbols is None:
+ return
+ if not libzbar.zbar_symbol_set_get_size(symbols):
+ return
+ symbol = libzbar.zbar_symbol_set_first_symbol(symbols)
+ data = libzbar.zbar_symbol_get_data(symbol)
+ return data.decode('utf8')
+
+def _find_system_cameras():
+ device_root = "/sys/class/video4linux"
+ devices = {} # Name -> device
+ if os.path.exists(device_root):
+ for device in os.listdir(device_root):
+ path = os.path.join(device_root, device, 'name')
+ try:
+ with open(path, encoding='utf-8') as f:
+ name = f.read()
+ except Exception:
+ continue
+ name = name.strip('\n')
+ devices[name] = os.path.join("/dev", device)
+ return devices
+
+
+if __name__ == "__main__":
+ print(scan_barcode())
diff --git a/electrum/ripemd.py b/electrum/ripemd.py
new file mode 100644
index 000000000..92305d747
--- /dev/null
+++ b/electrum/ripemd.py
@@ -0,0 +1,393 @@
+## ripemd.py - pure Python implementation of the RIPEMD-160 algorithm.
+## Bjorn Edstrom 16 december 2007.
+##
+## Copyrights
+## ==========
+##
+## This code is a derived from an implementation by Markus Friedl which is
+## subject to the following license. This Python implementation is not
+## subject to any other license.
+##
+##/*
+## * Copyright (c) 2001 Markus Friedl. All rights reserved.
+## *
+## * Redistribution and use in source and binary forms, with or without
+## * modification, are permitted provided that the following conditions
+## * are met:
+## * 1. Redistributions of source code must retain the above copyright
+## * notice, this list of conditions and the following disclaimer.
+## * 2. Redistributions in binary form must reproduce the above copyright
+## * notice, this list of conditions and the following disclaimer in the
+## * documentation and/or other materials provided with the distribution.
+## *
+## * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+## */
+##/*
+## * Preneel, Bosselaers, Dobbertin, "The Cryptographic Hash Function RIPEMD-160",
+## * RSA Laboratories, CryptoBytes, Volume 3, Number 2, Autumn 1997,
+## * ftp://ftp.rsasecurity.com/pub/cryptobytes/crypto3n2.pdf
+## */
+
+#block_size = 1
+digest_size = 20
+digestsize = 20
+
+class RIPEMD160:
+ """Return a new RIPEMD160 object. An optional string argument
+ may be provided; if present, this string will be automatically
+ hashed."""
+
+ def __init__(self, arg=None):
+ self.ctx = RMDContext()
+ if arg:
+ self.update(arg)
+ self.dig = None
+
+ def update(self, arg):
+ """update(arg)"""
+ RMD160Update(self.ctx, arg, len(arg))
+ self.dig = None
+
+ def digest(self):
+ """digest()"""
+ if self.dig:
+ return self.dig
+ ctx = self.ctx.copy()
+ self.dig = RMD160Final(self.ctx)
+ self.ctx = ctx
+ return self.dig
+
+ def hexdigest(self):
+ """hexdigest()"""
+ dig = self.digest()
+ hex_digest = ''
+ for d in dig:
+ hex_digest += '%02x' % d
+ return hex_digest
+
+ def copy(self):
+ """copy()"""
+ import copy
+ return copy.deepcopy(self)
+
+
+
+def new(arg=None):
+ """Return a new RIPEMD160 object. An optional string argument
+ may be provided; if present, this string will be automatically
+ hashed."""
+ return RIPEMD160(arg)
+
+
+
+#
+# Private.
+#
+
+class RMDContext:
+ def __init__(self):
+ self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE,
+ 0x10325476, 0xC3D2E1F0] # uint32
+ self.count = 0 # uint64
+ self.buffer = [0]*64 # uchar
+ def copy(self):
+ ctx = RMDContext()
+ ctx.state = self.state[:]
+ ctx.count = self.count
+ ctx.buffer = self.buffer[:]
+ return ctx
+
+K0 = 0x00000000
+K1 = 0x5A827999
+K2 = 0x6ED9EBA1
+K3 = 0x8F1BBCDC
+K4 = 0xA953FD4E
+
+KK0 = 0x50A28BE6
+KK1 = 0x5C4DD124
+KK2 = 0x6D703EF3
+KK3 = 0x7A6D76E9
+KK4 = 0x00000000
+
+def ROL(n, x):
+ return ((x << n) & 0xffffffff) | (x >> (32 - n))
+
+def F0(x, y, z):
+ return x ^ y ^ z
+
+def F1(x, y, z):
+ return (x & y) | (((~x) % 0x100000000) & z)
+
+def F2(x, y, z):
+ return (x | ((~y) % 0x100000000)) ^ z
+
+def F3(x, y, z):
+ return (x & z) | (((~z) % 0x100000000) & y)
+
+def F4(x, y, z):
+ return x ^ (y | ((~z) % 0x100000000))
+
+def R(a, b, c, d, e, Fj, Kj, sj, rj, X):
+ a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e
+ c = ROL(10, c)
+ return a % 0x100000000, c
+
+PADDING = [0x80] + [0]*63
+
+import sys
+import struct
+
+def RMD160Transform(state, block): #uint32 state[5], uchar block[64]
+ x = [0]*16
+ if sys.byteorder == 'little':
+ x = struct.unpack('<16L', bytes([x for x in block[0:64]]))
+ else:
+ raise "Error!!"
+ a = state[0]
+ b = state[1]
+ c = state[2]
+ d = state[3]
+ e = state[4]
+
+ #/* Round 1 */
+ a, c = R(a, b, c, d, e, F0, K0, 11, 0, x);
+ e, b = R(e, a, b, c, d, F0, K0, 14, 1, x);
+ d, a = R(d, e, a, b, c, F0, K0, 15, 2, x);
+ c, e = R(c, d, e, a, b, F0, K0, 12, 3, x);
+ b, d = R(b, c, d, e, a, F0, K0, 5, 4, x);
+ a, c = R(a, b, c, d, e, F0, K0, 8, 5, x);
+ e, b = R(e, a, b, c, d, F0, K0, 7, 6, x);
+ d, a = R(d, e, a, b, c, F0, K0, 9, 7, x);
+ c, e = R(c, d, e, a, b, F0, K0, 11, 8, x);
+ b, d = R(b, c, d, e, a, F0, K0, 13, 9, x);
+ a, c = R(a, b, c, d, e, F0, K0, 14, 10, x);
+ e, b = R(e, a, b, c, d, F0, K0, 15, 11, x);
+ d, a = R(d, e, a, b, c, F0, K0, 6, 12, x);
+ c, e = R(c, d, e, a, b, F0, K0, 7, 13, x);
+ b, d = R(b, c, d, e, a, F0, K0, 9, 14, x);
+ a, c = R(a, b, c, d, e, F0, K0, 8, 15, x); #/* #15 */
+ #/* Round 2 */
+ e, b = R(e, a, b, c, d, F1, K1, 7, 7, x);
+ d, a = R(d, e, a, b, c, F1, K1, 6, 4, x);
+ c, e = R(c, d, e, a, b, F1, K1, 8, 13, x);
+ b, d = R(b, c, d, e, a, F1, K1, 13, 1, x);
+ a, c = R(a, b, c, d, e, F1, K1, 11, 10, x);
+ e, b = R(e, a, b, c, d, F1, K1, 9, 6, x);
+ d, a = R(d, e, a, b, c, F1, K1, 7, 15, x);
+ c, e = R(c, d, e, a, b, F1, K1, 15, 3, x);
+ b, d = R(b, c, d, e, a, F1, K1, 7, 12, x);
+ a, c = R(a, b, c, d, e, F1, K1, 12, 0, x);
+ e, b = R(e, a, b, c, d, F1, K1, 15, 9, x);
+ d, a = R(d, e, a, b, c, F1, K1, 9, 5, x);
+ c, e = R(c, d, e, a, b, F1, K1, 11, 2, x);
+ b, d = R(b, c, d, e, a, F1, K1, 7, 14, x);
+ a, c = R(a, b, c, d, e, F1, K1, 13, 11, x);
+ e, b = R(e, a, b, c, d, F1, K1, 12, 8, x); #/* #31 */
+ #/* Round 3 */
+ d, a = R(d, e, a, b, c, F2, K2, 11, 3, x);
+ c, e = R(c, d, e, a, b, F2, K2, 13, 10, x);
+ b, d = R(b, c, d, e, a, F2, K2, 6, 14, x);
+ a, c = R(a, b, c, d, e, F2, K2, 7, 4, x);
+ e, b = R(e, a, b, c, d, F2, K2, 14, 9, x);
+ d, a = R(d, e, a, b, c, F2, K2, 9, 15, x);
+ c, e = R(c, d, e, a, b, F2, K2, 13, 8, x);
+ b, d = R(b, c, d, e, a, F2, K2, 15, 1, x);
+ a, c = R(a, b, c, d, e, F2, K2, 14, 2, x);
+ e, b = R(e, a, b, c, d, F2, K2, 8, 7, x);
+ d, a = R(d, e, a, b, c, F2, K2, 13, 0, x);
+ c, e = R(c, d, e, a, b, F2, K2, 6, 6, x);
+ b, d = R(b, c, d, e, a, F2, K2, 5, 13, x);
+ a, c = R(a, b, c, d, e, F2, K2, 12, 11, x);
+ e, b = R(e, a, b, c, d, F2, K2, 7, 5, x);
+ d, a = R(d, e, a, b, c, F2, K2, 5, 12, x); #/* #47 */
+ #/* Round 4 */
+ c, e = R(c, d, e, a, b, F3, K3, 11, 1, x);
+ b, d = R(b, c, d, e, a, F3, K3, 12, 9, x);
+ a, c = R(a, b, c, d, e, F3, K3, 14, 11, x);
+ e, b = R(e, a, b, c, d, F3, K3, 15, 10, x);
+ d, a = R(d, e, a, b, c, F3, K3, 14, 0, x);
+ c, e = R(c, d, e, a, b, F3, K3, 15, 8, x);
+ b, d = R(b, c, d, e, a, F3, K3, 9, 12, x);
+ a, c = R(a, b, c, d, e, F3, K3, 8, 4, x);
+ e, b = R(e, a, b, c, d, F3, K3, 9, 13, x);
+ d, a = R(d, e, a, b, c, F3, K3, 14, 3, x);
+ c, e = R(c, d, e, a, b, F3, K3, 5, 7, x);
+ b, d = R(b, c, d, e, a, F3, K3, 6, 15, x);
+ a, c = R(a, b, c, d, e, F3, K3, 8, 14, x);
+ e, b = R(e, a, b, c, d, F3, K3, 6, 5, x);
+ d, a = R(d, e, a, b, c, F3, K3, 5, 6, x);
+ c, e = R(c, d, e, a, b, F3, K3, 12, 2, x); #/* #63 */
+ #/* Round 5 */
+ b, d = R(b, c, d, e, a, F4, K4, 9, 4, x);
+ a, c = R(a, b, c, d, e, F4, K4, 15, 0, x);
+ e, b = R(e, a, b, c, d, F4, K4, 5, 5, x);
+ d, a = R(d, e, a, b, c, F4, K4, 11, 9, x);
+ c, e = R(c, d, e, a, b, F4, K4, 6, 7, x);
+ b, d = R(b, c, d, e, a, F4, K4, 8, 12, x);
+ a, c = R(a, b, c, d, e, F4, K4, 13, 2, x);
+ e, b = R(e, a, b, c, d, F4, K4, 12, 10, x);
+ d, a = R(d, e, a, b, c, F4, K4, 5, 14, x);
+ c, e = R(c, d, e, a, b, F4, K4, 12, 1, x);
+ b, d = R(b, c, d, e, a, F4, K4, 13, 3, x);
+ a, c = R(a, b, c, d, e, F4, K4, 14, 8, x);
+ e, b = R(e, a, b, c, d, F4, K4, 11, 11, x);
+ d, a = R(d, e, a, b, c, F4, K4, 8, 6, x);
+ c, e = R(c, d, e, a, b, F4, K4, 5, 15, x);
+ b, d = R(b, c, d, e, a, F4, K4, 6, 13, x); #/* #79 */
+
+ aa = a;
+ bb = b;
+ cc = c;
+ dd = d;
+ ee = e;
+
+ a = state[0]
+ b = state[1]
+ c = state[2]
+ d = state[3]
+ e = state[4]
+
+ #/* Parallel round 1 */
+ a, c = R(a, b, c, d, e, F4, KK0, 8, 5, x)
+ e, b = R(e, a, b, c, d, F4, KK0, 9, 14, x)
+ d, a = R(d, e, a, b, c, F4, KK0, 9, 7, x)
+ c, e = R(c, d, e, a, b, F4, KK0, 11, 0, x)
+ b, d = R(b, c, d, e, a, F4, KK0, 13, 9, x)
+ a, c = R(a, b, c, d, e, F4, KK0, 15, 2, x)
+ e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x)
+ d, a = R(d, e, a, b, c, F4, KK0, 5, 4, x)
+ c, e = R(c, d, e, a, b, F4, KK0, 7, 13, x)
+ b, d = R(b, c, d, e, a, F4, KK0, 7, 6, x)
+ a, c = R(a, b, c, d, e, F4, KK0, 8, 15, x)
+ e, b = R(e, a, b, c, d, F4, KK0, 11, 8, x)
+ d, a = R(d, e, a, b, c, F4, KK0, 14, 1, x)
+ c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x)
+ b, d = R(b, c, d, e, a, F4, KK0, 12, 3, x)
+ a, c = R(a, b, c, d, e, F4, KK0, 6, 12, x) #/* #15 */
+ #/* Parallel round 2 */
+ e, b = R(e, a, b, c, d, F3, KK1, 9, 6, x)
+ d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x)
+ c, e = R(c, d, e, a, b, F3, KK1, 15, 3, x)
+ b, d = R(b, c, d, e, a, F3, KK1, 7, 7, x)
+ a, c = R(a, b, c, d, e, F3, KK1, 12, 0, x)
+ e, b = R(e, a, b, c, d, F3, KK1, 8, 13, x)
+ d, a = R(d, e, a, b, c, F3, KK1, 9, 5, x)
+ c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x)
+ b, d = R(b, c, d, e, a, F3, KK1, 7, 14, x)
+ a, c = R(a, b, c, d, e, F3, KK1, 7, 15, x)
+ e, b = R(e, a, b, c, d, F3, KK1, 12, 8, x)
+ d, a = R(d, e, a, b, c, F3, KK1, 7, 12, x)
+ c, e = R(c, d, e, a, b, F3, KK1, 6, 4, x)
+ b, d = R(b, c, d, e, a, F3, KK1, 15, 9, x)
+ a, c = R(a, b, c, d, e, F3, KK1, 13, 1, x)
+ e, b = R(e, a, b, c, d, F3, KK1, 11, 2, x) #/* #31 */
+ #/* Parallel round 3 */
+ d, a = R(d, e, a, b, c, F2, KK2, 9, 15, x)
+ c, e = R(c, d, e, a, b, F2, KK2, 7, 5, x)
+ b, d = R(b, c, d, e, a, F2, KK2, 15, 1, x)
+ a, c = R(a, b, c, d, e, F2, KK2, 11, 3, x)
+ e, b = R(e, a, b, c, d, F2, KK2, 8, 7, x)
+ d, a = R(d, e, a, b, c, F2, KK2, 6, 14, x)
+ c, e = R(c, d, e, a, b, F2, KK2, 6, 6, x)
+ b, d = R(b, c, d, e, a, F2, KK2, 14, 9, x)
+ a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x)
+ e, b = R(e, a, b, c, d, F2, KK2, 13, 8, x)
+ d, a = R(d, e, a, b, c, F2, KK2, 5, 12, x)
+ c, e = R(c, d, e, a, b, F2, KK2, 14, 2, x)
+ b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x)
+ a, c = R(a, b, c, d, e, F2, KK2, 13, 0, x)
+ e, b = R(e, a, b, c, d, F2, KK2, 7, 4, x)
+ d, a = R(d, e, a, b, c, F2, KK2, 5, 13, x) #/* #47 */
+ #/* Parallel round 4 */
+ c, e = R(c, d, e, a, b, F1, KK3, 15, 8, x)
+ b, d = R(b, c, d, e, a, F1, KK3, 5, 6, x)
+ a, c = R(a, b, c, d, e, F1, KK3, 8, 4, x)
+ e, b = R(e, a, b, c, d, F1, KK3, 11, 1, x)
+ d, a = R(d, e, a, b, c, F1, KK3, 14, 3, x)
+ c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x)
+ b, d = R(b, c, d, e, a, F1, KK3, 6, 15, x)
+ a, c = R(a, b, c, d, e, F1, KK3, 14, 0, x)
+ e, b = R(e, a, b, c, d, F1, KK3, 6, 5, x)
+ d, a = R(d, e, a, b, c, F1, KK3, 9, 12, x)
+ c, e = R(c, d, e, a, b, F1, KK3, 12, 2, x)
+ b, d = R(b, c, d, e, a, F1, KK3, 9, 13, x)
+ a, c = R(a, b, c, d, e, F1, KK3, 12, 9, x)
+ e, b = R(e, a, b, c, d, F1, KK3, 5, 7, x)
+ d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x)
+ c, e = R(c, d, e, a, b, F1, KK3, 8, 14, x) #/* #63 */
+ #/* Parallel round 5 */
+ b, d = R(b, c, d, e, a, F0, KK4, 8, 12, x)
+ a, c = R(a, b, c, d, e, F0, KK4, 5, 15, x)
+ e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x)
+ d, a = R(d, e, a, b, c, F0, KK4, 9, 4, x)
+ c, e = R(c, d, e, a, b, F0, KK4, 12, 1, x)
+ b, d = R(b, c, d, e, a, F0, KK4, 5, 5, x)
+ a, c = R(a, b, c, d, e, F0, KK4, 14, 8, x)
+ e, b = R(e, a, b, c, d, F0, KK4, 6, 7, x)
+ d, a = R(d, e, a, b, c, F0, KK4, 8, 6, x)
+ c, e = R(c, d, e, a, b, F0, KK4, 13, 2, x)
+ b, d = R(b, c, d, e, a, F0, KK4, 6, 13, x)
+ a, c = R(a, b, c, d, e, F0, KK4, 5, 14, x)
+ e, b = R(e, a, b, c, d, F0, KK4, 15, 0, x)
+ d, a = R(d, e, a, b, c, F0, KK4, 13, 3, x)
+ c, e = R(c, d, e, a, b, F0, KK4, 11, 9, x)
+ b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */
+
+ t = (state[1] + cc + d) % 0x100000000;
+ state[1] = (state[2] + dd + e) % 0x100000000;
+ state[2] = (state[3] + ee + a) % 0x100000000;
+ state[3] = (state[4] + aa + b) % 0x100000000;
+ state[4] = (state[0] + bb + c) % 0x100000000;
+ state[0] = t % 0x100000000;
+
+ pass
+
+
+def RMD160Update(ctx, inp, inplen):
+ if type(inp) == str:
+ inp = [ord(i)&0xff for i in inp]
+
+ have = (ctx.count // 8) % 64
+ need = 64 - have
+ ctx.count += 8 * inplen
+ off = 0
+ if inplen >= need:
+ if have:
+ for i in range(need):
+ ctx.buffer[have+i] = inp[i]
+ RMD160Transform(ctx.state, ctx.buffer)
+ off = need
+ have = 0
+ while off + 64 <= inplen:
+ RMD160Transform(ctx.state, inp[off:]) #<---
+ off += 64
+ if off < inplen:
+ # memcpy(ctx->buffer + have, input+off, len-off);
+ for i in range(inplen - off):
+ ctx.buffer[have+i] = inp[off+i]
+
+def RMD160Final(ctx):
+ size = struct.pack(" 900)
+
+def getRandomBytes(howMany):
+ b = bytearray(os.urandom(howMany))
+ assert(len(b) == howMany)
+ return b
+
+prngName = "os.urandom"
+
+
+# **************************************************************************
+# Converter Functions
+# **************************************************************************
+
+def bytesToNumber(b):
+ total = 0
+ multiplier = 1
+ for count in range(len(b)-1, -1, -1):
+ byte = b[count]
+ total += multiplier * byte
+ multiplier *= 256
+ return total
+
+def numberToByteArray(n, howManyBytes=None):
+ """Convert an integer into a bytearray, zero-pad to howManyBytes.
+
+ The returned bytearray may be smaller than howManyBytes, but will
+ not be larger. The returned bytearray will contain a big-endian
+ encoding of the input integer (n).
+ """
+ if howManyBytes == None:
+ howManyBytes = numBytes(n)
+ b = bytearray(howManyBytes)
+ for count in range(howManyBytes-1, -1, -1):
+ b[count] = int(n % 256)
+ n >>= 8
+ return b
+
+def mpiToNumber(mpi): #mpi is an openssl-format bignum string
+ if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number
+ raise AssertionError()
+ b = bytearray(mpi[4:])
+ return bytesToNumber(b)
+
+def numberToMPI(n):
+ b = numberToByteArray(n)
+ ext = 0
+ #If the high-order bit is going to be set,
+ #add an extra byte of zeros
+ if (numBits(n) & 0x7)==0:
+ ext = 1
+ length = numBytes(n) + ext
+ b = bytearray(4+ext) + b
+ b[0] = (length >> 24) & 0xFF
+ b[1] = (length >> 16) & 0xFF
+ b[2] = (length >> 8) & 0xFF
+ b[3] = length & 0xFF
+ return bytes(b)
+
+
+# **************************************************************************
+# Misc. Utility Functions
+# **************************************************************************
+
+def numBits(n):
+ if n==0:
+ return 0
+ s = "%x" % n
+ return ((len(s)-1)*4) + \
+ {'0':0, '1':1, '2':2, '3':2,
+ '4':3, '5':3, '6':3, '7':3,
+ '8':4, '9':4, 'a':4, 'b':4,
+ 'c':4, 'd':4, 'e':4, 'f':4,
+ }[s[0]]
+ return int(math.floor(math.log(n, 2))+1)
+
+def numBytes(n):
+ if n==0:
+ return 0
+ bits = numBits(n)
+ return int(math.ceil(bits / 8.0))
+
+# **************************************************************************
+# Big Number Math
+# **************************************************************************
+
+def getRandomNumber(low, high):
+ if low >= high:
+ raise AssertionError()
+ howManyBits = numBits(high)
+ howManyBytes = numBytes(high)
+ lastBits = howManyBits % 8
+ while 1:
+ bytes = getRandomBytes(howManyBytes)
+ if lastBits:
+ bytes[0] = bytes[0] % (1 << lastBits)
+ n = bytesToNumber(bytes)
+ if n >= low and n < high:
+ return n
+
+def gcd(a,b):
+ a, b = max(a,b), min(a,b)
+ while b:
+ a, b = b, a % b
+ return a
+
+def lcm(a, b):
+ return (a * b) // gcd(a, b)
+
+#Returns inverse of a mod b, zero if none
+#Uses Extended Euclidean Algorithm
+def invMod(a, b):
+ c, d = a, b
+ uc, ud = 1, 0
+ while c != 0:
+ q = d // c
+ c, d = d-(q*c), c
+ uc, ud = ud - (q * uc), uc
+ if d == 1:
+ return ud % b
+ return 0
+
+
+def powMod(base, power, modulus):
+ if power < 0:
+ result = pow(base, power*-1, modulus)
+ result = invMod(result, modulus)
+ return result
+ else:
+ return pow(base, power, modulus)
+
+#Pre-calculate a sieve of the ~100 primes < 1000:
+def makeSieve(n):
+ sieve = list(range(n))
+ for count in range(2, int(math.sqrt(n))+1):
+ if sieve[count] == 0:
+ continue
+ x = sieve[count] * 2
+ while x < len(sieve):
+ sieve[x] = 0
+ x += sieve[count]
+ sieve = [x for x in sieve[2:] if x]
+ return sieve
+
+sieve = makeSieve(1000)
+
+def isPrime(n, iterations=5, display=False):
+ #Trial division with sieve
+ for x in sieve:
+ if x >= n: return True
+ if n % x == 0: return False
+ #Passed trial division, proceed to Rabin-Miller
+ #Rabin-Miller implemented per Ferguson & Schneier
+ #Compute s, t for Rabin-Miller
+ if display: print("*", end=' ')
+ s, t = n-1, 0
+ while s % 2 == 0:
+ s, t = s//2, t+1
+ #Repeat Rabin-Miller x times
+ a = 2 #Use 2 as a base for first iteration speedup, per HAC
+ for count in range(iterations):
+ v = powMod(a, s, n)
+ if v==1:
+ continue
+ i = 0
+ while v != n-1:
+ if i == t-1:
+ return False
+ else:
+ v, i = powMod(v, 2, n), i+1
+ a = getRandomNumber(2, n)
+ return True
+
+def getRandomPrime(bits, display=False):
+ if bits < 10:
+ raise AssertionError()
+ #The 1.5 ensures the 2 MSBs are set
+ #Thus, when used for p,q in RSA, n will have its MSB set
+ #
+ #Since 30 is lcm(2,3,5), we'll set our test numbers to
+ #29 % 30 and keep them there
+ low = ((2 ** (bits-1)) * 3) // 2
+ high = 2 ** bits - 30
+ p = getRandomNumber(low, high)
+ p += 29 - (p % 30)
+ while 1:
+ if display: print(".", end=' ')
+ p += 30
+ if p >= high:
+ p = getRandomNumber(low, high)
+ p += 29 - (p % 30)
+ if isPrime(p, display=display):
+ return p
+
+#Unused at the moment...
+def getRandomSafePrime(bits, display=False):
+ if bits < 10:
+ raise AssertionError()
+ #The 1.5 ensures the 2 MSBs are set
+ #Thus, when used for p,q in RSA, n will have its MSB set
+ #
+ #Since 30 is lcm(2,3,5), we'll set our test numbers to
+ #29 % 30 and keep them there
+ low = (2 ** (bits-2)) * 3//2
+ high = (2 ** (bits-1)) - 30
+ q = getRandomNumber(low, high)
+ q += 29 - (q % 30)
+ while 1:
+ if display: print(".", end=' ')
+ q += 30
+ if (q >= high):
+ q = getRandomNumber(low, high)
+ q += 29 - (q % 30)
+ #Ideas from Tom Wu's SRP code
+ #Do trial division on p and q before Rabin-Miller
+ if isPrime(q, 0, display=display):
+ p = (2 * q) + 1
+ if isPrime(p, display=display):
+ if isPrime(q, display=display):
+ return p
+
+
+class RSAKey(object):
+
+ def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0):
+ if (n and not e) or (e and not n):
+ raise AssertionError()
+ self.n = n
+ self.e = e
+ self.d = d
+ self.p = p
+ self.q = q
+ self.dP = dP
+ self.dQ = dQ
+ self.qInv = qInv
+ self.blinder = 0
+ self.unblinder = 0
+
+ def __len__(self):
+ """Return the length of this key in bits.
+
+ @rtype: int
+ """
+ return numBits(self.n)
+
+ def hasPrivateKey(self):
+ return self.d != 0
+
+ def hashAndSign(self, bytes):
+ """Hash and sign the passed-in bytes.
+
+ This requires the key to have a private component. It performs
+ a PKCS1-SHA1 signature on the passed-in data.
+
+ @type bytes: str or L{bytearray} of unsigned bytes
+ @param bytes: The value which will be hashed and signed.
+
+ @rtype: L{bytearray} of unsigned bytes.
+ @return: A PKCS1-SHA1 signature on the passed-in data.
+ """
+ hashBytes = SHA1(bytearray(bytes))
+ prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes)
+ sigBytes = self.sign(prefixedHashBytes)
+ return sigBytes
+
+ def hashAndVerify(self, sigBytes, bytes):
+ """Hash and verify the passed-in bytes with the signature.
+
+ This verifies a PKCS1-SHA1 signature on the passed-in data.
+
+ @type sigBytes: L{bytearray} of unsigned bytes
+ @param sigBytes: A PKCS1-SHA1 signature.
+
+ @type bytes: str or L{bytearray} of unsigned bytes
+ @param bytes: The value which will be hashed and verified.
+
+ @rtype: bool
+ @return: Whether the signature matches the passed-in data.
+ """
+ hashBytes = SHA1(bytearray(bytes))
+
+ # Try it with/without the embedded NULL
+ prefixedHashBytes1 = self._addPKCS1SHA1Prefix(hashBytes, False)
+ prefixedHashBytes2 = self._addPKCS1SHA1Prefix(hashBytes, True)
+ result1 = self.verify(sigBytes, prefixedHashBytes1)
+ result2 = self.verify(sigBytes, prefixedHashBytes2)
+ return (result1 or result2)
+
+ def sign(self, bytes):
+ """Sign the passed-in bytes.
+
+ This requires the key to have a private component. It performs
+ a PKCS1 signature on the passed-in data.
+
+ @type bytes: L{bytearray} of unsigned bytes
+ @param bytes: The value which will be signed.
+
+ @rtype: L{bytearray} of unsigned bytes.
+ @return: A PKCS1 signature on the passed-in data.
+ """
+ if not self.hasPrivateKey():
+ raise AssertionError()
+ paddedBytes = self._addPKCS1Padding(bytes, 1)
+ m = bytesToNumber(paddedBytes)
+ if m >= self.n:
+ raise ValueError()
+ c = self._rawPrivateKeyOp(m)
+ sigBytes = numberToByteArray(c, numBytes(self.n))
+ return sigBytes
+
+ def verify(self, sigBytes, bytes):
+ """Verify the passed-in bytes with the signature.
+
+ This verifies a PKCS1 signature on the passed-in data.
+
+ @type sigBytes: L{bytearray} of unsigned bytes
+ @param sigBytes: A PKCS1 signature.
+
+ @type bytes: L{bytearray} of unsigned bytes
+ @param bytes: The value which will be verified.
+
+ @rtype: bool
+ @return: Whether the signature matches the passed-in data.
+ """
+ if len(sigBytes) != numBytes(self.n):
+ return False
+ paddedBytes = self._addPKCS1Padding(bytes, 1)
+ c = bytesToNumber(sigBytes)
+ if c >= self.n:
+ return False
+ m = self._rawPublicKeyOp(c)
+ checkBytes = numberToByteArray(m, numBytes(self.n))
+ return checkBytes == paddedBytes
+
+ def encrypt(self, bytes):
+ """Encrypt the passed-in bytes.
+
+ This performs PKCS1 encryption of the passed-in data.
+
+ @type bytes: L{bytearray} of unsigned bytes
+ @param bytes: The value which will be encrypted.
+
+ @rtype: L{bytearray} of unsigned bytes.
+ @return: A PKCS1 encryption of the passed-in data.
+ """
+ paddedBytes = self._addPKCS1Padding(bytes, 2)
+ m = bytesToNumber(paddedBytes)
+ if m >= self.n:
+ raise ValueError()
+ c = self._rawPublicKeyOp(m)
+ encBytes = numberToByteArray(c, numBytes(self.n))
+ return encBytes
+
+ def decrypt(self, encBytes):
+ """Decrypt the passed-in bytes.
+
+ This requires the key to have a private component. It performs
+ PKCS1 decryption of the passed-in data.
+
+ @type encBytes: L{bytearray} of unsigned bytes
+ @param encBytes: The value which will be decrypted.
+
+ @rtype: L{bytearray} of unsigned bytes or None.
+ @return: A PKCS1 decryption of the passed-in data or None if
+ the data is not properly formatted.
+ """
+ if not self.hasPrivateKey():
+ raise AssertionError()
+ if len(encBytes) != numBytes(self.n):
+ return None
+ c = bytesToNumber(encBytes)
+ if c >= self.n:
+ return None
+ m = self._rawPrivateKeyOp(c)
+ decBytes = numberToByteArray(m, numBytes(self.n))
+ #Check first two bytes
+ if decBytes[0] != 0 or decBytes[1] != 2:
+ return None
+ #Scan through for zero separator
+ for x in range(1, len(decBytes)-1):
+ if decBytes[x]== 0:
+ break
+ else:
+ return None
+ return decBytes[x+1:] #Return everything after the separator
+
+
+
+
+ # **************************************************************************
+ # Helper Functions for RSA Keys
+ # **************************************************************************
+
+ def _addPKCS1SHA1Prefix(self, bytes, withNULL=True):
+ # There is a long history of confusion over whether the SHA1
+ # algorithmIdentifier should be encoded with a NULL parameter or
+ # with the parameter omitted. While the original intention was
+ # apparently to omit it, many toolkits went the other way. TLS 1.2
+ # specifies the NULL should be included, and this behavior is also
+ # mandated in recent versions of PKCS #1, and is what tlslite has
+ # always implemented. Anyways, verification code should probably
+ # accept both. However, nothing uses this code yet, so this is
+ # all fairly moot.
+ if not withNULL:
+ prefixBytes = bytearray(\
+ [0x30,0x1f,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x04,0x14])
+ else:
+ prefixBytes = bytearray(\
+ [0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14])
+ prefixedBytes = prefixBytes + bytes
+ return prefixedBytes
+
+ def _addPKCS1Padding(self, bytes, blockType):
+ padLength = (numBytes(self.n) - (len(bytes)+3))
+ if blockType == 1: #Signature padding
+ pad = [0xFF] * padLength
+ elif blockType == 2: #Encryption padding
+ pad = bytearray(0)
+ while len(pad) < padLength:
+ padBytes = getRandomBytes(padLength * 2)
+ pad = [b for b in padBytes if b != 0]
+ pad = pad[:padLength]
+ else:
+ raise AssertionError()
+
+ padding = bytearray([0,blockType] + pad + [0])
+ paddedBytes = padding + bytes
+ return paddedBytes
+
+
+
+
+ def _rawPrivateKeyOp(self, m):
+ #Create blinding values, on the first pass:
+ if not self.blinder:
+ self.unblinder = getRandomNumber(2, self.n)
+ self.blinder = powMod(invMod(self.unblinder, self.n), self.e,
+ self.n)
+
+ #Blind the input
+ m = (m * self.blinder) % self.n
+
+ #Perform the RSA operation
+ c = self._rawPrivateKeyOpHelper(m)
+
+ #Unblind the output
+ c = (c * self.unblinder) % self.n
+
+ #Update blinding values
+ self.blinder = (self.blinder * self.blinder) % self.n
+ self.unblinder = (self.unblinder * self.unblinder) % self.n
+
+ #Return the output
+ return c
+
+
+ def _rawPrivateKeyOpHelper(self, m):
+ #Non-CRT version
+ #c = powMod(m, self.d, self.n)
+
+ #CRT version (~3x faster)
+ s1 = powMod(m, self.dP, self.p)
+ s2 = powMod(m, self.dQ, self.q)
+ h = ((s1 - s2) * self.qInv) % self.p
+ c = s2 + self.q * h
+ return c
+
+ def _rawPublicKeyOp(self, c):
+ m = powMod(c, self.e, self.n)
+ return m
+
+ def acceptsPassword(self):
+ return False
+
+ def generate(bits):
+ key = RSAKey()
+ p = getRandomPrime(bits//2, False)
+ q = getRandomPrime(bits//2, False)
+ t = lcm(p-1, q-1)
+ key.n = p * q
+ key.e = 65537
+ key.d = invMod(key.e, t)
+ key.p = p
+ key.q = q
+ key.dP = key.d % (p-1)
+ key.dQ = key.d % (q-1)
+ key.qInv = invMod(q, p)
+ return key
+ generate = staticmethod(generate)
diff --git a/electrum/scripts/bip70.py b/electrum/scripts/bip70.py
new file mode 100755
index 000000000..2e04bfe76
--- /dev/null
+++ b/electrum/scripts/bip70.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# create a BIP70 payment request signed with a certificate
+
+import tlslite
+
+from electrum.transaction import Transaction
+from electrum import paymentrequest
+from electrum import paymentrequest_pb2 as pb2
+
+chain_file = 'mychain.pem'
+cert_file = 'mycert.pem'
+amount = 1000000
+address = "18U5kpCAU4s8weFF8Ps5n8HAfpdUjDVF64"
+memo = "blah"
+out_file = "payreq"
+
+
+with open(chain_file, 'r') as f:
+ chain = tlslite.X509CertChain()
+ chain.parsePemList(f.read())
+
+certificates = pb2.X509Certificates()
+certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List))
+
+with open(cert_file, 'r') as f:
+ rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read())
+
+script = Transaction.pay_script('address', address).decode('hex')
+
+pr_string = paymentrequest.make_payment_request(amount, script, memo, rsakey)
+
+with open(out_file,'wb') as f:
+ f.write(pr_string)
+
+print("Payment request was written to file '%s'"%out_file)
diff --git a/electrum/scripts/block_headers.py b/electrum/scripts/block_headers.py
new file mode 100755
index 000000000..d3ecb7fdb
--- /dev/null
+++ b/electrum/scripts/block_headers.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+
+# A simple script that connects to a server and displays block headers
+
+import time
+from .. import SimpleConfig, Network
+from electrum.util import print_msg, json_encode
+
+# start network
+c = SimpleConfig()
+network = Network(c)
+network.start()
+
+# wait until connected
+while network.is_connecting():
+ time.sleep(0.1)
+
+if not network.is_connected():
+ print_msg("daemon is not connected")
+ sys.exit(1)
+
+# 2. send the subscription
+callback = lambda response: print_msg(json_encode(response.get('result')))
+network.send([('server.version',["block_headers script", "1.2"])], callback)
+network.subscribe_to_headers(callback)
+
+# 3. wait for results
+while network.is_connected():
+ time.sleep(1)
diff --git a/electrum/scripts/estimate_fee.py b/electrum/scripts/estimate_fee.py
new file mode 100755
index 000000000..85f63cef0
--- /dev/null
+++ b/electrum/scripts/estimate_fee.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+from . import util
+import json
+from electrum.network import filter_protocol
+peers = filter_protocol(util.get_peers())
+results = util.send_request(peers, 'blockchain.estimatefee', [2])
+print(json.dumps(results, indent=4))
diff --git a/electrum/scripts/get_history.py b/electrum/scripts/get_history.py
new file mode 100755
index 000000000..399a4b114
--- /dev/null
+++ b/electrum/scripts/get_history.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+
+import sys
+from .. import Network
+from electrum.util import json_encode, print_msg
+from electrum import bitcoin
+
+try:
+ addr = sys.argv[1]
+except Exception:
+ print("usage: get_history ")
+ sys.exit(1)
+
+n = Network()
+n.start()
+_hash = bitcoin.address_to_scripthash(addr)
+h = n.get_history_for_scripthash(_hash)
+print_msg(json_encode(h))
diff --git a/electrum/scripts/peers.py b/electrum/scripts/peers.py
new file mode 100755
index 000000000..a887f0795
--- /dev/null
+++ b/electrum/scripts/peers.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+from . import util
+
+from electrum.network import filter_protocol
+from electrum.blockchain import hash_header
+
+peers = util.get_peers()
+peers = filter_protocol(peers, 's')
+
+results = util.send_request(peers, 'blockchain.headers.subscribe', [])
+
+for n,v in sorted(results.items(), key=lambda x:x[1].get('block_height')):
+ print("%60s"%n, v.get('block_height'), hash_header(v))
diff --git a/electrum/scripts/servers.py b/electrum/scripts/servers.py
new file mode 100755
index 000000000..c7201bc38
--- /dev/null
+++ b/electrum/scripts/servers.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+from .. import set_verbosity
+from electrum.network import filter_version
+from . import util
+import json
+set_verbosity(False)
+
+servers = filter_version(util.get_peers())
+print(json.dumps(servers, sort_keys = True, indent = 4))
diff --git a/electrum/scripts/txradar.py b/electrum/scripts/txradar.py
new file mode 100755
index 000000000..dda732274
--- /dev/null
+++ b/electrum/scripts/txradar.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+from . import util
+import sys
+try:
+ tx = sys.argv[1]
+except:
+ print("usage: txradar txid")
+ sys.exit(1)
+
+peers = util.get_peers()
+results = util.send_request(peers, 'blockchain.transaction.get', [tx])
+
+r1 = []
+r2 = []
+
+for k, v in results.items():
+ (r1 if v else r2).append(k)
+
+print("Received %d answers"%len(results))
+print("Propagation rate: %.1f percent" % (len(r1) *100./(len(r1)+ len(r2))))
diff --git a/electrum/scripts/util.py b/electrum/scripts/util.py
new file mode 100644
index 000000000..266348f75
--- /dev/null
+++ b/electrum/scripts/util.py
@@ -0,0 +1,87 @@
+import select, time, queue
+# import electrum
+from .. import Connection, Interface, SimpleConfig
+
+from electrum.network import parse_servers
+from collections import defaultdict
+
+# electrum.util.set_verbosity(1)
+def get_interfaces(servers, timeout=10):
+ '''Returns a map of servers to connected interfaces. If any
+ connections fail or timeout, they will be missing from the map.
+ '''
+ assert type(servers) is list
+ socket_queue = queue.Queue()
+ config = SimpleConfig()
+ connecting = {}
+ for server in servers:
+ if server not in connecting:
+ connecting[server] = Connection(server, socket_queue, config.path)
+ interfaces = {}
+ timeout = time.time() + timeout
+ count = 0
+ while time.time() < timeout and count < len(servers):
+ try:
+ server, socket = socket_queue.get(True, 0.3)
+ except queue.Empty:
+ continue
+ if socket:
+ interfaces[server] = Interface(server, socket)
+ count += 1
+ return interfaces
+
+def wait_on_interfaces(interfaces, timeout=10):
+ '''Return a map of servers to a list of (request, response) tuples.
+ Waits timeout seconds, or until each interface has a response'''
+ result = defaultdict(list)
+ timeout = time.time() + timeout
+ while len(result) < len(interfaces) and time.time() < timeout:
+ rin = [i for i in interfaces.values()]
+ win = [i for i in interfaces.values() if i.unsent_requests]
+ rout, wout, xout = select.select(rin, win, [], 1)
+ for interface in wout:
+ interface.send_requests()
+ for interface in rout:
+ responses = interface.get_responses()
+ if responses:
+ result[interface.server].extend(responses)
+ return result
+
+def get_peers():
+ config = SimpleConfig()
+ peers = {}
+ # 1. get connected interfaces
+ server = config.get('server')
+ if server is None:
+ print("You need to set a secure server, for example (for mainnet): 'electrum setconfig server helicarrier.bauerj.eu:50002:s'")
+ return []
+ interfaces = get_interfaces([server])
+ if not interfaces:
+ print("No connection to", server)
+ return []
+ # 2. get list of peers
+ interface = interfaces[server]
+ interface.queue_request('server.peers.subscribe', [], 0)
+ responses = wait_on_interfaces(interfaces).get(server)
+ if responses:
+ response = responses[0][1] # One response, (req, response) tuple
+ peers = parse_servers(response.get('result'))
+ return peers
+
+
+def send_request(peers, method, params):
+ print("Contacting %d servers"%len(peers))
+ interfaces = get_interfaces(peers)
+ print("%d servers could be reached" % len(interfaces))
+ for peer in peers:
+ if not peer in interfaces:
+ print("Connection failed:", peer)
+ for msg_id, i in enumerate(interfaces.values()):
+ i.queue_request(method, params, msg_id)
+ responses = wait_on_interfaces(interfaces)
+ for peer in interfaces:
+ if not peer in responses:
+ print(peer, "did not answer")
+ results = dict(zip(responses.keys(), [t[0][1].get('result') for t in responses.values()]))
+ print("%d answers"%len(results))
+ return results
diff --git a/electrum/scripts/watch_address.py b/electrum/scripts/watch_address.py
new file mode 100755
index 000000000..b176ec9f1
--- /dev/null
+++ b/electrum/scripts/watch_address.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+import sys
+import time
+from electrum import bitcoin
+from .. import SimpleConfig, Network
+from electrum.util import print_msg, json_encode
+
+try:
+ addr = sys.argv[1]
+except Exception:
+ print("usage: watch_address ")
+ sys.exit(1)
+
+sh = bitcoin.address_to_scripthash(addr)
+
+# start network
+c = SimpleConfig()
+network = Network(c)
+network.start()
+
+# wait until connected
+while network.is_connecting():
+ time.sleep(0.1)
+
+if not network.is_connected():
+ print_msg("daemon is not connected")
+ sys.exit(1)
+
+# 2. send the subscription
+callback = lambda response: print_msg(json_encode(response.get('result')))
+network.subscribe_to_address(addr, callback)
+
+# 3. wait for results
+while network.is_connected():
+ time.sleep(1)
diff --git a/electrum/segwit_addr.py b/electrum/segwit_addr.py
new file mode 100644
index 000000000..3f3992a5a
--- /dev/null
+++ b/electrum/segwit_addr.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2017 Pieter Wuille
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+"""Reference implementation for Bech32 and segwit addresses."""
+
+
+CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+
+
+def bech32_polymod(values):
+ """Internal function that computes the Bech32 checksum."""
+ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+ chk = 1
+ for value in values:
+ top = chk >> 25
+ chk = (chk & 0x1ffffff) << 5 ^ value
+ for i in range(5):
+ chk ^= generator[i] if ((top >> i) & 1) else 0
+ return chk
+
+
+def bech32_hrp_expand(hrp):
+ """Expand the HRP into values for checksum computation."""
+ return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
+
+
+def bech32_verify_checksum(hrp, data):
+ """Verify a checksum given HRP and converted data characters."""
+ return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1
+
+
+def bech32_create_checksum(hrp, data):
+ """Compute the checksum values given HRP and data."""
+ values = bech32_hrp_expand(hrp) + data
+ polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
+ return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
+
+
+def bech32_encode(hrp, data):
+ """Compute a Bech32 string given HRP and data values."""
+ combined = data + bech32_create_checksum(hrp, data)
+ return hrp + '1' + ''.join([CHARSET[d] for d in combined])
+
+
+def bech32_decode(bech):
+ """Validate a Bech32 string, and determine HRP and data."""
+ if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
+ (bech.lower() != bech and bech.upper() != bech)):
+ return (None, None)
+ bech = bech.lower()
+ pos = bech.rfind('1')
+ if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
+ return (None, None)
+ if not all(x in CHARSET for x in bech[pos+1:]):
+ return (None, None)
+ hrp = bech[:pos]
+ data = [CHARSET.find(x) for x in bech[pos+1:]]
+ if not bech32_verify_checksum(hrp, data):
+ return (None, None)
+ return (hrp, data[:-6])
+
+
+def convertbits(data, frombits, tobits, pad=True):
+ """General power-of-2 base conversion."""
+ acc = 0
+ bits = 0
+ ret = []
+ maxv = (1 << tobits) - 1
+ max_acc = (1 << (frombits + tobits - 1)) - 1
+ for value in data:
+ if value < 0 or (value >> frombits):
+ return None
+ acc = ((acc << frombits) | value) & max_acc
+ bits += frombits
+ while bits >= tobits:
+ bits -= tobits
+ ret.append((acc >> bits) & maxv)
+ if pad:
+ if bits:
+ ret.append((acc << (tobits - bits)) & maxv)
+ elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
+ return None
+ return ret
+
+
+def decode(hrp, addr):
+ """Decode a segwit address."""
+ hrpgot, data = bech32_decode(addr)
+ if hrpgot != hrp:
+ return (None, None)
+ decoded = convertbits(data[1:], 5, 8, False)
+ if decoded is None or len(decoded) < 2 or len(decoded) > 40:
+ return (None, None)
+ if data[0] > 16:
+ return (None, None)
+ if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
+ return (None, None)
+ return (data[0], decoded)
+
+
+def encode(hrp, witver, witprog):
+ """Encode a segwit address."""
+ ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
+ assert decode(hrp, ret) is not (None, None)
+ return ret
diff --git a/electrum/servers.json b/electrum/servers.json
new file mode 100644
index 000000000..d75c08433
--- /dev/null
+++ b/electrum/servers.json
@@ -0,0 +1,26 @@
+{
+ "ele1.bitcore.cc": {
+ "pruning": "-",
+ "s": "50002",
+ "t": "50001",
+ "version": "1.1"
+ },
+ "ele2.bitcore.cc": {
+ "pruning": "-",
+ "s": "50002",
+ "t": "50001",
+ "version": "1.1"
+ },
+ "ele3.bitcore.cc": {
+ "pruning": "-",
+ "s": "50002",
+ "t": "50001",
+ "version": "1.1"
+ },
+ "ele4.bitcore.cc": {
+ "pruning": "-",
+ "s": "50002",
+ "t": "50001",
+ "version": "1.1"
+ }
+}
diff --git a/electrum/servers_regtest.json b/electrum/servers_regtest.json
new file mode 100644
index 000000000..5356f090c
--- /dev/null
+++ b/electrum/servers_regtest.json
@@ -0,0 +1,8 @@
+{
+ "127.0.0.1": {
+ "pruning": "-",
+ "s": "51002",
+ "t": "51001",
+ "version": "1.2"
+ }
+}
diff --git a/electrum/servers_testnet.json b/electrum/servers_testnet.json
new file mode 100644
index 000000000..5356f090c
--- /dev/null
+++ b/electrum/servers_testnet.json
@@ -0,0 +1,8 @@
+{
+ "127.0.0.1": {
+ "pruning": "-",
+ "s": "51002",
+ "t": "51001",
+ "version": "1.2"
+ }
+}
diff --git a/electrum/simple_config.py b/electrum/simple_config.py
new file mode 100644
index 000000000..830c68867
--- /dev/null
+++ b/electrum/simple_config.py
@@ -0,0 +1,573 @@
+import json
+import threading
+import time
+import os
+import stat
+from decimal import Decimal
+from typing import Union, Optional
+from numbers import Real
+
+from copy import deepcopy
+
+from . import util
+from .util import (user_dir, print_error, PrintError, make_dir,
+ NoDynamicFeeEstimates, format_fee_satoshis, quantize_feerate)
+from .i18n import _
+
+FEE_ETA_TARGETS = [25, 10, 5, 2]
+FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000]
+
+# satoshi per kbyte
+FEERATE_MAX_DYNAMIC = 10000000
+FEERATE_WARNING_HIGH_FEE = 10000000
+FEERATE_FALLBACK_STATIC_FEE = 100000
+FEERATE_DEFAULT_RELAY = 100000
+FEERATE_STATIC_VALUES = [100000, 200000, 500000, 2000000, 5000000, 10000000, 20000000, 50000000, 100000000]
+
+
+config = None
+
+
+def get_config():
+ global config
+ return config
+
+
+def set_config(c):
+ global config
+ config = c
+
+
+FINAL_CONFIG_VERSION = 3
+
+
+class SimpleConfig(PrintError):
+ """
+ The SimpleConfig class is responsible for handling operations involving
+ configuration files.
+
+ There are two different sources of possible configuration values:
+ 1. Command line options.
+ 2. User configuration (in the user's config directory)
+ They are taken in order (1. overrides config options set in 2.)
+ """
+
+ def __init__(self, options=None, read_user_config_function=None,
+ read_user_dir_function=None):
+
+ if options is None:
+ options = {}
+
+ # This lock needs to be acquired for updating and reading the config in
+ # a thread-safe way.
+ self.lock = threading.RLock()
+
+ self.mempool_fees = {}
+ self.fee_estimates = {}
+ self.fee_estimates_last_updated = {}
+ self.last_time_fee_estimates_requested = 0 # zero ensures immediate fees
+
+ # The following two functions are there for dependency injection when
+ # testing.
+ if read_user_config_function is None:
+ read_user_config_function = read_user_config
+ if read_user_dir_function is None:
+ self.user_dir = user_dir
+ else:
+ self.user_dir = read_user_dir_function
+
+ # The command line options
+ self.cmdline_options = deepcopy(options)
+ # don't allow to be set on CLI:
+ self.cmdline_options.pop('config_version', None)
+
+ # Set self.path and read the user config
+ self.user_config = {} # for self.get in electrum_path()
+ self.path = self.electrum_path()
+ self.user_config = read_user_config_function(self.path)
+ if not self.user_config:
+ # avoid new config getting upgraded
+ self.user_config = {'config_version': FINAL_CONFIG_VERSION}
+
+ # config "upgrade" - CLI options
+ self.rename_config_keys(
+ self.cmdline_options, {'auto_cycle': 'auto_connect'}, True)
+
+ # config upgrade - user config
+ if self.requires_upgrade():
+ self.upgrade()
+
+ # Make a singleton instance of 'self'
+ set_config(self)
+
+ def electrum_path(self):
+ # Read electrum_path from command line
+ # Otherwise use the user's default data directory.
+ path = self.get('electrum_path')
+ if path is None:
+ path = self.user_dir()
+
+ make_dir(path, allow_symlink=False)
+ if self.get('testnet'):
+ path = os.path.join(path, 'testnet')
+ make_dir(path, allow_symlink=False)
+ elif self.get('regtest'):
+ path = os.path.join(path, 'regtest')
+ make_dir(path, allow_symlink=False)
+ elif self.get('simnet'):
+ path = os.path.join(path, 'simnet')
+ make_dir(path, allow_symlink=False)
+
+ self.print_error("electrum directory", path)
+ return path
+
+ def rename_config_keys(self, config, keypairs, deprecation_warning=False):
+ """Migrate old key names to new ones"""
+ updated = False
+ for old_key, new_key in keypairs.items():
+ if old_key in config:
+ if new_key not in config:
+ config[new_key] = config[old_key]
+ if deprecation_warning:
+ self.print_stderr('Note that the {} variable has been deprecated. '
+ 'You should use {} instead.'.format(old_key, new_key))
+ del config[old_key]
+ updated = True
+ return updated
+
+ def set_key(self, key, value, save=True):
+ if not self.is_modifiable(key):
+ self.print_stderr("Warning: not changing config key '%s' set on the command line" % key)
+ return
+ self._set_key_in_user_config(key, value, save)
+
+ def _set_key_in_user_config(self, key, value, save=True):
+ with self.lock:
+ if value is not None:
+ self.user_config[key] = value
+ else:
+ self.user_config.pop(key, None)
+ if save:
+ self.save_user_config()
+
+ def get(self, key, default=None):
+ with self.lock:
+ out = self.cmdline_options.get(key)
+ if out is None:
+ out = self.user_config.get(key, default)
+ return out
+
+ def requires_upgrade(self):
+ return self.get_config_version() < FINAL_CONFIG_VERSION
+
+ def upgrade(self):
+ with self.lock:
+ self.print_error('upgrading config')
+
+ self.convert_version_2()
+ self.convert_version_3()
+
+ self.set_key('config_version', FINAL_CONFIG_VERSION, save=True)
+
+ def convert_version_2(self):
+ if not self._is_upgrade_method_needed(1, 1):
+ return
+
+ self.rename_config_keys(self.user_config, {'auto_cycle': 'auto_connect'})
+
+ try:
+ # change server string FROM host:port:proto TO host:port:s
+ server_str = self.user_config.get('server')
+ host, port, protocol = str(server_str).rsplit(':', 2)
+ assert protocol in ('s', 't')
+ int(port) # Throw if cannot be converted to int
+ server_str = '{}:{}:s'.format(host, port)
+ self._set_key_in_user_config('server', server_str)
+ except BaseException:
+ self._set_key_in_user_config('server', None)
+
+ self.set_key('config_version', 2)
+
+ def convert_version_3(self):
+ if not self._is_upgrade_method_needed(2, 2):
+ return
+
+ base_unit = self.user_config.get('base_unit')
+ if isinstance(base_unit, str):
+ self._set_key_in_user_config('base_unit', None)
+ map_ = {'btc':8, 'mbtc':5, 'ubtc':2, 'bits':2, 'sat':0}
+ decimal_point = map_.get(base_unit.lower())
+ self._set_key_in_user_config('decimal_point', decimal_point)
+
+ self.set_key('config_version', 3)
+
+ def _is_upgrade_method_needed(self, min_version, max_version):
+ cur_version = self.get_config_version()
+ if cur_version > max_version:
+ return False
+ elif cur_version < min_version:
+ raise Exception(
+ ('config upgrade: unexpected version %d (should be %d-%d)'
+ % (cur_version, min_version, max_version)))
+ else:
+ return True
+
+ def get_config_version(self):
+ config_version = self.get('config_version', 1)
+ if config_version > FINAL_CONFIG_VERSION:
+ self.print_stderr('WARNING: config version ({}) is higher than ours ({})'
+ .format(config_version, FINAL_CONFIG_VERSION))
+ return config_version
+
+ def is_modifiable(self, key):
+ return key not in self.cmdline_options
+
+ def save_user_config(self):
+ if not self.path:
+ return
+ path = os.path.join(self.path, "config")
+ s = json.dumps(self.user_config, indent=4, sort_keys=True)
+ try:
+ with open(path, "w", encoding='utf-8') as f:
+ f.write(s)
+ os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
+ except FileNotFoundError:
+ # datadir probably deleted while running...
+ if os.path.exists(self.path): # or maybe not?
+ raise
+
+ def get_wallet_path(self):
+ """Set the path of the wallet."""
+
+ # command line -w option
+ if self.get('wallet_path'):
+ return os.path.join(self.get('cwd'), self.get('wallet_path'))
+
+ # path in config file
+ path = self.get('default_wallet_path')
+ if path and os.path.exists(path):
+ return path
+
+ # default path
+ util.assert_datadir_available(self.path)
+ dirpath = os.path.join(self.path, "wallets")
+ make_dir(dirpath, allow_symlink=False)
+
+ new_path = os.path.join(self.path, "wallets", "default_wallet")
+
+ # default path in pre 1.9 versions
+ old_path = os.path.join(self.path, "electrum-btx.dat")
+ if os.path.exists(old_path) and not os.path.exists(new_path):
+ os.rename(old_path, new_path)
+
+ return new_path
+
+ def remove_from_recently_open(self, filename):
+ recent = self.get('recently_open', [])
+ if filename in recent:
+ recent.remove(filename)
+ self.set_key('recently_open', recent)
+
+ def set_session_timeout(self, seconds):
+ self.print_error("session timeout -> %d seconds" % seconds)
+ self.set_key('session_timeout', seconds)
+
+ def get_session_timeout(self):
+ return self.get('session_timeout', 300)
+
+ def open_last_wallet(self):
+ if self.get('wallet_path') is None:
+ last_wallet = self.get('gui_last_wallet')
+ if last_wallet is not None and os.path.exists(last_wallet):
+ self.cmdline_options['default_wallet_path'] = last_wallet
+
+ def save_last_wallet(self, wallet):
+ if self.get('wallet_path') is None:
+ path = wallet.storage.path
+ self.set_key('gui_last_wallet', path)
+
+ def impose_hard_limits_on_fee(func):
+ def get_fee_within_limits(self, *args, **kwargs):
+ fee = func(self, *args, **kwargs)
+ if fee is None:
+ return fee
+ fee = min(FEERATE_MAX_DYNAMIC, fee)
+ fee = max(FEERATE_DEFAULT_RELAY, fee)
+ return fee
+ return get_fee_within_limits
+
+ def eta_to_fee(self, slider_pos) -> Optional[int]:
+ """Returns fee in sat/kbyte."""
+ slider_pos = max(slider_pos, 0)
+ slider_pos = min(slider_pos, len(FEE_ETA_TARGETS))
+ if slider_pos < len(FEE_ETA_TARGETS):
+ num_blocks = FEE_ETA_TARGETS[slider_pos]
+ fee = self.eta_target_to_fee(num_blocks)
+ else:
+ fee = self.eta_target_to_fee(1)
+ return fee
+
+ @impose_hard_limits_on_fee
+ def eta_target_to_fee(self, num_blocks: int) -> Optional[int]:
+ """Returns fee in sat/kbyte."""
+ if num_blocks == 1:
+ fee = self.fee_estimates.get(2)
+ if fee is not None:
+ fee += fee / 2
+ fee = int(fee)
+ else:
+ fee = self.fee_estimates.get(num_blocks)
+ return fee
+
+ def fee_to_depth(self, target_fee: Real) -> int:
+ """For a given sat/vbyte fee, returns an estimate of how deep
+ it would be in the current mempool in vbytes.
+ Pessimistic == overestimates the depth.
+ """
+ depth = 0
+ for fee, s in self.mempool_fees:
+ depth += s
+ if fee <= target_fee:
+ break
+ return depth
+
+ def depth_to_fee(self, slider_pos) -> int:
+ """Returns fee in sat/kbyte."""
+ target = self.depth_target(slider_pos)
+ return self.depth_target_to_fee(target)
+
+ @impose_hard_limits_on_fee
+ def depth_target_to_fee(self, target: int) -> int:
+ """Returns fee in sat/kbyte.
+ target: desired mempool depth in vbytes
+ """
+ depth = 0
+ for fee, s in self.mempool_fees:
+ depth += s
+ if depth > target:
+ break
+ else:
+ return 0
+ # add one sat/byte as currently that is
+ # the max precision of the histogram
+ fee += 1
+ # convert to sat/kbyte
+ return fee * 1000
+
+ def depth_target(self, slider_pos):
+ slider_pos = max(slider_pos, 0)
+ slider_pos = min(slider_pos, len(FEE_DEPTH_TARGETS)-1)
+ return FEE_DEPTH_TARGETS[slider_pos]
+
+ def eta_target(self, i):
+ if i == len(FEE_ETA_TARGETS):
+ return 1
+ return FEE_ETA_TARGETS[i]
+
+ def fee_to_eta(self, fee_per_kb):
+ import operator
+ l = list(self.fee_estimates.items()) + [(1, self.eta_to_fee(4))]
+ dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), l)
+ min_target, min_value = min(dist, key=operator.itemgetter(1))
+ if fee_per_kb < self.fee_estimates.get(25)/2:
+ min_target = -1
+ return min_target
+
+ def depth_tooltip(self, depth):
+ return "%.1f MB from tip"%(depth/1000000)
+
+ def eta_tooltip(self, x):
+ if x < 0:
+ return _('Low fee')
+ elif x == 1:
+ return _('In the next block')
+ else:
+ return _('Within {} blocks').format(x)
+
+ def get_fee_status(self):
+ dyn = self.is_dynfee()
+ mempool = self.use_mempool_fees()
+ pos = self.get_depth_level() if mempool else self.get_fee_level()
+ fee_rate = self.fee_per_kb()
+ target, tooltip = self.get_fee_text(pos, dyn, mempool, fee_rate)
+ return tooltip + ' [%s]'%target if dyn else target + ' [Static]'
+
+ def get_fee_text(self, pos, dyn, mempool, fee_rate):
+ """Returns (text, tooltip) where
+ text is what we target: static fee / num blocks to confirm in / mempool depth
+ tooltip is the corresponding estimate (e.g. num blocks for a static fee)
+ """
+ if fee_rate is None:
+ rate_str = 'unknown'
+ else:
+ rate_str = format_fee_satoshis(fee_rate/1000) + ' sat/byte'
+
+ if dyn:
+ if mempool:
+ depth = self.depth_target(pos)
+ text = self.depth_tooltip(depth)
+ else:
+ eta = self.eta_target(pos)
+ text = self.eta_tooltip(eta)
+ tooltip = rate_str
+ else:
+ text = rate_str
+ if mempool and self.has_fee_mempool():
+ depth = self.fee_to_depth(fee_rate)
+ tooltip = self.depth_tooltip(depth)
+ elif not mempool and self.has_fee_etas():
+ eta = self.fee_to_eta(fee_rate)
+ tooltip = self.eta_tooltip(eta)
+ else:
+ tooltip = ''
+ return text, tooltip
+
+ def get_depth_level(self):
+ maxp = len(FEE_DEPTH_TARGETS) - 1
+ return min(maxp, self.get('depth_level', 2))
+
+ def get_fee_level(self):
+ maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block"
+ return min(maxp, self.get('fee_level', 2))
+
+ def get_fee_slider(self, dyn, mempool):
+ if dyn:
+ if mempool:
+ pos = self.get_depth_level()
+ maxp = len(FEE_DEPTH_TARGETS) - 1
+ fee_rate = self.depth_to_fee(pos)
+ else:
+ pos = self.get_fee_level()
+ maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block"
+ fee_rate = self.eta_to_fee(pos)
+ else:
+ fee_rate = self.fee_per_kb(dyn=False)
+ pos = self.static_fee_index(fee_rate)
+ maxp = 9
+ return maxp, pos, fee_rate
+
+ def static_fee(self, i):
+ return FEERATE_STATIC_VALUES[i]
+
+ def static_fee_index(self, value):
+ if value is None:
+ raise TypeError('static fee cannot be None')
+ dist = list(map(lambda x: abs(x - value), FEERATE_STATIC_VALUES))
+ return min(range(len(dist)), key=dist.__getitem__)
+
+ def has_fee_etas(self):
+ return len(self.fee_estimates) == 4
+
+ def has_fee_mempool(self):
+ return bool(self.mempool_fees)
+
+ def has_dynamic_fees_ready(self):
+ if self.use_mempool_fees():
+ return self.has_fee_mempool()
+ else:
+ return self.has_fee_etas()
+
+ def is_dynfee(self):
+ return bool(self.get('dynamic_fees', False))
+
+ def use_mempool_fees(self):
+ return bool(self.get('mempool_fees', False))
+
+ def _feerate_from_fractional_slider_position(self, fee_level: float, dyn: bool,
+ mempool: bool) -> Union[int, None]:
+ fee_level = max(fee_level, 0)
+ fee_level = min(fee_level, 1)
+ if dyn:
+ max_pos = (len(FEE_DEPTH_TARGETS) - 1) if mempool else len(FEE_ETA_TARGETS)
+ slider_pos = round(fee_level * max_pos)
+ fee_rate = self.depth_to_fee(slider_pos) if mempool else self.eta_to_fee(slider_pos)
+ else:
+ max_pos = len(FEERATE_STATIC_VALUES) - 1
+ slider_pos = round(fee_level * max_pos)
+ fee_rate = FEERATE_STATIC_VALUES[slider_pos]
+ return fee_rate
+
+ def fee_per_kb(self, dyn: bool=None, mempool: bool=None, fee_level: float=None) -> Union[int, None]:
+ """Returns sat/kvB fee to pay for a txn.
+ Note: might return None.
+
+ fee_level: float between 0.0 and 1.0, representing fee slider position
+ """
+ if dyn is None:
+ dyn = self.is_dynfee()
+ if mempool is None:
+ mempool = self.use_mempool_fees()
+ if fee_level is not None:
+ return self._feerate_from_fractional_slider_position(fee_level, dyn, mempool)
+ # there is no fee_level specified; will use config.
+ # note: 'depth_level' and 'fee_level' in config are integer slider positions,
+ # unlike fee_level here, which (when given) is a float in [0.0, 1.0]
+ if dyn:
+ if mempool:
+ fee_rate = self.depth_to_fee(self.get_depth_level())
+ else:
+ fee_rate = self.eta_to_fee(self.get_fee_level())
+ else:
+ fee_rate = self.get('fee_per_kb', FEERATE_FALLBACK_STATIC_FEE)
+ return fee_rate
+
+ def fee_per_byte(self):
+ """Returns sat/vB fee to pay for a txn.
+ Note: might return None.
+ """
+ fee_per_kb = self.fee_per_kb()
+ return fee_per_kb / 1000 if fee_per_kb is not None else None
+
+ def estimate_fee(self, size):
+ fee_per_kb = self.fee_per_kb()
+ if fee_per_kb is None:
+ raise NoDynamicFeeEstimates()
+ return self.estimate_fee_for_feerate(fee_per_kb, size)
+
+ @classmethod
+ def estimate_fee_for_feerate(cls, fee_per_kb, size):
+ fee_per_kb = Decimal(fee_per_kb)
+ fee_per_byte = fee_per_kb / 1000
+ # to be consistent with what is displayed in the GUI,
+ # the calculation needs to use the same precision:
+ fee_per_byte = quantize_feerate(fee_per_byte)
+ return round(fee_per_byte * size)
+
+ def update_fee_estimates(self, key, value):
+ self.fee_estimates[key] = value
+ self.fee_estimates_last_updated[key] = time.time()
+
+ def is_fee_estimates_update_required(self):
+ """Checks time since last requested and updated fee estimates.
+ Returns True if an update should be requested.
+ """
+ now = time.time()
+ return now - self.last_time_fee_estimates_requested > 60
+
+ def requested_fee_estimates(self):
+ self.last_time_fee_estimates_requested = time.time()
+
+ def get_video_device(self):
+ device = self.get("video_device", "default")
+ if device == 'default':
+ device = ''
+ return device
+
+
+def read_user_config(path):
+ """Parse and store the user config settings in electrum.conf into user_config[]."""
+ if not path:
+ return {}
+ config_path = os.path.join(path, "config")
+ if not os.path.exists(config_path):
+ return {}
+ try:
+ with open(config_path, "r", encoding='utf-8') as f:
+ data = f.read()
+ result = json.loads(data)
+ except:
+ print_error("Warning: Cannot read config file.", config_path)
+ return {}
+ if not type(result) is dict:
+ return {}
+ return result
diff --git a/electrum/storage.py b/electrum/storage.py
new file mode 100644
index 000000000..156df968e
--- /dev/null
+++ b/electrum/storage.py
@@ -0,0 +1,670 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+import ast
+import threading
+import json
+import copy
+import re
+import stat
+import hmac, hashlib
+import base64
+import zlib
+from collections import defaultdict
+
+from . import util, bitcoin, ecc
+from .util import PrintError, profiler, InvalidPassword, WalletFileException, bfh
+from .plugin import run_hook, plugin_loaders
+from .keystore import bip44_derivation
+
+
+# seed_version is now used for the version of the wallet file
+
+OLD_SEED_VERSION = 4 # electrum versions < 2.0
+NEW_SEED_VERSION = 11 # electrum versions >= 2.0
+FINAL_SEED_VERSION = 18 # electrum >= 2.7 will set this to prevent
+ # old versions from overwriting new format
+
+
+
+def multisig_type(wallet_type):
+ '''If wallet_type is mofn multi-sig, return [m, n],
+ otherwise return None.'''
+ if not wallet_type:
+ return None
+ match = re.match('(\d+)of(\d+)', wallet_type)
+ if match:
+ match = [int(x) for x in match.group(1, 2)]
+ return match
+
+def get_derivation_used_for_hw_device_encryption():
+ return ("m"
+ "/4541509'" # ascii 'ELE' as decimal ("BIP43 purpose")
+ "/1112098098'") # ascii 'BIE2' as decimal
+
+# storage encryption version
+STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW = range(0, 3)
+
+
+class JsonDB(PrintError):
+
+ def __init__(self, path):
+ self.db_lock = threading.RLock()
+ self.data = {}
+ self.path = path
+ self.modified = False
+
+ def get(self, key, default=None):
+ with self.db_lock:
+ v = self.data.get(key)
+ if v is None:
+ v = default
+ else:
+ v = copy.deepcopy(v)
+ return v
+
+ def put(self, key, value):
+ try:
+ json.dumps(key, cls=util.MyEncoder)
+ json.dumps(value, cls=util.MyEncoder)
+ except:
+ self.print_error("json error: cannot save", key)
+ return
+ with self.db_lock:
+ if value is not None:
+ if self.data.get(key) != value:
+ self.modified = True
+ self.data[key] = copy.deepcopy(value)
+ elif key in self.data:
+ self.modified = True
+ self.data.pop(key)
+
+ @profiler
+ def write(self):
+ with self.db_lock:
+ self._write()
+
+ def _write(self):
+ if threading.currentThread().isDaemon():
+ self.print_error('warning: daemon thread cannot write db')
+ return
+ if not self.modified:
+ return
+ s = json.dumps(self.data, indent=4, sort_keys=True, cls=util.MyEncoder)
+ s = self.encrypt_before_writing(s)
+
+ temp_path = "%s.tmp.%s" % (self.path, os.getpid())
+ with open(temp_path, "w", encoding='utf-8') as f:
+ f.write(s)
+ f.flush()
+ os.fsync(f.fileno())
+
+ mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE
+ # perform atomic write on POSIX systems
+ try:
+ os.rename(temp_path, self.path)
+ except:
+ os.remove(self.path)
+ os.rename(temp_path, self.path)
+ os.chmod(self.path, mode)
+ self.print_error("saved", self.path)
+ self.modified = False
+
+ def encrypt_before_writing(self, plaintext: str) -> str:
+ return plaintext
+
+ def file_exists(self):
+ return self.path and os.path.exists(self.path)
+
+
+class WalletStorage(JsonDB):
+
+ def __init__(self, path, manual_upgrades=False):
+ self.print_error("wallet path", path)
+ JsonDB.__init__(self, path)
+ self.manual_upgrades = manual_upgrades
+ self.pubkey = None
+ if self.file_exists():
+ with open(self.path, "r", encoding='utf-8') as f:
+ self.raw = f.read()
+ self._encryption_version = self._init_encryption_version()
+ if not self.is_encrypted():
+ self.load_data(self.raw)
+ else:
+ self._encryption_version = STO_EV_PLAINTEXT
+ # avoid new wallets getting 'upgraded'
+ self.put('seed_version', FINAL_SEED_VERSION)
+
+ def load_data(self, s):
+ try:
+ self.data = json.loads(s)
+ except:
+ try:
+ d = ast.literal_eval(s)
+ labels = d.get('labels', {})
+ except Exception as e:
+ raise IOError("Cannot read wallet file '%s'" % self.path)
+ self.data = {}
+ for key, value in d.items():
+ try:
+ json.dumps(key)
+ json.dumps(value)
+ except:
+ self.print_error('Failed to convert label to json format', key)
+ continue
+ self.data[key] = value
+
+ # check here if I need to load a plugin
+ t = self.get('wallet_type')
+ l = plugin_loaders.get(t)
+ if l: l()
+
+ if not self.manual_upgrades:
+ if self.requires_split():
+ raise WalletFileException("This wallet has multiple accounts and must be split")
+ if self.requires_upgrade():
+ self.upgrade()
+
+ def is_past_initial_decryption(self):
+ """Return if storage is in a usable state for normal operations.
+
+ The value is True exactly
+ if encryption is disabled completely (self.is_encrypted() == False),
+ or if encryption is enabled but the contents have already been decrypted.
+ """
+ return bool(self.data)
+
+ def is_encrypted(self):
+ """Return if storage encryption is currently enabled."""
+ return self.get_encryption_version() != STO_EV_PLAINTEXT
+
+ def is_encrypted_with_user_pw(self):
+ return self.get_encryption_version() == STO_EV_USER_PW
+
+ def is_encrypted_with_hw_device(self):
+ return self.get_encryption_version() == STO_EV_XPUB_PW
+
+ def get_encryption_version(self):
+ """Return the version of encryption used for this storage.
+
+ 0: plaintext / no encryption
+
+ ECIES, private key derived from a password,
+ 1: password is provided by user
+ 2: password is derived from an xpub; used with hw wallets
+ """
+ return self._encryption_version
+
+ def _init_encryption_version(self):
+ try:
+ magic = base64.b64decode(self.raw)[0:4]
+ if magic == b'BIE1':
+ return STO_EV_USER_PW
+ elif magic == b'BIE2':
+ return STO_EV_XPUB_PW
+ else:
+ return STO_EV_PLAINTEXT
+ except:
+ return STO_EV_PLAINTEXT
+
+ @staticmethod
+ def get_eckey_from_password(password):
+ secret = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'), b'', iterations=1024)
+ ec_key = ecc.ECPrivkey.from_arbitrary_size_secret(secret)
+ return ec_key
+
+ def _get_encryption_magic(self):
+ v = self._encryption_version
+ if v == STO_EV_USER_PW:
+ return b'BIE1'
+ elif v == STO_EV_XPUB_PW:
+ return b'BIE2'
+ else:
+ raise WalletFileException('no encryption magic for version: %s' % v)
+
+ def decrypt(self, password):
+ ec_key = self.get_eckey_from_password(password)
+ if self.raw:
+ enc_magic = self._get_encryption_magic()
+ s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic))
+ else:
+ s = None
+ self.pubkey = ec_key.get_public_key_hex()
+ s = s.decode('utf8')
+ self.load_data(s)
+
+ def encrypt_before_writing(self, plaintext: str) -> str:
+ s = plaintext
+ if self.pubkey:
+ s = bytes(s, 'utf8')
+ c = zlib.compress(s)
+ enc_magic = self._get_encryption_magic()
+ public_key = ecc.ECPubkey(bfh(self.pubkey))
+ s = public_key.encrypt_message(c, enc_magic)
+ s = s.decode('utf8')
+ return s
+
+ def check_password(self, password):
+ """Raises an InvalidPassword exception on invalid password"""
+ if not self.is_encrypted():
+ return
+ if self.pubkey and self.pubkey != self.get_eckey_from_password(password).get_public_key_hex():
+ raise InvalidPassword()
+
+ def set_keystore_encryption(self, enable):
+ self.put('use_encryption', enable)
+
+ def set_password(self, password, enc_version=None):
+ """Set a password to be used for encrypting this storage."""
+ if enc_version is None:
+ enc_version = self._encryption_version
+ if password and enc_version != STO_EV_PLAINTEXT:
+ ec_key = self.get_eckey_from_password(password)
+ self.pubkey = ec_key.get_public_key_hex()
+ self._encryption_version = enc_version
+ else:
+ self.pubkey = None
+ self._encryption_version = STO_EV_PLAINTEXT
+ # make sure next storage.write() saves changes
+ with self.db_lock:
+ self.modified = True
+
+ def requires_split(self):
+ d = self.get('accounts', {})
+ return len(d) > 1
+
+ def split_accounts(storage):
+ result = []
+ # backward compatibility with old wallets
+ d = storage.get('accounts', {})
+ if len(d) < 2:
+ return
+ wallet_type = storage.get('wallet_type')
+ if wallet_type == 'old':
+ assert len(d) == 2
+ storage1 = WalletStorage(storage.path + '.deterministic')
+ storage1.data = copy.deepcopy(storage.data)
+ storage1.put('accounts', {'0': d['0']})
+ storage1.upgrade()
+ storage1.write()
+ storage2 = WalletStorage(storage.path + '.imported')
+ storage2.data = copy.deepcopy(storage.data)
+ storage2.put('accounts', {'/x': d['/x']})
+ storage2.put('seed', None)
+ storage2.put('seed_version', None)
+ storage2.put('master_public_key', None)
+ storage2.put('wallet_type', 'imported')
+ storage2.upgrade()
+ storage2.write()
+ result = [storage1.path, storage2.path]
+ elif wallet_type in ['bip44', 'trezor', 'keepkey', 'ledger', 'btchip', 'digitalbitbox', 'safe_t']:
+ mpk = storage.get('master_public_keys')
+ for k in d.keys():
+ i = int(k)
+ x = d[k]
+ if x.get("pending"):
+ continue
+ xpub = mpk["x/%d'"%i]
+ new_path = storage.path + '.' + k
+ storage2 = WalletStorage(new_path)
+ storage2.data = copy.deepcopy(storage.data)
+ # save account, derivation and xpub at index 0
+ storage2.put('accounts', {'0': x})
+ storage2.put('master_public_keys', {"x/0'": xpub})
+ storage2.put('derivation', bip44_derivation(k))
+ storage2.upgrade()
+ storage2.write()
+ result.append(new_path)
+ else:
+ raise WalletFileException("This wallet has multiple accounts and must be split")
+ return result
+
+ def requires_upgrade(self):
+ return self.file_exists() and self.get_seed_version() < FINAL_SEED_VERSION
+
+ @profiler
+ def upgrade(self):
+ self.print_error('upgrading wallet format')
+
+ self.convert_imported()
+ self.convert_wallet_type()
+ self.convert_account()
+ self.convert_version_13_b()
+ self.convert_version_14()
+ self.convert_version_15()
+ self.convert_version_16()
+ self.convert_version_17()
+ self.convert_version_18()
+
+ self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
+ self.write()
+
+ def convert_wallet_type(self):
+ if not self._is_upgrade_method_needed(0, 13):
+ return
+
+ wallet_type = self.get('wallet_type')
+ if wallet_type == 'btchip': wallet_type = 'ledger'
+ if self.get('keystore') or self.get('x1/') or wallet_type=='imported':
+ return False
+ assert not self.requires_split()
+ seed_version = self.get_seed_version()
+ seed = self.get('seed')
+ xpubs = self.get('master_public_keys')
+ xprvs = self.get('master_private_keys', {})
+ mpk = self.get('master_public_key')
+ keypairs = self.get('keypairs')
+ key_type = self.get('key_type')
+ if seed_version == OLD_SEED_VERSION or wallet_type == 'old':
+ d = {
+ 'type': 'old',
+ 'seed': seed,
+ 'mpk': mpk,
+ }
+ self.put('wallet_type', 'standard')
+ self.put('keystore', d)
+
+ elif key_type == 'imported':
+ d = {
+ 'type': 'imported',
+ 'keypairs': keypairs,
+ }
+ self.put('wallet_type', 'standard')
+ self.put('keystore', d)
+
+ elif wallet_type in ['xpub', 'standard']:
+ xpub = xpubs["x/"]
+ xprv = xprvs.get("x/")
+ d = {
+ 'type': 'bip32',
+ 'xpub': xpub,
+ 'xprv': xprv,
+ 'seed': seed,
+ }
+ self.put('wallet_type', 'standard')
+ self.put('keystore', d)
+
+ elif wallet_type in ['bip44']:
+ xpub = xpubs["x/0'"]
+ xprv = xprvs.get("x/0'")
+ d = {
+ 'type': 'bip32',
+ 'xpub': xpub,
+ 'xprv': xprv,
+ }
+ self.put('wallet_type', 'standard')
+ self.put('keystore', d)
+
+ elif wallet_type in ['trezor', 'keepkey', 'ledger', 'digitalbitbox', 'safe_t']:
+ xpub = xpubs["x/0'"]
+ derivation = self.get('derivation', bip44_derivation(0))
+ d = {
+ 'type': 'hardware',
+ 'hw_type': wallet_type,
+ 'xpub': xpub,
+ 'derivation': derivation,
+ }
+ self.put('wallet_type', 'standard')
+ self.put('keystore', d)
+
+ elif (wallet_type == '2fa') or multisig_type(wallet_type):
+ for key in xpubs.keys():
+ d = {
+ 'type': 'bip32',
+ 'xpub': xpubs[key],
+ 'xprv': xprvs.get(key),
+ }
+ if key == 'x1/' and seed:
+ d['seed'] = seed
+ self.put(key, d)
+ else:
+ raise WalletFileException('Unable to tell wallet type. Is this even a wallet file?')
+ # remove junk
+ self.put('master_public_key', None)
+ self.put('master_public_keys', None)
+ self.put('master_private_keys', None)
+ self.put('derivation', None)
+ self.put('seed', None)
+ self.put('keypairs', None)
+ self.put('key_type', None)
+
+ def convert_version_13_b(self):
+ # version 13 is ambiguous, and has an earlier and a later structure
+ if not self._is_upgrade_method_needed(0, 13):
+ return
+
+ if self.get('wallet_type') == 'standard':
+ if self.get('keystore').get('type') == 'imported':
+ pubkeys = self.get('keystore').get('keypairs').keys()
+ d = {'change': []}
+ receiving_addresses = []
+ for pubkey in pubkeys:
+ addr = bitcoin.pubkey_to_address('p2pkh', pubkey)
+ receiving_addresses.append(addr)
+ d['receiving'] = receiving_addresses
+ self.put('addresses', d)
+ self.put('pubkeys', None)
+
+ self.put('seed_version', 13)
+
+ def convert_version_14(self):
+ # convert imported wallets for 3.0
+ if not self._is_upgrade_method_needed(13, 13):
+ return
+
+ if self.get('wallet_type') =='imported':
+ addresses = self.get('addresses')
+ if type(addresses) is list:
+ addresses = dict([(x, None) for x in addresses])
+ self.put('addresses', addresses)
+ elif self.get('wallet_type') == 'standard':
+ if self.get('keystore').get('type')=='imported':
+ addresses = set(self.get('addresses').get('receiving'))
+ pubkeys = self.get('keystore').get('keypairs').keys()
+ assert len(addresses) == len(pubkeys)
+ d = {}
+ for pubkey in pubkeys:
+ addr = bitcoin.pubkey_to_address('p2pkh', pubkey)
+ assert addr in addresses
+ d[addr] = {
+ 'pubkey': pubkey,
+ 'redeem_script': None,
+ 'type': 'p2pkh'
+ }
+ self.put('addresses', d)
+ self.put('pubkeys', None)
+ self.put('wallet_type', 'imported')
+ self.put('seed_version', 14)
+
+ def convert_version_15(self):
+ if not self._is_upgrade_method_needed(14, 14):
+ return
+ if self.get('seed_type') == 'segwit':
+ # should not get here; get_seed_version should have caught this
+ raise Exception('unsupported derivation (development segwit, v14)')
+ self.put('seed_version', 15)
+
+ def convert_version_16(self):
+ # fixes issue #3193 for Imported_Wallets with addresses
+ # also, previous versions allowed importing any garbage as an address
+ # which we now try to remove, see pr #3191
+ if not self._is_upgrade_method_needed(15, 15):
+ return
+
+ def remove_address(addr):
+ def remove_from_dict(dict_name):
+ d = self.get(dict_name, None)
+ if d is not None:
+ d.pop(addr, None)
+ self.put(dict_name, d)
+
+ def remove_from_list(list_name):
+ lst = self.get(list_name, None)
+ if lst is not None:
+ s = set(lst)
+ s -= {addr}
+ self.put(list_name, list(s))
+
+ # note: we don't remove 'addr' from self.get('addresses')
+ remove_from_dict('addr_history')
+ remove_from_dict('labels')
+ remove_from_dict('payment_requests')
+ remove_from_list('frozen_addresses')
+
+ if self.get('wallet_type') == 'imported':
+ addresses = self.get('addresses')
+ assert isinstance(addresses, dict)
+ addresses_new = dict()
+ for address, details in addresses.items():
+ if not bitcoin.is_address(address):
+ remove_address(address)
+ continue
+ if details is None:
+ addresses_new[address] = {}
+ else:
+ addresses_new[address] = details
+ self.put('addresses', addresses_new)
+
+ self.put('seed_version', 16)
+
+ def convert_version_17(self):
+ # delete pruned_txo; construct spent_outpoints
+ if not self._is_upgrade_method_needed(16, 16):
+ return
+
+ self.put('pruned_txo', None)
+
+ from .transaction import Transaction
+ transactions = self.get('transactions', {}) # txid -> raw_tx
+ spent_outpoints = defaultdict(dict)
+ for txid, raw_tx in transactions.items():
+ tx = Transaction(raw_tx)
+ for txin in tx.inputs():
+ if txin['type'] == 'coinbase':
+ continue
+ prevout_hash = txin['prevout_hash']
+ prevout_n = txin['prevout_n']
+ spent_outpoints[prevout_hash][prevout_n] = txid
+ self.put('spent_outpoints', spent_outpoints)
+
+ self.put('seed_version', 17)
+
+ def convert_version_18(self):
+ # delete verified_tx3 as its structure changed
+ if not self._is_upgrade_method_needed(17, 17):
+ return
+
+ self.put('verified_tx3', None)
+
+ self.put('seed_version', 18)
+
+ def convert_imported(self):
+ if not self._is_upgrade_method_needed(0, 13):
+ return
+
+ # '/x' is the internal ID for imported accounts
+ d = self.get('accounts', {}).get('/x', {}).get('imported',{})
+ if not d:
+ return False
+ addresses = []
+ keypairs = {}
+ for addr, v in d.items():
+ pubkey, privkey = v
+ if privkey:
+ keypairs[pubkey] = privkey
+ else:
+ addresses.append(addr)
+ if addresses and keypairs:
+ raise WalletFileException('mixed addresses and privkeys')
+ elif addresses:
+ self.put('addresses', addresses)
+ self.put('accounts', None)
+ elif keypairs:
+ self.put('wallet_type', 'standard')
+ self.put('key_type', 'imported')
+ self.put('keypairs', keypairs)
+ self.put('accounts', None)
+ else:
+ raise WalletFileException('no addresses or privkeys')
+
+ def convert_account(self):
+ if not self._is_upgrade_method_needed(0, 13):
+ return
+
+ self.put('accounts', None)
+
+ def _is_upgrade_method_needed(self, min_version, max_version):
+ cur_version = self.get_seed_version()
+ if cur_version > max_version:
+ return False
+ elif cur_version < min_version:
+ raise WalletFileException(
+ 'storage upgrade: unexpected version {} (should be {}-{})'
+ .format(cur_version, min_version, max_version))
+ else:
+ return True
+
+ def get_action(self):
+ action = run_hook('get_action', self)
+ if self.file_exists() and self.requires_upgrade():
+ if action:
+ raise WalletFileException('Incomplete wallet files cannot be upgraded.')
+ return 'upgrade_storage'
+ if action:
+ return action
+ if not self.file_exists():
+ return 'new'
+
+ def get_seed_version(self):
+ seed_version = self.get('seed_version')
+ if not seed_version:
+ seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION
+ if seed_version > FINAL_SEED_VERSION:
+ raise WalletFileException('This version of Electrum is too old to open this wallet.\n'
+ '(highest supported storage version: {}, version of this file: {})'
+ .format(FINAL_SEED_VERSION, seed_version))
+ if seed_version==14 and self.get('seed_type') == 'segwit':
+ self.raise_unsupported_version(seed_version)
+ if seed_version >=12:
+ return seed_version
+ if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]:
+ self.raise_unsupported_version(seed_version)
+ return seed_version
+
+ def raise_unsupported_version(self, seed_version):
+ msg = "Your wallet has an unsupported seed version."
+ msg += '\n\nWallet file: %s' % os.path.abspath(self.path)
+ if seed_version in [5, 7, 8, 9, 10, 14]:
+ msg += "\n\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
+ if seed_version == 6:
+ # version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog
+ msg += '\n\nThis file was created because of a bug in version 1.9.8.'
+ if self.get('master_public_keys') is None and self.get('master_private_keys') is None and self.get('imported_keys') is None:
+ # pbkdf2 (at that time an additional dependency) was not included with the binaries, and wallet creation aborted.
+ msg += "\nIt does not contain any keys, and can safely be removed."
+ else:
+ # creation was complete if electrum was run from source
+ msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet."
+ raise WalletFileException(msg)
diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py
new file mode 100644
index 000000000..48635df81
--- /dev/null
+++ b/electrum/synchronizer.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from threading import Lock
+import hashlib
+
+# from .bitcoin import Hash, hash_encode
+from .transaction import Transaction
+from .util import ThreadJob, bh2u
+
+
+class Synchronizer(ThreadJob):
+ '''The synchronizer keeps the wallet up-to-date with its set of
+ addresses and their transactions. It subscribes over the network
+ to wallet addresses, gets the wallet to generate new addresses
+ when necessary, requests the transaction history of any addresses
+ we don't have the full history of, and requests binary transaction
+ data of any transactions the wallet doesn't have.
+
+ External interface: __init__() and add() member functions.
+ '''
+
+ def __init__(self, wallet, network):
+ self.wallet = wallet
+ self.network = network
+ self.new_addresses = set()
+ # Entries are (tx_hash, tx_height) tuples
+ self.requested_tx = {}
+ self.requested_histories = {}
+ self.requested_addrs = set()
+ self.lock = Lock()
+
+ self.initialized = False
+ self.initialize()
+
+ def parse_response(self, response):
+ if response.get('error'):
+ self.print_error("response error:", response)
+ return None, None
+ return response['params'], response['result']
+
+ def is_up_to_date(self):
+ return (not self.requested_tx and not self.requested_histories
+ and not self.requested_addrs)
+
+ def release(self):
+ self.network.unsubscribe(self.on_address_status)
+
+ def add(self, address):
+ '''This can be called from the proxy or GUI threads.'''
+ with self.lock:
+ self.new_addresses.add(address)
+
+ def subscribe_to_addresses(self, addresses):
+ if addresses:
+ self.requested_addrs |= addresses
+ self.network.subscribe_to_addresses(addresses, self.on_address_status)
+
+ def get_status(self, h):
+ if not h:
+ return None
+ status = ''
+ for tx_hash, height in h:
+ status += tx_hash + ':%d:' % height
+ return bh2u(hashlib.sha256(status.encode('ascii')).digest())
+
+ def on_address_status(self, response):
+ if self.wallet.synchronizer is None and self.initialized:
+ return # we have been killed, this was just an orphan callback
+ params, result = self.parse_response(response)
+ if not params:
+ return
+ addr = params[0]
+ history = self.wallet.history.get(addr, [])
+ if self.get_status(history) != result:
+ # note that at this point 'result' can be None;
+ # if we had a history for addr but now the server is telling us
+ # there is no history
+ if addr not in self.requested_histories:
+ self.requested_histories[addr] = result
+ self.network.request_address_history(addr, self.on_address_history)
+ # remove addr from list only after it is added to requested_histories
+ if addr in self.requested_addrs: # Notifications won't be in
+ self.requested_addrs.remove(addr)
+
+ def on_address_history(self, response):
+ if self.wallet.synchronizer is None and self.initialized:
+ return # we have been killed, this was just an orphan callback
+ params, result = self.parse_response(response)
+ if not params:
+ return
+ addr = params[0]
+ try:
+ server_status = self.requested_histories[addr]
+ except KeyError:
+ # note: server_status can be None even if we asked for the history,
+ # so it is not sufficient to test that
+ self.print_error("receiving history (unsolicited)", addr, len(result))
+ return
+ self.print_error("receiving history", addr, len(result))
+ hashes = set(map(lambda item: item['tx_hash'], result))
+ hist = list(map(lambda item: (item['tx_hash'], item['height']), result))
+ # tx_fees
+ tx_fees = [(item['tx_hash'], item.get('fee')) for item in result]
+ tx_fees = dict(filter(lambda x:x[1] is not None, tx_fees))
+ # Check that txids are unique
+ if len(hashes) != len(result):
+ self.print_error("error: server history has non-unique txids: %s"% addr)
+ # Check that the status corresponds to what was announced
+ elif self.get_status(hist) != server_status:
+ self.print_error("error: status mismatch: %s" % addr)
+ else:
+ # Store received history
+ self.wallet.receive_history_callback(addr, hist, tx_fees)
+ # Request transactions we don't have
+ self.request_missing_txs(hist)
+ # Remove request; this allows up_to_date to be True
+ self.requested_histories.pop(addr)
+
+ def on_tx_response(self, response):
+ if self.wallet.synchronizer is None and self.initialized:
+ return # we have been killed, this was just an orphan callback
+ params, result = self.parse_response(response)
+ if not params:
+ return
+ tx_hash = params[0]
+ tx = Transaction(result)
+ try:
+ tx.deserialize()
+ except Exception:
+ self.print_msg("cannot deserialize transaction, skipping", tx_hash)
+ return
+ if tx_hash != tx.txid():
+ self.print_error("received tx does not match expected txid ({} != {})"
+ .format(tx_hash, tx.txid()))
+ return
+ tx_height = self.requested_tx.pop(tx_hash)
+ self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
+ self.print_error("received tx %s height: %d bytes: %d" %
+ (tx_hash, tx_height, len(tx.raw)))
+ # callbacks
+ self.network.trigger_callback('new_transaction', tx)
+ if not self.requested_tx:
+ self.network.trigger_callback('updated')
+
+ def request_missing_txs(self, hist):
+ # "hist" is a list of [tx_hash, tx_height] lists
+ transaction_hashes = []
+ for tx_hash, tx_height in hist:
+ if tx_hash in self.requested_tx:
+ continue
+ if tx_hash in self.wallet.transactions:
+ continue
+ transaction_hashes.append(tx_hash)
+ self.requested_tx[tx_hash] = tx_height
+
+ self.network.get_transactions(transaction_hashes, self.on_tx_response)
+
+ def initialize(self):
+ '''Check the initial state of the wallet. Subscribe to all its
+ addresses, and request any transactions in its address history
+ we don't have.
+ '''
+ for history in self.wallet.history.values():
+ # Old electrum servers returned ['*'] when all history for
+ # the address was pruned. This no longer happens but may
+ # remain in old wallets.
+ if history == ['*']:
+ continue
+ self.request_missing_txs(history)
+
+ if self.requested_tx:
+ self.print_error("missing tx", self.requested_tx)
+ self.subscribe_to_addresses(set(self.wallet.get_addresses()))
+ self.initialized = True
+
+ def run(self):
+ '''Called from the network proxy thread main loop.'''
+ # 1. Create new addresses
+ self.wallet.synchronize()
+
+ # 2. Subscribe to new addresses
+ with self.lock:
+ addresses = self.new_addresses
+ self.new_addresses = set()
+ self.subscribe_to_addresses(addresses)
+
+ # 3. Detect if situation has changed
+ up_to_date = self.is_up_to_date()
+ if up_to_date != self.wallet.is_up_to_date():
+ self.wallet.set_up_to_date(up_to_date)
+ self.network.trigger_callback('updated')
diff --git a/electrum/tests/__init__.py b/electrum/tests/__init__.py
new file mode 100644
index 000000000..7d1d336d3
--- /dev/null
+++ b/electrum/tests/__init__.py
@@ -0,0 +1,38 @@
+import unittest
+import threading
+
+from electrum import constants
+
+
+# Set this locally to make the test suite run faster.
+# If set, unit tests that would normally test functions with multiple implementations,
+# will only be run once, using the fastest implementation.
+# e.g. libsecp256k1 vs python-ecdsa. pycryptodomex vs pyaes.
+FAST_TESTS = False
+
+
+# some unit tests are modifying globals; sorry.
+class SequentialTestCase(unittest.TestCase):
+
+ test_lock = threading.Lock()
+
+ def setUp(self):
+ super().setUp()
+ self.test_lock.acquire()
+
+ def tearDown(self):
+ super().tearDown()
+ self.test_lock.release()
+
+
+class TestCaseForTestnet(SequentialTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ constants.set_testnet()
+
+ @classmethod
+ def tearDownClass(cls):
+ super().tearDownClass()
+ constants.set_mainnet()
diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py
new file mode 100644
index 000000000..2575ec78f
--- /dev/null
+++ b/electrum/tests/test_bitcoin.py
@@ -0,0 +1,766 @@
+import base64
+import unittest
+import sys
+
+from electrum.bitcoin import (
+ public_key_to_p2pkh,
+ bip32_root, bip32_public_derivation, bip32_private_derivation,
+ Hash, address_from_private_key,
+ is_address, is_private_key, xpub_from_xprv, is_new_seed, is_old_seed,
+ var_int, op_push, address_to_script,
+ deserialize_privkey, serialize_privkey, is_segwit_address,
+ is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub,
+ xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check,
+ script_num_to_hex, push_script, add_number_to_script, int_to_hex, convert_bip32_path_to_list_of_uint32)
+from electrum import ecc, crypto, constants
+from electrum.ecc import number_to_string, string_to_number
+from electrum.transaction import opcodes
+from electrum.util import bfh, bh2u
+from electrum.storage import WalletStorage
+from electrum.keystore import xtype_from_derivation
+
+from electrum import ecc_fast
+
+from . import SequentialTestCase
+from . import TestCaseForTestnet
+from . import FAST_TESTS
+
+
+try:
+ import ecdsa
+except ImportError:
+ sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
+
+
+def needs_test_with_all_ecc_implementations(func):
+ """Function decorator to run a unit test twice:
+ once when libsecp256k1 is not available, once when it is.
+
+ NOTE: this is inherently sequential;
+ tests running in parallel would break things
+ """
+ def run_test(*args, **kwargs):
+ if FAST_TESTS: # if set, only run tests once, using fastest implementation
+ func(*args, **kwargs)
+ return
+ ecc_fast.undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
+ try:
+ # first test without libsecp
+ func(*args, **kwargs)
+ finally:
+ ecc_fast.do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
+ # if libsecp is not available, we are done
+ if not ecc_fast._libsecp256k1:
+ return
+ # if libsecp is available, test again now
+ func(*args, **kwargs)
+ return run_test
+
+
+def needs_test_with_all_aes_implementations(func):
+ """Function decorator to run a unit test twice:
+ once when pycryptodomex is not available, once when it is.
+
+ NOTE: this is inherently sequential;
+ tests running in parallel would break things
+ """
+ def run_test(*args, **kwargs):
+ if FAST_TESTS: # if set, only run tests once, using fastest implementation
+ func(*args, **kwargs)
+ return
+ _aes = crypto.AES
+ crypto.AES = None
+ try:
+ # first test without pycryptodomex
+ func(*args, **kwargs)
+ finally:
+ crypto.AES = _aes
+ # if pycryptodomex is not available, we are done
+ if not _aes:
+ return
+ # if pycryptodomex is available, test again now
+ func(*args, **kwargs)
+ return run_test
+
+
+class Test_bitcoin(SequentialTestCase):
+
+ def test_libsecp256k1_is_available(self):
+ # we want the unit testing framework to test with libsecp256k1 available.
+ self.assertTrue(bool(ecc_fast._libsecp256k1))
+
+ def test_pycryptodomex_is_available(self):
+ # we want the unit testing framework to test with pycryptodomex available.
+ self.assertTrue(bool(crypto.AES))
+
+ @needs_test_with_all_aes_implementations
+ @needs_test_with_all_ecc_implementations
+ def test_crypto(self):
+ for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]:
+ self._do_test_crypto(message)
+
+ def _do_test_crypto(self, message):
+ G = ecc.generator()
+ _r = G.order()
+ pvk = ecdsa.util.randrange(_r)
+
+ Pub = pvk*G
+ pubkey_c = Pub.get_public_key_bytes(True)
+ #pubkey_u = point_to_ser(Pub,False)
+ addr_c = public_key_to_p2pkh(pubkey_c)
+
+ #print "Private key ", '%064x'%pvk
+ eck = ecc.ECPrivkey(number_to_string(pvk,_r))
+
+ #print "Compressed public key ", pubkey_c.encode('hex')
+ enc = ecc.ECPubkey(pubkey_c).encrypt_message(message)
+ dec = eck.decrypt_message(enc)
+ self.assertEqual(message, dec)
+
+ #print "Uncompressed public key", pubkey_u.encode('hex')
+ #enc2 = EC_KEY.encrypt_message(message, pubkey_u)
+ dec2 = eck.decrypt_message(enc)
+ self.assertEqual(message, dec2)
+
+ signature = eck.sign_message(message, True)
+ #print signature
+ eck.verify_message_for_address(signature, message)
+
+ @needs_test_with_all_ecc_implementations
+ def test_ecc_sanity(self):
+ G = ecc.generator()
+ n = G.order()
+ self.assertEqual(ecc.CURVE_ORDER, n)
+ inf = n * G
+ self.assertEqual(ecc.point_at_infinity(), inf)
+ self.assertTrue(inf.is_at_infinity())
+ self.assertFalse(G.is_at_infinity())
+ self.assertEqual(11 * G, 7 * G + 4 * G)
+ self.assertEqual((n + 2) * G, 2 * G)
+ self.assertEqual((n - 2) * G, -2 * G)
+ A = (n - 2) * G
+ B = (n - 1) * G
+ C = n * G
+ D = (n + 1) * G
+ self.assertFalse(A.is_at_infinity())
+ self.assertFalse(B.is_at_infinity())
+ self.assertTrue(C.is_at_infinity())
+ self.assertTrue((C * 5).is_at_infinity())
+ self.assertFalse(D.is_at_infinity())
+ self.assertEqual(inf, C)
+ self.assertEqual(inf, A + 2 * G)
+ self.assertEqual(inf, D + (-1) * G)
+ self.assertNotEqual(A, B)
+
+ @needs_test_with_all_ecc_implementations
+ def test_msg_signing(self):
+ msg1 = b'Chancellor on brink of second bailout for banks'
+ msg2 = b'Electrum'
+
+ def sign_message_with_wif_privkey(wif_privkey, msg):
+ txin_type, privkey, compressed = deserialize_privkey(wif_privkey)
+ key = ecc.ECPrivkey(privkey)
+ return key.sign_message(msg, compressed)
+
+ sig1 = sign_message_with_wif_privkey(
+ 'L1TnU2zbNaAqMoVh65Cyvmcjzbrj41Gs9iTLcWbpJCMynXuap6UN', msg1)
+ addr1 = '15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz'
+ sig2 = sign_message_with_wif_privkey(
+ '5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD', msg2)
+ addr2 = '1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6'
+
+ sig1_b64 = base64.b64encode(sig1)
+ sig2_b64 = base64.b64encode(sig2)
+
+ self.assertEqual(sig1_b64, b'H/9jMOnj4MFbH3d7t4yCQ9i7DgZU/VZ278w3+ySv2F4yIsdqjsc5ng3kmN8OZAThgyfCZOQxZCWza9V5XzlVY0Y=')
+ self.assertEqual(sig2_b64, b'G84dmJ8TKIDKMT9qBRhpX2sNmR0y5t+POcYnFFJCs66lJmAs3T8A6Sbpx7KA6yTQ9djQMabwQXRrDomOkIKGn18=')
+
+ self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg1))
+ self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg2))
+
+ self.assertFalse(ecc.verify_message_with_address(addr1, b'wrong', msg1))
+ self.assertFalse(ecc.verify_message_with_address(addr1, sig2, msg1))
+
+ @needs_test_with_all_aes_implementations
+ @needs_test_with_all_ecc_implementations
+ def test_decrypt_message(self):
+ key = WalletStorage.get_eckey_from_password('pw123')
+ self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQMDFtgT3zWSQsa+Uie8H/WvfUjlu9UN9OJtTt3KlgKeSTi6SQfuhcg1uIz9hp3WIUOFGTLr4RNQBdjPNqzXwhkcPi2Xsbiw6UCNJncVPJ6QBg=='))
+ self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQKXOXbylOQTSMGfo4MFRwivAxeEEkewWQrpdYTzjPhqjHcGBJwdIhB7DyRfRQihuXx1y0ZLLv7XxLzrILzkl/H4YUtZB4uWjuOAcmxQH4i/Og=='))
+ self.assertEqual(b'hey_there' * 100, key.decrypt_message(b'QklFMQLOOsabsXtGQH8edAa6VOUa5wX8/DXmxX9NyHoAx1a5bWgllayGRVPeI2bf0ZdWK0tfal0ap0ZIVKbd2eOJybqQkILqT6E1/Syzq0Zicyb/AA1eZNkcX5y4gzloxinw00ubCA8M7gcUjJpOqbnksATcJ5y2YYXcHMGGfGurWu6uJ/UyrNobRidWppRMW5yR9/6utyNvT6OHIolCMEf7qLcmtneoXEiz51hkRdZS7weNf9mGqSbz9a2NL3sdh1A0feHIjAZgcCKcAvksNUSauf0/FnIjzTyPRpjRDMeDC8Ci3sGiuO3cvpWJwhZfbjcS26KmBv2CHWXfRRNFYOInHZNIXWNAoBB47Il5bGSMd+uXiGr+SQ9tNvcu+BiJNmFbxYqg+oQ8dGAl1DtvY2wJVY8k7vO9BIWSpyIxfGw7EDifhc5vnOmGe016p6a01C3eVGxgl23UYMrP7+fpjOcPmTSF4rk5U5ljEN3MSYqlf1QEv0OqlI9q1TwTK02VBCjMTYxDHsnt04OjNBkNO8v5uJ4NR+UUDBEp433z53I59uawZ+dbk4v4ZExcl8EGmKm3Gzbal/iJ/F7KQuX2b/ySEhLOFVYFWxK73X1nBvCSK2mC2/8fCw8oI5pmvzJwQhcCKTdEIrz3MMvAHqtPScDUOjzhXxInQOCb3+UBj1PPIdqkYLvZss1TEaBwYZjLkVnK2MBj7BaqT6Rp6+5A/fippUKHsnB6eYMEPR2YgDmCHL+4twxHJG6UWdP3ybaKiiAPy2OHNP6PTZ0HrqHOSJzBSDD+Z8YpaRg29QX3UEWlqnSKaan0VYAsV1VeaN0XFX46/TWO0L5tjhYVXJJYGqo6tIQJymxATLFRF6AZaD1Mwd27IAL04WkmoQoXfO6OFfwdp/shudY/1gBkDBvGPICBPtnqkvhGF+ZF3IRkuPwiFWeXmwBxKHsRx/3+aJu32Ml9+za41zVk2viaxcGqwTc5KMexQFLAUwqhv+aIik7U+5qk/gEVSuRoVkihoweFzKolNF+BknH2oB4rZdPixag5Zje3DvgjsSFlOl69W/67t/Gs8htfSAaHlsB8vWRQr9+v/lxTbrAw+O0E+sYGoObQ4qQMyQshNZEHbpPg63eWiHtJJnrVBvOeIbIHzoLDnMDsWVWZSMzAQ1vhX1H5QLgSEbRlKSliVY03kDkh/Nk/KOn+B2q37Ialq4JcRoIYFGJ8AoYEAD0tRuTqFddIclE75HzwaNG7NyKW1plsa72ciOPwsPJsdd5F0qdSQ3OSKtooTn7uf6dXOc4lDkfrVYRlZ0PX'))
+
+ @needs_test_with_all_aes_implementations
+ @needs_test_with_all_ecc_implementations
+ def test_encrypt_message(self):
+ key = WalletStorage.get_eckey_from_password('secret_password77')
+ msgs = [
+ bytes([0] * 555),
+ b'cannot think of anything funny'
+ ]
+ for plaintext in msgs:
+ ciphertext1 = key.encrypt_message(plaintext)
+ ciphertext2 = key.encrypt_message(plaintext)
+ self.assertEqual(plaintext, key.decrypt_message(ciphertext1))
+ self.assertEqual(plaintext, key.decrypt_message(ciphertext2))
+ self.assertNotEqual(ciphertext1, ciphertext2)
+
+ @needs_test_with_all_ecc_implementations
+ def test_sign_transaction(self):
+ eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'))
+ sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94'))
+ self.assertEqual(bfh('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9'), sig1)
+
+ eckey2 = ecc.ECPrivkey(bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'))
+ sig2 = eckey2.sign_transaction(bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac'))
+ self.assertEqual(bfh('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52'), sig2)
+
+ @needs_test_with_all_aes_implementations
+ def test_aes_homomorphic(self):
+ """Make sure AES is homomorphic."""
+ payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
+ password = u'secret'
+ enc = crypto.pw_encode(payload, password)
+ dec = crypto.pw_decode(enc, password)
+ self.assertEqual(dec, payload)
+
+ @needs_test_with_all_aes_implementations
+ def test_aes_encode_without_password(self):
+ """When not passed a password, pw_encode is noop on the payload."""
+ payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
+ enc = crypto.pw_encode(payload, None)
+ self.assertEqual(payload, enc)
+
+ @needs_test_with_all_aes_implementations
+ def test_aes_deencode_without_password(self):
+ """When not passed a password, pw_decode is noop on the payload."""
+ payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
+ enc = crypto.pw_decode(payload, None)
+ self.assertEqual(payload, enc)
+
+ @needs_test_with_all_aes_implementations
+ def test_aes_decode_with_invalid_password(self):
+ """pw_decode raises an Exception when supplied an invalid password."""
+ payload = u"blah"
+ password = u"uber secret"
+ wrong_password = u"not the password"
+ enc = crypto.pw_encode(payload, password)
+ self.assertRaises(Exception, crypto.pw_decode, enc, wrong_password)
+
+ def test_hash(self):
+ """Make sure the Hash function does sha256 twice"""
+ payload = u"test"
+ expected = b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4'
+
+ result = Hash(payload)
+ self.assertEqual(expected, result)
+
+ def test_int_to_hex(self):
+ self.assertEqual('00', int_to_hex(0, 1))
+ self.assertEqual('ff', int_to_hex(-1, 1))
+ self.assertEqual('00000000', int_to_hex(0, 4))
+ self.assertEqual('01000000', int_to_hex(1, 4))
+ self.assertEqual('7f', int_to_hex(127, 1))
+ self.assertEqual('7f00', int_to_hex(127, 2))
+ self.assertEqual('80', int_to_hex(128, 1))
+ self.assertEqual('80', int_to_hex(-128, 1))
+ self.assertEqual('8000', int_to_hex(128, 2))
+ self.assertEqual('ff', int_to_hex(255, 1))
+ self.assertEqual('ff7f', int_to_hex(32767, 2))
+ self.assertEqual('0080', int_to_hex(-32768, 2))
+ self.assertEqual('ffff', int_to_hex(65535, 2))
+ with self.assertRaises(OverflowError): int_to_hex(256, 1)
+ with self.assertRaises(OverflowError): int_to_hex(-129, 1)
+ with self.assertRaises(OverflowError): int_to_hex(-257, 1)
+ with self.assertRaises(OverflowError): int_to_hex(65536, 2)
+ with self.assertRaises(OverflowError): int_to_hex(-32769, 2)
+
+ def test_var_int(self):
+ for i in range(0xfd):
+ self.assertEqual(var_int(i), "{:02x}".format(i) )
+
+ self.assertEqual(var_int(0xfd), "fdfd00")
+ self.assertEqual(var_int(0xfe), "fdfe00")
+ self.assertEqual(var_int(0xff), "fdff00")
+ self.assertEqual(var_int(0x1234), "fd3412")
+ self.assertEqual(var_int(0xffff), "fdffff")
+ self.assertEqual(var_int(0x10000), "fe00000100")
+ self.assertEqual(var_int(0x12345678), "fe78563412")
+ self.assertEqual(var_int(0xffffffff), "feffffffff")
+ self.assertEqual(var_int(0x100000000), "ff0000000001000000")
+ self.assertEqual(var_int(0x0123456789abcdef), "ffefcdab8967452301")
+
+ def test_op_push(self):
+ self.assertEqual(op_push(0x00), '00')
+ self.assertEqual(op_push(0x12), '12')
+ self.assertEqual(op_push(0x4b), '4b')
+ self.assertEqual(op_push(0x4c), '4c4c')
+ self.assertEqual(op_push(0xfe), '4cfe')
+ self.assertEqual(op_push(0xff), '4cff')
+ self.assertEqual(op_push(0x100), '4d0001')
+ self.assertEqual(op_push(0x1234), '4d3412')
+ self.assertEqual(op_push(0xfffe), '4dfeff')
+ self.assertEqual(op_push(0xffff), '4dffff')
+ self.assertEqual(op_push(0x10000), '4e00000100')
+ self.assertEqual(op_push(0x12345678), '4e78563412')
+
+ def test_script_num_to_hex(self):
+ # test vectors from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptnum.go#L77
+ self.assertEqual(script_num_to_hex(127), '7f')
+ self.assertEqual(script_num_to_hex(-127), 'ff')
+ self.assertEqual(script_num_to_hex(128), '8000')
+ self.assertEqual(script_num_to_hex(-128), '8080')
+ self.assertEqual(script_num_to_hex(129), '8100')
+ self.assertEqual(script_num_to_hex(-129), '8180')
+ self.assertEqual(script_num_to_hex(256), '0001')
+ self.assertEqual(script_num_to_hex(-256), '0081')
+ self.assertEqual(script_num_to_hex(32767), 'ff7f')
+ self.assertEqual(script_num_to_hex(-32767), 'ffff')
+ self.assertEqual(script_num_to_hex(32768), '008000')
+ self.assertEqual(script_num_to_hex(-32768), '008080')
+
+ def test_push_script(self):
+ # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators
+ self.assertEqual(push_script(''), bh2u(bytes([opcodes.OP_0])))
+ self.assertEqual(push_script('07'), bh2u(bytes([opcodes.OP_7])))
+ self.assertEqual(push_script('10'), bh2u(bytes([opcodes.OP_16])))
+ self.assertEqual(push_script('81'), bh2u(bytes([opcodes.OP_1NEGATE])))
+ self.assertEqual(push_script('11'), '0111')
+ self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42')
+ self.assertEqual(push_script(76 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42')))
+ self.assertEqual(push_script(100 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42')))
+ self.assertEqual(push_script(255 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42')))
+ self.assertEqual(push_script(256 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42')))
+ self.assertEqual(push_script(520 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')))
+
+ def test_add_number_to_script(self):
+ # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#numbers
+ self.assertEqual(add_number_to_script(0), bytes([opcodes.OP_0]))
+ self.assertEqual(add_number_to_script(7), bytes([opcodes.OP_7]))
+ self.assertEqual(add_number_to_script(16), bytes([opcodes.OP_16]))
+ self.assertEqual(add_number_to_script(-1), bytes([opcodes.OP_1NEGATE]))
+ self.assertEqual(add_number_to_script(-127), bfh('01ff'))
+ self.assertEqual(add_number_to_script(-2), bfh('0182'))
+ self.assertEqual(add_number_to_script(17), bfh('0111'))
+ self.assertEqual(add_number_to_script(127), bfh('017f'))
+ self.assertEqual(add_number_to_script(-32767), bfh('02ffff'))
+ self.assertEqual(add_number_to_script(-128), bfh('028080'))
+ self.assertEqual(add_number_to_script(128), bfh('028000'))
+ self.assertEqual(add_number_to_script(32767), bfh('02ff7f'))
+ self.assertEqual(add_number_to_script(-8388607), bfh('03ffffff'))
+ self.assertEqual(add_number_to_script(-32768), bfh('03008080'))
+ self.assertEqual(add_number_to_script(32768), bfh('03008000'))
+ self.assertEqual(add_number_to_script(8388607), bfh('03ffff7f'))
+ self.assertEqual(add_number_to_script(-2147483647), bfh('04ffffffff'))
+ self.assertEqual(add_number_to_script(-8388608 ), bfh('0400008080'))
+ self.assertEqual(add_number_to_script(8388608), bfh('0400008000'))
+ self.assertEqual(add_number_to_script(2147483647), bfh('04ffffff7f'))
+
+ def test_address_to_script(self):
+ # bech32 native segwit
+ # test vectors from BIP-0173
+ self.assertEqual(address_to_script('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'), '0014751e76e8199196d454941c45d1b3a323f1433bd6')
+ self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')
+ self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e')
+ self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323')
+
+ # base58 P2PKH
+ self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')
+ self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')
+
+ # base58 P2SH
+ self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')
+ self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387')
+
+
+class Test_bitcoin_testnet(TestCaseForTestnet):
+
+ def test_address_to_script(self):
+ # bech32 native segwit
+ # test vectors from BIP-0173
+ self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262')
+ self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433')
+
+ # base58 P2PKH
+ self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX'), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac')
+ self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K'), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac')
+
+ # base58 P2SH
+ self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p'), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87')
+ self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87')
+
+
+class Test_xprv_xpub(SequentialTestCase):
+
+ xprv_xpub = (
+ # Taken from test vectors in https://en.bitcoin.it/wiki/BIP_0032_TestVectors
+ {'xprv': 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
+ 'xpub': 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
+ 'xtype': 'standard'},
+ {'xprv': 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7',
+ 'xpub': 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4',
+ 'xtype': 'p2wpkh-p2sh'},
+ {'xprv': 'zprvAWgYBBk7JR8GkraNZJeEodAp2UR1VRWJTXyV1ywuUVs1awUgTiBS1ZTDtLA5F3MFDn1LZzu8dUpSKdT7ToDpvEG6PQu4bJs7zQY47Sd3sEZ',
+ 'xpub': 'zpub6jftahH18ngZyLeqfLBFAm7YaWFVttE9pku5pNMX2qPzTjoq1FVgZMmhjecyB2nqFb31gHE9vNvbaggU6vvWpNZbXEWLLUjYjFqG95LNyT8',
+ 'xtype': 'p2wpkh'},
+ )
+
+ def _do_test_bip32(self, seed, sequence):
+ xprv, xpub = bip32_root(bfh(seed), 'standard')
+ self.assertEqual("m/", sequence[0:2])
+ path = 'm'
+ sequence = sequence[2:]
+ for n in sequence.split('/'):
+ child_path = path + '/' + n
+ if n[-1] != "'":
+ xpub2 = bip32_public_derivation(xpub, path, child_path)
+ xprv, xpub = bip32_private_derivation(xprv, path, child_path)
+ if n[-1] != "'":
+ self.assertEqual(xpub, xpub2)
+ path = child_path
+
+ return xpub, xprv
+
+ @needs_test_with_all_ecc_implementations
+ def test_bip32(self):
+ # see https://en.bitcoin.it/wiki/BIP_0032_TestVectors
+ xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000")
+ self.assertEqual("xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", xpub)
+ self.assertEqual("xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", xprv)
+
+ xpub, xprv = self._do_test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","m/0/2147483647'/1/2147483646'/2")
+ self.assertEqual("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", xpub)
+ self.assertEqual("xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", xprv)
+
+ @needs_test_with_all_ecc_implementations
+ def test_xpub_from_xprv(self):
+ """We can derive the xpub key from a xprv."""
+ for xprv_details in self.xprv_xpub:
+ result = xpub_from_xprv(xprv_details['xprv'])
+ self.assertEqual(result, xprv_details['xpub'])
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_xpub(self):
+ for xprv_details in self.xprv_xpub:
+ xpub = xprv_details['xpub']
+ self.assertTrue(is_xpub(xpub))
+ self.assertFalse(is_xpub('xpub1nval1d'))
+ self.assertFalse(is_xpub('xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG'))
+
+ @needs_test_with_all_ecc_implementations
+ def test_xpub_type(self):
+ for xprv_details in self.xprv_xpub:
+ xpub = xprv_details['xpub']
+ self.assertEqual(xprv_details['xtype'], xpub_type(xpub))
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_xprv(self):
+ for xprv_details in self.xprv_xpub:
+ xprv = xprv_details['xprv']
+ self.assertTrue(is_xprv(xprv))
+ self.assertFalse(is_xprv('xprv1nval1d'))
+ self.assertFalse(is_xprv('xprv661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG'))
+
+ def test_is_bip32_derivation(self):
+ self.assertTrue(is_bip32_derivation("m/0'/1"))
+ self.assertTrue(is_bip32_derivation("m/0'/0'"))
+ self.assertTrue(is_bip32_derivation("m/44'/0'/0'/0/0"))
+ self.assertTrue(is_bip32_derivation("m/49'/0'/0'/0/0"))
+ self.assertFalse(is_bip32_derivation("mmmmmm"))
+ self.assertFalse(is_bip32_derivation("n/"))
+ self.assertFalse(is_bip32_derivation(""))
+ self.assertFalse(is_bip32_derivation("m/q8462"))
+
+ def test_convert_bip32_path_to_list_of_uint32(self):
+ self.assertEqual([0, 0x80000001, 0x80000001], convert_bip32_path_to_list_of_uint32("m/0/-1/1'"))
+ self.assertEqual([], convert_bip32_path_to_list_of_uint32("m/"))
+ self.assertEqual([2147483692, 2147488889, 221], convert_bip32_path_to_list_of_uint32("m/44'/5241'/221"))
+
+ def test_xtype_from_derivation(self):
+ self.assertEqual('standard', xtype_from_derivation("m/44'"))
+ self.assertEqual('standard', xtype_from_derivation("m/44'/"))
+ self.assertEqual('standard', xtype_from_derivation("m/44'/0'/0'"))
+ self.assertEqual('standard', xtype_from_derivation("m/44'/5241'/221"))
+ self.assertEqual('standard', xtype_from_derivation("m/45'"))
+ self.assertEqual('standard', xtype_from_derivation("m/45'/56165/271'"))
+ self.assertEqual('p2wpkh-p2sh', xtype_from_derivation("m/49'"))
+ self.assertEqual('p2wpkh-p2sh', xtype_from_derivation("m/49'/134"))
+ self.assertEqual('p2wpkh', xtype_from_derivation("m/84'"))
+ self.assertEqual('p2wpkh', xtype_from_derivation("m/84'/112'/992/112/33'/0/2"))
+ self.assertEqual('p2wsh-p2sh', xtype_from_derivation("m/48'/0'/0'/1'"))
+ self.assertEqual('p2wsh-p2sh', xtype_from_derivation("m/48'/0'/0'/1'/52112/52'"))
+ self.assertEqual('p2wsh-p2sh', xtype_from_derivation("m/48'/9'/2'/1'"))
+ self.assertEqual('p2wsh', xtype_from_derivation("m/48'/0'/0'/2'"))
+ self.assertEqual('p2wsh', xtype_from_derivation("m/48'/1'/0'/2'/77'/0"))
+
+ def test_version_bytes(self):
+ xprv_headers_b58 = {
+ 'standard': 'xprv',
+ 'p2wpkh-p2sh': 'yprv',
+ 'p2wsh-p2sh': 'Yprv',
+ 'p2wpkh': 'zprv',
+ 'p2wsh': 'Zprv',
+ }
+ xpub_headers_b58 = {
+ 'standard': 'xpub',
+ 'p2wpkh-p2sh': 'ypub',
+ 'p2wsh-p2sh': 'Ypub',
+ 'p2wpkh': 'zpub',
+ 'p2wsh': 'Zpub',
+ }
+ for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items():
+ xkey_header_bytes = bfh("%08x" % xkey_header_bytes)
+ xkey_bytes = xkey_header_bytes + bytes([0] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype]))
+
+ xkey_bytes = xkey_header_bytes + bytes([255] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype]))
+
+ for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items():
+ xkey_header_bytes = bfh("%08x" % xkey_header_bytes)
+ xkey_bytes = xkey_header_bytes + bytes([0] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
+
+ xkey_bytes = xkey_header_bytes + bytes([255] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
+
+
+class Test_xprv_xpub_testnet(TestCaseForTestnet):
+
+ def test_version_bytes(self):
+ xprv_headers_b58 = {
+ 'standard': 'tprv',
+ 'p2wpkh-p2sh': 'uprv',
+ 'p2wsh-p2sh': 'Uprv',
+ 'p2wpkh': 'vprv',
+ 'p2wsh': 'Vprv',
+ }
+ xpub_headers_b58 = {
+ 'standard': 'tpub',
+ 'p2wpkh-p2sh': 'upub',
+ 'p2wsh-p2sh': 'Upub',
+ 'p2wpkh': 'vpub',
+ 'p2wsh': 'Vpub',
+ }
+ for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items():
+ xkey_header_bytes = bfh("%08x" % xkey_header_bytes)
+ xkey_bytes = xkey_header_bytes + bytes([0] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype]))
+
+ xkey_bytes = xkey_header_bytes + bytes([255] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype]))
+
+ for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items():
+ xkey_header_bytes = bfh("%08x" % xkey_header_bytes)
+ xkey_bytes = xkey_header_bytes + bytes([0] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
+
+ xkey_bytes = xkey_header_bytes + bytes([255] * 74)
+ xkey_b58 = EncodeBase58Check(xkey_bytes)
+ self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
+
+
+class Test_keyImport(SequentialTestCase):
+
+ priv_pub_addr = (
+ {'priv': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6',
+ 'exported_privkey': 'p2pkh:KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6',
+ 'pub': '02c6467b7e621144105ed3e4835b0b4ab7e35266a2ae1c4f8baa19e9ca93452997',
+ 'address': '17azqT8T16coRmWKYFj3UjzJuxiYrYFRBR',
+ 'minikey' : False,
+ 'txin_type': 'p2pkh',
+ 'compressed': True,
+ 'addr_encoding': 'base58',
+ 'scripthash': 'c9aecd1fef8d661a42c560bf75c8163e337099800b8face5ca3d1393a30508a7'},
+ {'priv': 'p2pkh:Kzj8VjwpZ99bQqVeUiRXrKuX9mLr1o6sWxFMCBJn1umC38BMiQTD',
+ 'exported_privkey': 'p2pkh:Kzj8VjwpZ99bQqVeUiRXrKuX9mLr1o6sWxFMCBJn1umC38BMiQTD',
+ 'pub': '0352d78b4b37e0f6d4e164423436f2925fa57817467178eca550a88f2821973c41',
+ 'address': '1GXgZ5Qi6gmXTHVSpUPZLy4Ci2nbfb3ZNb',
+ 'minikey': False,
+ 'txin_type': 'p2pkh',
+ 'compressed': True,
+ 'addr_encoding': 'base58',
+ 'scripthash': 'a9b2a76fc196c553b352186dfcca81fcf323a721cd8431328f8e9d54216818c1'},
+ {'priv': '5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD',
+ 'exported_privkey': 'p2pkh:5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD',
+ 'pub': '04e5fe91a20fac945845a5518450d23405ff3e3e1ce39827b47ee6d5db020a9075422d56a59195ada0035e4a52a238849f68e7a325ba5b2247013e0481c5c7cb3f',
+ 'address': '1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6',
+ 'minikey': False,
+ 'txin_type': 'p2pkh',
+ 'compressed': False,
+ 'addr_encoding': 'base58',
+ 'scripthash': 'f5914651408417e1166f725a5829ff9576d0dbf05237055bf13abd2af7f79473'},
+ {'priv': 'p2pkh:5KhYQCe1xd5g2tqpmmGpUWDpDuTbA8vnpbiCNDwMPAx29WNQYfN',
+ 'exported_privkey': 'p2pkh:5KhYQCe1xd5g2tqpmmGpUWDpDuTbA8vnpbiCNDwMPAx29WNQYfN',
+ 'pub': '048f0431b0776e8210376c81280011c2b68be43194cb00bd47b7e9aa66284b713ce09556cde3fee606051a07613f3c159ef3953b8927c96ae3dae94a6ba4182e0e',
+ 'address': '147kiRHHm9fqeMQSgqf4k35XzuWLP9fmmS',
+ 'minikey': False,
+ 'txin_type': 'p2pkh',
+ 'compressed': False,
+ 'addr_encoding': 'base58',
+ 'scripthash': '6dd2e07ad2de9ba8eec4bbe8467eb53f8845acff0d9e6f5627391acc22ff62df'},
+ {'priv': 'LHJnnvRzsdrTX2j5QeWVsaBkabK7gfMNqNNqxnbBVRaJYfk24iJz',
+ 'exported_privkey': 'p2wpkh-p2sh:Kz9XebiCXL2BZzhYJViiHDzn5iup1povWV8aqstzWU4sz1K5nVva',
+ 'pub': '0279ad237ca0d812fb503ab86f25e15ebd5fa5dd95c193639a8a738dcd1acbad81',
+ 'address': '3GeVJB3oKr7psgKR6BTXSxKtWUkfsHHhk7',
+ 'minikey': False,
+ 'txin_type': 'p2wpkh-p2sh',
+ 'compressed': True,
+ 'addr_encoding': 'base58',
+ 'scripthash': 'd7b04e882fa6b13246829ac552a2b21461d9152eb00f0a6adb58457a3e63d7c5'},
+ {'priv': 'p2wpkh-p2sh:L3CZH1pm87X4bbE6mSGvZnAZ1KcFDRomBudUkrkBG7EZhDtBVXMW',
+ 'exported_privkey': 'p2wpkh-p2sh:L3CZH1pm87X4bbE6mSGvZnAZ1KcFDRomBudUkrkBG7EZhDtBVXMW',
+ 'pub': '0229da20a15b3363b2c28e3c5093c180b56c439df0b968a970366bb1f38435361e',
+ 'address': '3C79goMwT7zSTjXnPoCg6VFGAnUpZAkyus',
+ 'minikey': False,
+ 'txin_type': 'p2wpkh-p2sh',
+ 'compressed': True,
+ 'addr_encoding': 'base58',
+ 'scripthash': '714bf6bfe1083e69539f40d4c7a7dca85d187471b35642e55f20d7e866494cf7'},
+ {'priv': 'L8g5V8kFFeg2WbecahRSdobARbHz2w2STH9S8ePHVSY4fmia7Rsj',
+ 'exported_privkey': 'p2wpkh:Kz6SuyPM5VktY5dr2d2YqdVgBA6LCWkiHqXJaC3BzxnMPSUuYzmF',
+ 'pub': '03e9f948421aaa89415dc5f281a61b60dde12aae3181b3a76cd2d849b164fc6d0b',
+ 'address': 'bc1qqmpt7u5e9hfznljta5gnvhyvfd2kdd0r90hwue',
+ 'minikey': False,
+ 'txin_type': 'p2wpkh',
+ 'compressed': True,
+ 'addr_encoding': 'bech32',
+ 'scripthash': '1929acaaef3a208c715228e9f1ca0318e3a6b9394ab53c8d026137f847ecf97b'},
+ {'priv': 'p2wpkh:KyDWy5WbjLA58Zesh1o8m3pADGdJ3v33DKk4m7h8BD5zDKDmDFwo',
+ 'exported_privkey': 'p2wpkh:KyDWy5WbjLA58Zesh1o8m3pADGdJ3v33DKk4m7h8BD5zDKDmDFwo',
+ 'pub': '038c57657171c1f73e34d5b3971d05867d50221ad94980f7e87cbc2344425e6a1e',
+ 'address': 'bc1qpakeeg4d9ydyjxd8paqrw4xy9htsg532xzxn50',
+ 'minikey': False,
+ 'txin_type': 'p2wpkh',
+ 'compressed': True,
+ 'addr_encoding': 'bech32',
+ 'scripthash': '242f02adde84ebb2a7dd778b2f3a81b3826f111da4d8960d826d7a4b816cb261'},
+ # from http://bitscan.com/articles/security/spotlight-on-mini-private-keys
+ {'priv': 'SzavMBLoXU6kDrqtUVmffv',
+ 'exported_privkey': 'p2pkh:5Kb8kLf9zgWQnogidDA76MzPL6TsZZY36hWXMssSzNydYXYB9KF',
+ 'pub': '04588d202afcc1ee4ab5254c7847ec25b9a135bbda0f2bc69ee1a714749fd77dc9f88ff2a00d7e752d44cbe16e1ebcf0890b76ec7c78886109dee76ccfc8445424',
+ 'address': '1CC3X2gu58d6wXUWMffpuzN9JAfTUWu4Kj',
+ 'minikey': True,
+ 'txin_type': 'p2pkh',
+ 'compressed': False, # this is actually ambiguous... issue #2748
+ 'addr_encoding': 'base58',
+ 'scripthash': '5b07ddfde826f5125ee823900749103cea37808038ecead5505a766a07c34445'},
+ )
+
+ @needs_test_with_all_ecc_implementations
+ def test_public_key_from_private_key(self):
+ for priv_details in self.priv_pub_addr:
+ txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])
+ result = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
+ self.assertEqual(priv_details['pub'], result)
+ self.assertEqual(priv_details['txin_type'], txin_type)
+ self.assertEqual(priv_details['compressed'], compressed)
+
+ @needs_test_with_all_ecc_implementations
+ def test_address_from_private_key(self):
+ for priv_details in self.priv_pub_addr:
+ addr2 = address_from_private_key(priv_details['priv'])
+ self.assertEqual(priv_details['address'], addr2)
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_valid_address(self):
+ for priv_details in self.priv_pub_addr:
+ addr = priv_details['address']
+ self.assertFalse(is_address(priv_details['priv']))
+ self.assertFalse(is_address(priv_details['pub']))
+ self.assertTrue(is_address(addr))
+
+ is_enc_b58 = priv_details['addr_encoding'] == 'base58'
+ self.assertEqual(is_enc_b58, is_b58_address(addr))
+
+ is_enc_bech32 = priv_details['addr_encoding'] == 'bech32'
+ self.assertEqual(is_enc_bech32, is_segwit_address(addr))
+
+ self.assertFalse(is_address("not an address"))
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_private_key(self):
+ for priv_details in self.priv_pub_addr:
+ self.assertTrue(is_private_key(priv_details['priv']))
+ self.assertTrue(is_private_key(priv_details['exported_privkey']))
+ self.assertFalse(is_private_key(priv_details['pub']))
+ self.assertFalse(is_private_key(priv_details['address']))
+ self.assertFalse(is_private_key("not a privkey"))
+
+ @needs_test_with_all_ecc_implementations
+ def test_serialize_privkey(self):
+ for priv_details in self.priv_pub_addr:
+ txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])
+ priv2 = serialize_privkey(privkey, compressed, txin_type)
+ self.assertEqual(priv_details['exported_privkey'], priv2)
+
+ @needs_test_with_all_ecc_implementations
+ def test_address_to_scripthash(self):
+ for priv_details in self.priv_pub_addr:
+ sh = address_to_scripthash(priv_details['address'])
+ self.assertEqual(priv_details['scripthash'], sh)
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_minikey(self):
+ for priv_details in self.priv_pub_addr:
+ minikey = priv_details['minikey']
+ priv = priv_details['priv']
+ self.assertEqual(minikey, is_minikey(priv))
+
+ @needs_test_with_all_ecc_implementations
+ def test_is_compressed(self):
+ for priv_details in self.priv_pub_addr:
+ self.assertEqual(priv_details['compressed'],
+ is_compressed(priv_details['priv']))
+
+
+class Test_seeds(SequentialTestCase):
+ """ Test old and new seeds. """
+
+ mnemonics = {
+ ('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),
+ ('cell dumb heartbeat north boom tease ' * 4, 'old'),
+ ('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),
+ ('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),
+ (' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'),
+ # below seed is actually 'invalid old' as it maps to 33 hex chars
+ ('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),
+ ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),
+ ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),
+ ('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),
+ ('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),
+ (' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'),
+ ('x8', 'standard'),
+ ('science dawn member doll dutch real can brick knife deny drive list', '2fa'),
+ ('science dawn member doll dutch real ca brick knife deny drive list', ''),
+ (' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'),
+ ('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),
+ (' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),
+ ('9dk', 'segwit'),
+ }
+
+ def test_new_seed(self):
+ seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
+ self.assertTrue(is_new_seed(seed))
+
+ seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
+ self.assertFalse(is_new_seed(seed))
+
+ def test_old_seed(self):
+ self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
+ self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
+ self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
+ self.assertFalse(is_old_seed("not a seed"))
+
+ self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
+ self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
+
+ def test_seed_type(self):
+ for seed_words, _type in self.mnemonics:
+ self.assertEqual(_type, seed_type(seed_words), msg=seed_words)
diff --git a/electrum/tests/test_commands.py b/electrum/tests/test_commands.py
new file mode 100644
index 000000000..6aa0ba715
--- /dev/null
+++ b/electrum/tests/test_commands.py
@@ -0,0 +1,33 @@
+import unittest
+from decimal import Decimal
+
+from electrum.commands import Commands
+
+
+class TestCommands(unittest.TestCase):
+
+ def test_setconfig_non_auth_number(self):
+ self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', "7777"))
+ self.assertEqual(7777, Commands._setconfig_normalize_value('rpcport', '7777'))
+ self.assertAlmostEqual(Decimal(2.3), Commands._setconfig_normalize_value('somekey', '2.3'))
+
+ def test_setconfig_non_auth_number_as_string(self):
+ self.assertEqual("7777", Commands._setconfig_normalize_value('somekey', "'7777'"))
+
+ def test_setconfig_non_auth_boolean(self):
+ self.assertEqual(True, Commands._setconfig_normalize_value('show_console_tab', "true"))
+ self.assertEqual(True, Commands._setconfig_normalize_value('show_console_tab', "True"))
+
+ def test_setconfig_non_auth_list(self):
+ self.assertEqual(['file:///var/www/', 'https://electrum.org'],
+ Commands._setconfig_normalize_value('url_rewrite', "['file:///var/www/','https://electrum.org']"))
+ self.assertEqual(['file:///var/www/', 'https://electrum.org'],
+ Commands._setconfig_normalize_value('url_rewrite', '["file:///var/www/","https://electrum.org"]'))
+
+ def test_setconfig_auth(self):
+ self.assertEqual("7777", Commands._setconfig_normalize_value('rpcuser', "7777"))
+ self.assertEqual("7777", Commands._setconfig_normalize_value('rpcuser', '7777'))
+ self.assertEqual("7777", Commands._setconfig_normalize_value('rpcpassword', '7777'))
+ self.assertEqual("2asd", Commands._setconfig_normalize_value('rpcpassword', '2asd'))
+ self.assertEqual("['file:///var/www/','https://electrum.org']",
+ Commands._setconfig_normalize_value('rpcpassword', "['file:///var/www/','https://electrum.org']"))
diff --git a/electrum/tests/test_dnssec.py b/electrum/tests/test_dnssec.py
new file mode 100644
index 000000000..e7e9ac4fb
--- /dev/null
+++ b/electrum/tests/test_dnssec.py
@@ -0,0 +1,41 @@
+import dns
+
+from electrum import dnssec
+
+from . import SequentialTestCase
+from .test_bitcoin import needs_test_with_all_ecc_implementations
+
+
+class TestDnsSec(SequentialTestCase):
+
+ @needs_test_with_all_ecc_implementations
+ def test_python_validate_rrsig_ecdsa(self):
+ rrset = dns.rrset.from_text("getmonero.org.", 3599, 1, 48,
+ "257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d xCjjnopKl+GqJxpVXckHAeF+KkxLbxIL fDLUT0rAK9iUzy1L53eKGQ==",
+ "256 3 13 koPbw9wmYZ7ggcjnQ6ayHyhHaDNMYELK TqT+qRGrZpWSccr/lBcrm10Z1PuQHB3A zhii+sb0PYFkH1ruxLhe5g==")
+ rrsig = dns.rdtypes.ANY.RRSIG.RRSIG.from_text(1, 46,
+ dns.tokenizer.Tokenizer("DNSKEY 13 2 3600 20180612115508 20180413115508 2371 getmonero.org. SSjtP2jCtXPukps7E3kum709xq2TH6Lt Ur32UhE7WKwSUfLTZ4EAoD5g22mi1fpB GDGb30kCMndDVjnHAEBDWw=="))
+ keys = {dns.name.Name([b'getmonero', b'org', b'']): rrset}
+ origin = None
+ now = 1527185178.7842247
+
+ # 'None' means it is valid
+ self.assertEqual(None, dnssec.python_validate_rrsig(rrset, rrsig, keys, origin, now))
+
+ def test_python_validate_rrsig_rsa(self):
+ rrset = dns.rrset.from_text("getmonero.org.", 12698, 1, 43,
+ "2371 13 2 3b7f818a879ecb9931dae983d4529afedeb53993759d8080735083f954d40bc8")
+ rrsig = dns.rdtypes.ANY.RRSIG.RRSIG.from_text(1, 46,
+ dns.tokenizer.Tokenizer("DS 7 2 86400 20180609010045 20180519000045 1862 org. SgdGsY4BAm7c3qpwzVLy3ua4orvrsJQO 0rUQDDrrXR6lElnbF+AS0gEEfdZfDv11 65AuNil/+kT2Qh/ExgstvhWQ88XdDnHB ouvRMf9pg3p/q5Otet/StRzf33SMPgC1 zLzkfkSBCjJkwVmwde8saGnjdcW522ra Ge/6JcsryRw="))
+
+ rrset2 = dns.rrset.from_text("org.", 866, 1, 48,
+ "256 3 7 AwEAAXxsMmN/JgpEE9Y4uFNRJm7Q9GBw mEYUCsCxuKlgBU9WrQEFRrvAeMamUBeX 4SE8s3V/TEk/TgGmPPp0pMkKD7mseluK 6Ard2HZ6O3nPAzL4i8py/UDRUmYNSCxw fdfjUWRmcB9H+NKWMsJoDhAkLFqg5HS7 f0j4Vb99Wac24Fk7",
+ "256 3 7 AwEAAcLdAPt3vn/ND00zZlyTx7OBko+9 YeCrSl2eGuEXjef0Lqf0tKGikoHwnmTH tT8J/aGqkZImLMVByJbknE0wKDnbvbKD oTQxPwUQZLH6k3sTdsPKESKDSBSc6VFM q35gx6CeuRYZ9KkGWiUsKqJhXPo6tyJF CBxfaNQQyrzBnv4/",
+ "257 3 7 AwEAAZTjbIO5kIpxWUtyXc8avsKyHIIZ +LjC2Dv8naO+Tz6X2fqzDC1bdq7HlZwt kaqTkMVVJ+8gE9FIreGJ4c8G1GdbjQgb P1OyYIG7OHTc4hv5T2NlyWr6k6QFz98Q 4zwFIGTFVvwBhmrMDYsOTtXakK6QwHov A1+83BsUACxlidpwB0hQacbD6x+I2RCD zYuTzj64Jv0/9XsX6AYV3ebcgn4hL1jI R2eJYyXlrAoWxdzxcW//5yeL5RVWuhRx ejmnSVnCuxkfS4AQ485KH2tpdbWcCopL JZs6tw8q3jWcpTGzdh/v3xdYfNpQNcPI mFlxAun3BtORPA2r8ti6MNoJEHU=",
+ "257 3 7 AwEAAcMnWBKLuvG/LwnPVykcmpvnntwx fshHlHRhlY0F3oz8AMcuF8gw9McCw+Bo C2YxWaiTpNPuxjSNhUlBtcJmcdkz3/r7 PIn0oDf14ept1Y9pdPh8SbIBIWx50ZPf VRlj8oQXv2Y6yKiQik7bi3MT37zMRU2k w2oy3cgrsGAzGN4s/C6SFYon5N1Q2O4h GDbeOq538kATOy0GFELjuauV9guX/431 msYu4Rgb5lLuQ3Mx5FSIxXpI/RaAn2mh M4nEZ/5IeRPKZVGydcuLBS8GZlxW4qbb 8MgRZ8bwMg0pqWRHmhirGmJIt3UuzvN1 pSFBfX7ysI9PPhSnwXCNDXk0kk0=")
+ keys = {dns.name.Name([b'org', b'']): rrset2}
+ origin = None
+ now = 1527191953.6527798
+
+ # 'None' means it is valid
+ self.assertEqual(None, dnssec.python_validate_rrsig(rrset, rrsig, keys, origin, now))
diff --git a/electrum/tests/test_interface.py b/electrum/tests/test_interface.py
new file mode 100644
index 000000000..402588ca8
--- /dev/null
+++ b/electrum/tests/test_interface.py
@@ -0,0 +1,28 @@
+import unittest
+
+from electrum import interface
+
+from . import SequentialTestCase
+
+
+class TestInterface(SequentialTestCase):
+
+ def test_match_host_name(self):
+ self.assertTrue(interface._match_hostname('asd.fgh.com', 'asd.fgh.com'))
+ self.assertFalse(interface._match_hostname('asd.fgh.com', 'asd.zxc.com'))
+ self.assertTrue(interface._match_hostname('asd.fgh.com', '*.fgh.com'))
+ self.assertFalse(interface._match_hostname('asd.fgh.com', '*fgh.com'))
+ self.assertFalse(interface._match_hostname('asd.fgh.com', '*.zxc.com'))
+
+ def test_check_host_name(self):
+ i = interface.TcpConnection(server=':1:', queue=None, config_path=None)
+
+ self.assertFalse(i.check_host_name(None, None))
+ self.assertFalse(i.check_host_name(
+ peercert={'subjectAltName': []}, name=''))
+ self.assertTrue(i.check_host_name(
+ peercert={'subjectAltName': [('DNS', 'foo.bar.com')]},
+ name='foo.bar.com'))
+ self.assertTrue(i.check_host_name(
+ peercert={'subject': [('commonName', 'foo.bar.com')]},
+ name='foo.bar.com'))
diff --git a/electrum/tests/test_mnemonic.py b/electrum/tests/test_mnemonic.py
new file mode 100644
index 000000000..191ce9023
--- /dev/null
+++ b/electrum/tests/test_mnemonic.py
@@ -0,0 +1,42 @@
+import unittest
+from electrum import keystore
+from electrum import mnemonic
+from electrum import old_mnemonic
+from electrum.util import bh2u
+
+from . import SequentialTestCase
+
+
+class Test_NewMnemonic(SequentialTestCase):
+
+ def test_to_seed(self):
+ seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none')
+ self.assertEqual(bh2u(seed),
+ '741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5b'
+ 'b22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce')
+
+ def test_random_seeds(self):
+ iters = 10
+ m = mnemonic.Mnemonic(lang='en')
+ for _ in range(iters):
+ seed = m.make_seed()
+ i = m.mnemonic_decode(seed)
+ self.assertEqual(m.mnemonic_encode(i), seed)
+
+
+class Test_OldMnemonic(SequentialTestCase):
+
+ def test(self):
+ seed = '8edad31a95e7d59f8837667510d75a4d'
+ result = old_mnemonic.mn_encode(seed)
+ words = 'hardly point goal hallway patience key stone difference ready caught listen fact'
+ self.assertEqual(result, words.split())
+ self.assertEqual(old_mnemonic.mn_decode(result), seed)
+
+class Test_BIP39Checksum(SequentialTestCase):
+
+ def test(self):
+ mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'
+ is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic)
+ self.assertTrue(is_wordlist_valid)
+ self.assertTrue(is_checksum_valid)
diff --git a/electrum/tests/test_simple_config.py b/electrum/tests/test_simple_config.py
new file mode 100644
index 000000000..6a3dbd023
--- /dev/null
+++ b/electrum/tests/test_simple_config.py
@@ -0,0 +1,186 @@
+import ast
+import sys
+import os
+import unittest
+import tempfile
+import shutil
+
+from io import StringIO
+from electrum.simple_config import (SimpleConfig, read_user_config)
+
+from . import SequentialTestCase
+
+
+class Test_SimpleConfig(SequentialTestCase):
+
+ def setUp(self):
+ super(Test_SimpleConfig, self).setUp()
+ # make sure "read_user_config" and "user_dir" return a temporary directory.
+ self.electrum_dir = tempfile.mkdtemp()
+ # Do the same for the user dir to avoid overwriting the real configuration
+ # for development machines with electrum installed :)
+ self.user_dir = tempfile.mkdtemp()
+
+ self.options = {"electrum_path": self.electrum_dir}
+ self._saved_stdout = sys.stdout
+ self._stdout_buffer = StringIO()
+ sys.stdout = self._stdout_buffer
+
+ def tearDown(self):
+ super(Test_SimpleConfig, self).tearDown()
+ # Remove the temporary directory after each test (to make sure we don't
+ # pollute /tmp for nothing.
+ shutil.rmtree(self.electrum_dir)
+ shutil.rmtree(self.user_dir)
+
+ # Restore the "real" stdout
+ sys.stdout = self._saved_stdout
+
+ def test_simple_config_key_rename(self):
+ """auto_cycle was renamed auto_connect"""
+ fake_read_user = lambda _: {"auto_cycle": True}
+ read_user_dir = lambda : self.user_dir
+ config = SimpleConfig(options=self.options,
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ self.assertEqual(config.get("auto_connect"), True)
+ self.assertEqual(config.get("auto_cycle"), None)
+ fake_read_user = lambda _: {"auto_connect": False, "auto_cycle": True}
+ config = SimpleConfig(options=self.options,
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ self.assertEqual(config.get("auto_connect"), False)
+ self.assertEqual(config.get("auto_cycle"), None)
+
+ def test_simple_config_command_line_overrides_everything(self):
+ """Options passed by command line override all other configuration
+ sources"""
+ fake_read_user = lambda _: {"electrum_path": "b"}
+ read_user_dir = lambda : self.user_dir
+ config = SimpleConfig(options=self.options,
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ self.assertEqual(self.options.get("electrum_path"),
+ config.get("electrum_path"))
+
+ def test_simple_config_user_config_is_used_if_others_arent_specified(self):
+ """If no system-wide configuration and no command-line options are
+ specified, the user configuration is used instead."""
+ fake_read_user = lambda _: {"electrum_path": self.electrum_dir}
+ read_user_dir = lambda : self.user_dir
+ config = SimpleConfig(options={},
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ self.assertEqual(self.options.get("electrum_path"),
+ config.get("electrum_path"))
+
+ def test_cannot_set_options_passed_by_command_line(self):
+ fake_read_user = lambda _: {"electrum_path": "b"}
+ read_user_dir = lambda : self.user_dir
+ config = SimpleConfig(options=self.options,
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ config.set_key("electrum_path", "c")
+ self.assertEqual(self.options.get("electrum_path"),
+ config.get("electrum_path"))
+
+ def test_can_set_options_set_in_user_config(self):
+ another_path = tempfile.mkdtemp()
+ fake_read_user = lambda _: {"electrum_path": self.electrum_dir}
+ read_user_dir = lambda : self.user_dir
+ config = SimpleConfig(options={},
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ config.set_key("electrum_path", another_path)
+ self.assertEqual(another_path, config.get("electrum_path"))
+
+ def test_user_config_is_not_written_with_read_only_config(self):
+ """The user config does not contain command-line options when saved."""
+ fake_read_user = lambda _: {"something": "a"}
+ read_user_dir = lambda : self.user_dir
+ self.options.update({"something": "c"})
+ config = SimpleConfig(options=self.options,
+ read_user_config_function=fake_read_user,
+ read_user_dir_function=read_user_dir)
+ config.save_user_config()
+ contents = None
+ with open(os.path.join(self.electrum_dir, "config"), "r") as f:
+ contents = f.read()
+ result = ast.literal_eval(contents)
+ result.pop('config_version', None)
+ self.assertEqual({"something": "a"}, result)
+
+ def test_depth_target_to_fee(self):
+ config = SimpleConfig(self.options)
+ config.mempool_fees = [[49, 100110], [10, 121301], [6, 153731], [5, 125872], [1, 36488810]]
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(1000000))
+ self.assertEqual( 6 * 1000, config.depth_target_to_fee( 500000))
+ self.assertEqual( 7 * 1000, config.depth_target_to_fee( 250000))
+ self.assertEqual(11 * 1000, config.depth_target_to_fee( 200000))
+ self.assertEqual(50 * 1000, config.depth_target_to_fee( 100000))
+ config.mempool_fees = []
+ self.assertEqual( 1 * 1000, config.depth_target_to_fee(10 ** 5))
+ self.assertEqual( 1 * 1000, config.depth_target_to_fee(10 ** 6))
+ self.assertEqual( 1 * 1000, config.depth_target_to_fee(10 ** 7))
+ config.mempool_fees = [[1, 36488810]]
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(10 ** 5))
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(10 ** 6))
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(10 ** 7))
+ self.assertEqual( 1 * 1000, config.depth_target_to_fee(10 ** 8))
+ config.mempool_fees = [[5, 125872], [1, 36488810]]
+ self.assertEqual( 6 * 1000, config.depth_target_to_fee(10 ** 5))
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(10 ** 6))
+ self.assertEqual( 2 * 1000, config.depth_target_to_fee(10 ** 7))
+ self.assertEqual( 1 * 1000, config.depth_target_to_fee(10 ** 8))
+
+ def test_fee_to_depth(self):
+ config = SimpleConfig(self.options)
+ config.mempool_fees = [[49, 100000], [10, 120000], [6, 150000], [5, 125000], [1, 36000000]]
+ self.assertEqual(100000, config.fee_to_depth(500))
+ self.assertEqual(100000, config.fee_to_depth(50))
+ self.assertEqual(100000, config.fee_to_depth(49))
+ self.assertEqual(220000, config.fee_to_depth(48))
+ self.assertEqual(220000, config.fee_to_depth(10))
+ self.assertEqual(370000, config.fee_to_depth(9))
+ self.assertEqual(370000, config.fee_to_depth(6.5))
+ self.assertEqual(370000, config.fee_to_depth(6))
+ self.assertEqual(495000, config.fee_to_depth(5.5))
+ self.assertEqual(36495000, config.fee_to_depth(0.5))
+
+
+class TestUserConfig(SequentialTestCase):
+
+ def setUp(self):
+ super(TestUserConfig, self).setUp()
+ self._saved_stdout = sys.stdout
+ self._stdout_buffer = StringIO()
+ sys.stdout = self._stdout_buffer
+
+ self.user_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ super(TestUserConfig, self).tearDown()
+ shutil.rmtree(self.user_dir)
+ sys.stdout = self._saved_stdout
+
+ def test_no_path_means_no_result(self):
+ result = read_user_config(None)
+ self.assertEqual({}, result)
+
+ def test_path_without_config_file(self):
+ """We pass a path but if does not contain a "config" file."""
+ result = read_user_config(self.user_dir)
+ self.assertEqual({}, result)
+
+ def test_path_with_reprd_object(self):
+
+ class something(object):
+ pass
+
+ thefile = os.path.join(self.user_dir, "config")
+ payload = something()
+ with open(thefile, "w") as f:
+ f.write(repr(payload))
+
+ result = read_user_config(self.user_dir)
+ self.assertEqual({}, result)
diff --git a/electrum/tests/test_storage_upgrade.py b/electrum/tests/test_storage_upgrade.py
new file mode 100644
index 000000000..8117176fe
--- /dev/null
+++ b/electrum/tests/test_storage_upgrade.py
@@ -0,0 +1,301 @@
+import shutil
+import tempfile
+
+from electrum.storage import WalletStorage
+from electrum.wallet import Wallet
+
+from .test_wallet import WalletTestCase
+
+from . import SequentialTestCase
+
+
+# TODO add other wallet types: 2fa, xpub-only
+# TODO hw wallet with client version 2.6.x (single-, and multiacc)
+class TestStorageUpgrade(WalletTestCase):
+
+ def test_upgrade_from_client_1_9_8_seeded(self):
+ 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}"
+ self._upgrade_storage(wallet_str)
+
+ # TODO pre-2.0 mixed wallets are not split currently
+ #def test_upgrade_from_client_1_9_8_mixed(self):
+ # 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}"
+ # self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_0_4_seeded(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_0_4_importedkeys(self):
+ wallet_str = '{"accounts":{"/x":{"imported":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":["0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5","L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM"],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":["04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2","5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":["0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f","L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U"]}}},"accounts_expanded":{},"use_encryption":false,"wallet_type":"imported"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_0_4_watchaddresses(self):
+ wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"wallet_type":"imported"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_0_4_trezor_singleacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_0_4_trezor_multiacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_0_4_multisig(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_1_1_seeded(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_1_1_importedkeys(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_1_1_watchaddresses(self):
+ wallet_str = '{"accounts":{"/x":{"imported":{"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf":[null,null],"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs":[null,null],"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa":[null,null]}}},"accounts_expanded":{},"pruned_txo":{},"transactions":{},"txi":{},"txo":{},"wallet_type":"imported"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_1_1_trezor_singleacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_1_1_trezor_multiacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_1_1_multisig(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_2_0_seeded(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_2_0_importedkeys(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_2_0_watchaddresses(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_2_0_trezor_singleacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_2_0_trezor_multiacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_2_0_multisig(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_3_2_seeded(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_3_2_importedkeys(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_3_2_watchaddresses(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_3_2_trezor_singleacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_3_2_trezor_multiacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_3_2_multisig(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_4_3_seeded(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_4_3_importedkeys(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_4_3_watchaddresses(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_4_3_trezor_singleacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_4_3_trezor_multiacc(self):
+ 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"}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_4_3_multisig(self):
+ 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"}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_5_4_seeded(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_5_4_importedkeys(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_5_4_watchaddresses(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_5_4_trezor_singleacc(self):
+ 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]}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_5_4_trezor_multiacc(self):
+ 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]}'''
+ self._upgrade_storage(wallet_str, accounts=2)
+
+ def test_upgrade_from_client_2_5_4_multisig(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_6_4_seeded(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_6_4_importedkeys(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_6_4_watchaddresses(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_6_4_multisig(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_7_18_seeded(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_7_18_importedkeys(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_7_18_watchaddresses(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_7_18_trezor_singleacc(self):
+ 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]}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_7_18_multisig(self):
+ 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"}}'
+ self._upgrade_storage(wallet_str)
+
+ # seed_version 13 is ambiguous
+ # client 2.7.18 created wallets with an earlier "v13" structure
+ # client 2.8.3 created wallets with a later "v13" structure
+ # client 2.8.3 did not do a proper clean-slate upgrade
+ # the wallet here was created in 2.7.18 with a couple privkeys imported
+ # then opened in 2.8.3, after which a few other new privkeys were imported
+ # it's in some sense in an "inconsistent" state
+ def test_upgrade_from_client_2_8_3_importedkeys_flawed_previous_upgrade_from_2_7_18(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_8_3_seeded(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_8_3_importedkeys(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_8_3_watchaddresses(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_8_3_trezor_singleacc(self):
+ 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]}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_8_3_multisig(self):
+ 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"}}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_9_3_seeded(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_9_3_importedkeys(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_9_3_watchaddresses(self):
+ 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]}'
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_9_3_trezor_singleacc(self):
+ 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]}'''
+ self._upgrade_storage(wallet_str)
+
+ def test_upgrade_from_client_2_9_3_multisig(self):
+ 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"}}'
+ self._upgrade_storage(wallet_str)
+
+##########
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ from electrum.plugin import Plugins
+ from electrum.simple_config import SimpleConfig
+
+ cls.electrum_path = tempfile.mkdtemp()
+ config = SimpleConfig({'electrum_path': cls.electrum_path})
+
+ gui_name = 'cmdline'
+ # TODO it's probably wasteful to load all plugins... only need Trezor
+ Plugins(config, True, gui_name)
+
+ @classmethod
+ def tearDownClass(cls):
+ super().tearDownClass()
+ shutil.rmtree(cls.electrum_path)
+
+ def _upgrade_storage(self, wallet_json, accounts=1):
+ storage = self._load_storage_from_json_string(wallet_json, manual_upgrades=True)
+
+ if accounts == 1:
+ self.assertFalse(storage.requires_split())
+ if storage.requires_upgrade():
+ storage.upgrade()
+ self._sanity_check_upgraded_storage(storage)
+ else:
+ self.assertTrue(storage.requires_split())
+ new_paths = storage.split_accounts()
+ self.assertEqual(accounts, len(new_paths))
+ for new_path in new_paths:
+ new_storage = WalletStorage(new_path, manual_upgrades=False)
+ self._sanity_check_upgraded_storage(new_storage)
+
+ def _sanity_check_upgraded_storage(self, storage):
+ self.assertFalse(storage.requires_split())
+ self.assertFalse(storage.requires_upgrade())
+ w = Wallet(storage)
+
+ def _load_storage_from_json_string(self, wallet_json, manual_upgrades=True):
+ with open(self.wallet_path, "w") as f:
+ f.write(wallet_json)
+ storage = WalletStorage(self.wallet_path, manual_upgrades=manual_upgrades)
+ return storage
diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py
new file mode 100644
index 000000000..71a0406b9
--- /dev/null
+++ b/electrum/tests/test_transaction.py
@@ -0,0 +1,819 @@
+import unittest
+
+from electrum import transaction
+from electrum.bitcoin import TYPE_ADDRESS
+from electrum.keystore import xpubkey_to_address
+from electrum.util import bh2u, bfh
+
+from . import SequentialTestCase, TestCaseForTestnet
+from .test_bitcoin import needs_test_with_all_ecc_implementations
+
+unsigned_blob = '45505446ff0001000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
+signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
+v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700"
+signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000"
+
+signed_blob_signatures = ['3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501', ]
+
+class TestBCDataStream(SequentialTestCase):
+
+ def test_compact_size(self):
+ s = transaction.BCDataStream()
+ values = [0, 1, 252, 253, 2**16-1, 2**16, 2**32-1, 2**32, 2**64-1]
+ for v in values:
+ s.write_compact_size(v)
+
+ with self.assertRaises(transaction.SerializationError):
+ s.write_compact_size(-1)
+
+ self.assertEqual(bh2u(s.input),
+ '0001fcfdfd00fdfffffe00000100feffffffffff0000000001000000ffffffffffffffffff')
+ for v in values:
+ self.assertEqual(s.read_compact_size(), v)
+
+ with self.assertRaises(transaction.SerializationError):
+ s.read_compact_size()
+
+ def test_string(self):
+ s = transaction.BCDataStream()
+ with self.assertRaises(transaction.SerializationError):
+ s.read_string()
+
+ msgs = ['Hello', ' ', 'World', '', '!']
+ for msg in msgs:
+ s.write_string(msg)
+ for msg in msgs:
+ self.assertEqual(s.read_string(), msg)
+
+ with self.assertRaises(transaction.SerializationError):
+ s.read_string()
+
+ def test_bytes(self):
+ s = transaction.BCDataStream()
+ s.write(b'foobar')
+ self.assertEqual(s.read_bytes(3), b'foo')
+ self.assertEqual(s.read_bytes(2), b'ba')
+ self.assertEqual(s.read_bytes(4), b'r')
+ self.assertEqual(s.read_bytes(1), b'')
+
+class TestTransaction(SequentialTestCase):
+
+ @needs_test_with_all_ecc_implementations
+ def test_tx_unsigned(self):
+ expected = {
+ 'inputs': [{
+ 'type': 'p2pkh',
+ 'address': '1446oU3z268EeFgfcwJv6X2VBXHfoYxfuD',
+ 'num_sig': 1,
+ 'prevout_hash': '3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a',
+ 'prevout_n': 0,
+ 'pubkeys': ['02e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6'],
+ 'scriptSig': '01ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000',
+ 'sequence': 4294967295,
+ 'signatures': [None],
+ 'x_pubkeys': ['ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000']}],
+ 'lockTime': 0,
+ 'outputs': [{
+ 'address': '14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs',
+ 'prevout_n': 0,
+ 'scriptPubKey': '76a914230ac37834073a42146f11ef8414ae929feaafc388ac',
+ 'type': TYPE_ADDRESS,
+ 'value': 1000000}],
+ 'partial': True,
+ 'segwit_ser': False,
+ 'version': 1,
+ }
+ tx = transaction.Transaction(unsigned_blob)
+ self.assertEqual(tx.deserialize(), expected)
+ self.assertEqual(tx.deserialize(), None)
+
+ self.assertEqual(tx.as_dict(), {'hex': unsigned_blob, 'complete': False, 'final': True})
+ self.assertEqual(tx.get_outputs(), [('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs', 1000000)])
+ self.assertEqual(tx.get_output_addresses(), ['14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs'])
+
+ self.assertTrue(tx.has_address('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs'))
+ self.assertTrue(tx.has_address('1446oU3z268EeFgfcwJv6X2VBXHfoYxfuD'))
+ self.assertFalse(tx.has_address('1CQj15y1N7LDHp7wTt28eoD1QhHgFgxECH'))
+
+ self.assertEqual(tx.serialize(), unsigned_blob)
+
+ tx.update_signatures(signed_blob_signatures)
+ self.assertEqual(tx.raw, signed_blob)
+
+ tx.update(unsigned_blob)
+ tx.raw = None
+ blob = str(tx)
+ self.assertEqual(transaction.deserialize(blob), expected)
+
+ @needs_test_with_all_ecc_implementations
+ def test_tx_signed(self):
+ expected = {
+ 'inputs': [{'address': None,
+ 'num_sig': 0,
+ 'prevout_hash': '3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a',
+ 'prevout_n': 0,
+ 'scriptSig': '493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6',
+ 'sequence': 4294967295,
+ 'type': 'unknown'}],
+ 'lockTime': 0,
+ 'outputs': [{
+ 'address': '14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs',
+ 'prevout_n': 0,
+ 'scriptPubKey': '76a914230ac37834073a42146f11ef8414ae929feaafc388ac',
+ 'type': TYPE_ADDRESS,
+ 'value': 1000000}],
+ 'partial': False,
+ 'segwit_ser': False,
+ 'version': 1
+ }
+ tx = transaction.Transaction(signed_blob)
+ self.assertEqual(tx.deserialize(), expected)
+ self.assertEqual(tx.deserialize(), None)
+ self.assertEqual(tx.as_dict(), {'hex': signed_blob, 'complete': True, 'final': True})
+
+ self.assertEqual(tx.serialize(), signed_blob)
+
+ tx.update_signatures(signed_blob_signatures)
+
+ self.assertEqual(tx.estimated_total_size(), 193)
+ self.assertEqual(tx.estimated_base_size(), 193)
+ self.assertEqual(tx.estimated_witness_size(), 0)
+ self.assertEqual(tx.estimated_weight(), 772)
+ self.assertEqual(tx.estimated_size(), 193)
+
+ def test_estimated_output_size(self):
+ estimated_output_size = transaction.Transaction.estimated_output_size
+ self.assertEqual(estimated_output_size('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), 34)
+ self.assertEqual(estimated_output_size('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 32)
+ self.assertEqual(estimated_output_size('bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af'), 31)
+ self.assertEqual(estimated_output_size('bc1qnvks7gfdu72de8qv6q6rhkkzu70fqz4wpjzuxjf6aydsx7wxfwcqnlxuv3'), 43)
+
+ # TODO other tests for segwit tx
+ def test_tx_signed_segwit(self):
+ tx = transaction.Transaction(signed_segwit_blob)
+
+ self.assertEqual(tx.estimated_total_size(), 222)
+ self.assertEqual(tx.estimated_base_size(), 113)
+ self.assertEqual(tx.estimated_witness_size(), 109)
+ self.assertEqual(tx.estimated_weight(), 561)
+ self.assertEqual(tx.estimated_size(), 141)
+
+ def test_errors(self):
+ with self.assertRaises(TypeError):
+ transaction.Transaction.pay_script(output_type=None, addr='')
+
+ with self.assertRaises(BaseException):
+ xpubkey_to_address('')
+
+ def test_parse_xpub(self):
+ res = xpubkey_to_address('fe4e13b0f311a55b8a5db9a32e959da9f011b131019d4cebe6141b9e2c93edcbfc0954c358b062a9f94111548e50bde5847a3096b8b7872dcffadb0e9579b9017b01000200')
+ self.assertEqual(res, ('04ee98d63800824486a1cf5b4376f2f574d86e0a3009a6448105703453f3368e8e1d8d090aaecdd626a45cc49876709a3bbb6dc96a4311b3cac03e225df5f63dfc', '19h943e4diLc68GXW7G75QNe2KWuMu7BaJ'))
+
+ def test_version_field(self):
+ tx = transaction.Transaction(v2_blob)
+ self.assertEqual(tx.txid(), "b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe")
+
+ def test_get_address_from_output_script(self):
+ # the inverse of this test is in test_bitcoin: test_address_to_script
+ addr_from_script = lambda script: transaction.get_address_from_output_script(bfh(script))
+ ADDR = transaction.TYPE_ADDRESS
+
+ # bech32 native segwit
+ # test vectors from BIP-0173
+ self.assertEqual((ADDR, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), addr_from_script('0014751e76e8199196d454941c45d1b3a323f1433bd6'))
+ self.assertEqual((ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6'))
+ self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e'))
+ self.assertEqual((ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323'))
+
+ # base58 p2pkh
+ self.assertEqual((ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac'))
+ self.assertEqual((ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac'))
+
+ # base58 p2sh
+ self.assertEqual((ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487'))
+ self.assertEqual((ADDR, '3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387'))
+
+#####
+
+ def _run_naive_tests_on_tx(self, raw_tx, txid):
+ tx = transaction.Transaction(raw_tx)
+ self.assertEqual(txid, tx.txid())
+ self.assertEqual(raw_tx, tx.serialize())
+ self.assertTrue(tx.estimated_size() >= 0)
+
+ def test_txid_coinbase_to_p2pk(self):
+ raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000'
+ txid = 'dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_coinbase_to_p2pkh(self):
+ raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000'
+ txid = '4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_segwit_coinbase_to_p2pk(self):
+ raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000'
+ txid = 'fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_segwit_coinbase_to_p2pkh(self):
+ raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000'
+ txid = 'ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_segwit_coinbase_to_p2sh(self):
+ raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff050214030101ffffffff02902f50090000000017a914ba582096f8647ca4195f55c8ef7e7e6e120e88b1870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000'
+ txid = 'e28ee5866ec0535fe5efac5ad350cbf4960ed981b471a0c4a6baad1d8168d3d7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pk_to_p2pkh(self):
+ raw_tx = '010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000'
+ txid = '90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pk_to_p2sh(self):
+ raw_tx = '0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000'
+ txid = '172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pk_to_p2wpkh(self):
+ raw_tx = '01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000'
+ txid = 'ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pkh_to_p2pkh(self):
+ raw_tx = '0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000'
+ txid = '24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pkh_to_p2sh(self):
+ raw_tx = '010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000'
+ txid = '155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2pkh_to_p2wpkh(self):
+ raw_tx = '0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000'
+ txid = 'ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2sh_to_p2pkh(self):
+ raw_tx = '01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000'
+ txid = '17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2sh_to_p2sh(self):
+ raw_tx = '01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000'
+ txid = 'ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2sh_to_p2wpkh(self):
+ raw_tx = '010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000'
+ txid = '6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2wpkh_to_p2pkh(self):
+ raw_tx = '0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000'
+ txid = 'c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2wpkh_to_p2sh(self):
+ raw_tx = '010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000'
+ txid = '390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_p2wpkh_to_p2wpkh(self):
+ raw_tx = '010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000'
+ txid = '51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_input_p2wsh_p2sh_not_multisig(self):
+ raw_tx = '0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000'
+ txid = 'e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ # input: p2sh, not multisig
+ def test_txid_regression_issue_3899(self):
+ raw_tx = '0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000'
+ txid = 'f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_negative_version_num(self):
+ raw_tx = 'f0b47b9a01ecf5e5c3bbf2cf1f71ecdc7f708b0b222432e914b394e24aad1494a42990ddfc000000008b483045022100852744642305a99ad74354e9495bf43a1f96ded470c256cd32e129290f1fa191022030c11d294af6a61b3da6ed2c0c296251d21d113cfd71ec11126517034b0dcb70014104a0fe6e4a600f859a0932f701d3af8e0ecd4be886d91045f06a5a6b931b95873aea1df61da281ba29cadb560dad4fc047cf47b4f7f2570da4c0b810b3dfa7e500ffffffff0240420f00000000001976a9147eeacb8a9265cd68c92806611f704fc55a21e1f588ac05f00d00000000001976a914eb3bd8ccd3ba6f1570f844b59ba3e0a667024a6a88acff7f0000'
+ txid = 'c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_regression_issue_4333(self):
+ raw_tx = '0100000001a300499298b3f03200c05d1a15aa111a33c769aff6fb355c6bf52ebdb58ca37100000000171600756161616161616161616161616161616161616151fdffffff01c40900000000000017a914001975d5f07f3391674416c1fcd67fd511d257ff871bc71300'
+ txid = '9b9f39e314662a7433aadaa5c94a2f1e24c7e7bf55fc9e1f83abd72be933eb95'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ # see https://bitcoin.stackexchange.com/questions/38006/txout-script-criteria-scriptpubkey-critieria
+ def test_txid_invalid_op_return(self):
+ raw_tx = '01000000019ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d010000006b4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fdffffffff080100000000000000010101000000000000000202010100000000000000014c0100000000000000034c02010100000000000000014d0100000000000000044dffff010100000000000000014e0100000000000000064effffffff0100000000'
+ txid = 'ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+
+# these transactions are from Bitcoin Core unit tests --->
+# https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json
+
+ def test_txid_bitcoin_core_0001(self):
+ raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
+ txid = '23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0002(self):
+ raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
+ txid = 'fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0003(self):
+ raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
+ txid = 'c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0004(self):
+ raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
+ txid = 'da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0005(self):
+ raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
+ txid = 'f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0006(self):
+ raw_tx = '01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000'
+ txid = 'c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0007(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000'
+ txid = 'e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0008(self):
+ raw_tx = '01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000'
+ txid = 'f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0009(self):
+ raw_tx = '01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000'
+ txid = 'b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0010(self):
+ raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000'
+ txid = '99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0011(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000'
+ txid = 'ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0012(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000'
+ txid = '4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0013(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000'
+ txid = '9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0014(self):
+ raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000'
+ txid = '99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0015(self):
+ raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000'
+ txid = 'c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0016(self):
+ raw_tx = '010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000'
+ txid = 'c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0017(self):
+ raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000'
+ txid = 'a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0018(self):
+ raw_tx = '010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000'
+ txid = 'afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0019(self):
+ raw_tx = '01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000'
+ txid = 'f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0020(self):
+ raw_tx = '0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000'
+ txid = 'cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0021(self):
+ raw_tx = '01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000'
+ txid = '1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0022(self):
+ raw_tx = '0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000'
+ txid = '018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0023(self):
+ raw_tx = '0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000'
+ txid = '1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0024(self):
+ raw_tx = '010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000'
+ txid = '1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0025(self):
+ raw_tx = '0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000'
+ txid = '24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0026(self):
+ raw_tx = '0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000'
+ txid = '9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0027(self):
+ raw_tx = '01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000'
+ txid = '46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0028(self):
+ raw_tx = '01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000'
+ txid = '8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0029(self):
+ raw_tx = '01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000'
+ txid = 'aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0030(self):
+ raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000'
+ txid = '6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0031(self):
+ raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000'
+ txid = '892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0032(self):
+ raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000'
+ txid = '578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0033(self):
+ raw_tx = '0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
+ txid = '974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0034(self):
+ raw_tx = '01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
+ txid = 'b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0035(self):
+ raw_tx = '01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
+ txid = 'feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0036(self):
+ raw_tx = '01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
+ txid = 'a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0037(self):
+ raw_tx = '0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000'
+ txid = '5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0038(self):
+ raw_tx = '0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000'
+ txid = 'ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0039(self):
+ raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
+ txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0040(self):
+ raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d'
+ txid = 'abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0041(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d'
+ txid = '58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0042(self):
+ raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff'
+ txid = '5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0043(self):
+ raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000'
+ txid = '25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0044(self):
+ raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff'
+ txid = '1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0045(self):
+ raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
+ txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0046(self):
+ raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000'
+ txid = 'f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0047(self):
+ raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000'
+ txid = 'd193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0048(self):
+ raw_tx = '010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000'
+ txid = '50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0049(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
+ txid = 'e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0050(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000'
+ txid = 'f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0051(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000'
+ txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0052(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000'
+ txid = '3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0053(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000'
+ txid = 'bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0054(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000'
+ txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0055(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000'
+ txid = 'f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0056(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000'
+ txid = '19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0057(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000'
+ txid = 'c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0058(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000'
+ txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0059(self):
+ raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000'
+ txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0060(self):
+ raw_tx = '02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000'
+ txid = '4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0061(self):
+ raw_tx = '0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000'
+ txid = '5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0062(self):
+ raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
+ txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0063(self):
+ raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
+ txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0064(self):
+ raw_tx = '01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
+ txid = 'fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0065(self):
+ raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
+ txid = '5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0066(self):
+ raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000'
+ txid = '07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0067(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0068(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = 'f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0069(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0070(self):
+ raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = 'e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0071(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0072(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0073(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0074(self):
+ raw_tx = '01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = 'cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0075(self):
+ raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = 'aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0076(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0077(self):
+ raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0078(self):
+ raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000'
+ txid = 'd93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0079(self):
+ raw_tx = '0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
+ txid = 'b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0080(self):
+ raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000'
+ txid = '2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0081(self):
+ raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000'
+ txid = '60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0082(self):
+ raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
+ txid = 'ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0083(self):
+ raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
+ txid = 'f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0084(self):
+ raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000'
+ txid = '98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0085(self):
+ raw_tx = '01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000'
+ txid = '570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0086(self):
+ raw_tx = '01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000'
+ txid = 'e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0087(self):
+ raw_tx = '0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000'
+ txid = 'b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0088(self):
+ raw_tx = '0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000'
+ txid = '27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0089(self):
+ raw_tx = '010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000'
+ txid = '22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0090(self):
+ raw_tx = '0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000'
+ txid = '2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0091(self):
+ raw_tx = '01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000'
+ txid = '1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_bitcoin_core_0092(self):
+ raw_tx = '010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000'
+ txid = '45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+# txns from Bitcoin Core ends <---
+
+
+class TestTransactionTestnet(TestCaseForTestnet):
+
+ def _run_naive_tests_on_tx(self, raw_tx, txid):
+ tx = transaction.Transaction(raw_tx)
+ self.assertEqual(txid, tx.txid())
+ self.assertEqual(raw_tx, tx.serialize())
+ self.assertTrue(tx.estimated_size() >= 0)
+
+# partial txns using our partial format --->
+ # NOTE: our partial format contains xpubs, and xpubs have version bytes,
+ # and version bytes encode the network as well; so these are network-sensitive!
+
+ def test_txid_partial_segwit_p2wpkh(self):
+ raw_tx = '45505446ff000100000000010115a847356cbb44be67f345965bb3f2589e2fec1c9a0ada21fd28225dcc602e8f0100000000fdffffff02f6fd1200000000001600149c756aa33f4f89418b33872a973274b5445c727b80969800000000001600140f9de573bc679d040e763d13f0250bd03e625f6ffeffffffff9095ab000000000000000201ff53ff045f1cf6014af5fa07800000002fa3f450ba41799b9b62642979505817783a9b6c656dc11cd0bb4fa362096808026adc616c25a4d0a877d1741eb1db9cef65c15118bd7d5f31bf65f319edda81840100c8000f391400'
+ txid = '63ff7e99d85d8e33f683e6ec84574bdf8f5111078a5fe900893e019f9a7f95c3'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_partial_segwit_p2wpkh_p2sh_simple(self):
+ raw_tx = '45505446ff0001000000000101d0d23a6fbddb21cc664cb81cca96715baa4d6dbe5b7b9bcc6632f1005a7b0b840100000017160014a78a91261e71a681b6312cd184b14503a21f856afdffffff0134410f000000000017a914d6514ca17ecc31952c990daf96e307fbc58529cd87feffffffff40420f000000000000000201ff53ff044a5262033601222e800000001618aa51e49a961f63fd111f64cd4a7e792c1d7168be7a07703de505ebed2cf70286ebbe755767adaa5835f4d78dec1ee30849d69eacfe80b7ee6b1585279536c30000020011391400'
+ txid = '2739f2e7fde9b8ec73fce4aee53722cc7683312d1321ded073284c51fadf44df'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+ def test_txid_partial_segwit_p2wpkh_p2sh_mixed_outputs(self):
+ raw_tx = '45505446ff00010000000001011dcac788f24b84d771b60c44e1f9b6b83429e50f06e1472d47241922164013b00100000017160014801d28ca6e2bde551112031b6cb75de34f10851ffdffffff0440420f00000000001600140f9de573bc679d040e763d13f0250bd03e625f6fc0c62d000000000017a9142899f6484e477233ce60072fc185ef4c1f2c654487809698000000000017a914d40f85ba3c8fa0f3615bcfa5d6603e36dfc613ef87712d19040000000017a914e38c0cffde769cb65e72cda1c234052ae8d2254187feffffffff6ad1ee040000000000000201ff53ff044a5262033601222e800000001618aa51e49a961f63fd111f64cd4a7e792c1d7168be7a07703de505ebed2cf70286ebbe755767adaa5835f4d78dec1ee30849d69eacfe80b7ee6b1585279536c301000c000f391400'
+ txid = 'ba5c88e07a4025a39ad3b85247cbd4f556a70d6312b18e04513c7cec9d45d6ac'
+ self._run_naive_tests_on_tx(raw_tx, txid)
+
+# end partial txns <---
+
+
+class NetworkMock(object):
+
+ def __init__(self, unspent):
+ self.unspent = unspent
+
+ def synchronous_send(self, arg):
+ return self.unspent
diff --git a/electrum/tests/test_util.py b/electrum/tests/test_util.py
new file mode 100644
index 000000000..1ffe1945f
--- /dev/null
+++ b/electrum/tests/test_util.py
@@ -0,0 +1,109 @@
+import unittest
+from electrum.util import format_satoshis, parse_URI
+
+from . import SequentialTestCase
+
+
+class TestUtil(SequentialTestCase):
+
+ def test_format_satoshis(self):
+ result = format_satoshis(1234)
+ expected = "0.00001234"
+ self.assertEqual(expected, result)
+
+ def test_format_satoshis_negative(self):
+ result = format_satoshis(-1234)
+ expected = "-0.00001234"
+ self.assertEqual(expected, result)
+
+ def test_format_fee(self):
+ result = format_satoshis(1700/1000, 0, 0)
+ expected = "1.7"
+ self.assertEqual(expected, result)
+
+ def test_format_fee_precision(self):
+ result = format_satoshis(1666/1000, 0, 0, precision=6)
+ expected = "1.666"
+ self.assertEqual(expected, result)
+
+ result = format_satoshis(1666/1000, 0, 0, precision=1)
+ expected = "1.7"
+ self.assertEqual(expected, result)
+
+ def test_format_satoshis_whitespaces(self):
+ result = format_satoshis(12340, whitespaces=True)
+ expected = " 0.0001234 "
+ self.assertEqual(expected, result)
+
+ result = format_satoshis(1234, whitespaces=True)
+ expected = " 0.00001234"
+ self.assertEqual(expected, result)
+
+ def test_format_satoshis_whitespaces_negative(self):
+ result = format_satoshis(-12340, whitespaces=True)
+ expected = " -0.0001234 "
+ self.assertEqual(expected, result)
+
+ result = format_satoshis(-1234, whitespaces=True)
+ expected = " -0.00001234"
+ self.assertEqual(expected, result)
+
+ def test_format_satoshis_diff_positive(self):
+ result = format_satoshis(1234, is_diff=True)
+ expected = "+0.00001234"
+ self.assertEqual(expected, result)
+
+ def test_format_satoshis_diff_negative(self):
+ result = format_satoshis(-1234, is_diff=True)
+ expected = "-0.00001234"
+ self.assertEqual(expected, result)
+
+ def _do_test_parse_URI(self, uri, expected):
+ result = parse_URI(uri)
+ self.assertEqual(expected, result)
+
+ def test_parse_URI_address(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'})
+
+ def test_parse_URI_only_address(self):
+ self._do_test_parse_URI('15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'})
+
+
+ def test_parse_URI_address_label(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?label=electrum%20test',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'label': 'electrum test'})
+
+ def test_parse_URI_address_message(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?message=electrum%20test',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'message': 'electrum test', 'memo': 'electrum test'})
+
+ def test_parse_URI_address_amount(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 30000})
+
+ def test_parse_URI_address_request_url(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?r=http://domain.tld/page?h%3D2a8628fc2fbe',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'r': 'http://domain.tld/page?h=2a8628fc2fbe'})
+
+ def test_parse_URI_ignore_args(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?test=test',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'test': 'test'})
+
+ def test_parse_URI_multiple_args(self):
+ self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.00004&label=electrum-test&message=electrum%20test&test=none&r=http://domain.tld/page',
+ {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 4000, 'label': 'electrum-test', 'message': u'electrum test', 'memo': u'electrum test', 'r': 'http://domain.tld/page', 'test': 'none'})
+
+ def test_parse_URI_no_address_request_url(self):
+ self._do_test_parse_URI('bitcoin:?r=http://domain.tld/page?h%3D2a8628fc2fbe',
+ {'r': 'http://domain.tld/page?h=2a8628fc2fbe'})
+
+ def test_parse_URI_invalid_address(self):
+ self.assertRaises(BaseException, parse_URI, 'bitcoin:invalidaddress')
+
+ def test_parse_URI_invalid(self):
+ self.assertRaises(BaseException, parse_URI, 'notbitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma')
+
+ def test_parse_URI_parameter_polution(self):
+ self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0')
diff --git a/electrum/tests/test_wallet.py b/electrum/tests/test_wallet.py
new file mode 100644
index 000000000..a19970240
--- /dev/null
+++ b/electrum/tests/test_wallet.py
@@ -0,0 +1,71 @@
+import shutil
+import tempfile
+import sys
+import unittest
+import os
+import json
+
+from io import StringIO
+from electrum.storage import WalletStorage, FINAL_SEED_VERSION
+
+from . import SequentialTestCase
+
+
+class FakeSynchronizer(object):
+
+ def __init__(self):
+ self.store = []
+
+ def add(self, address):
+ self.store.append(address)
+
+
+class WalletTestCase(SequentialTestCase):
+
+ def setUp(self):
+ super(WalletTestCase, self).setUp()
+ self.user_dir = tempfile.mkdtemp()
+
+ self.wallet_path = os.path.join(self.user_dir, "somewallet")
+
+ self._saved_stdout = sys.stdout
+ self._stdout_buffer = StringIO()
+ sys.stdout = self._stdout_buffer
+
+ def tearDown(self):
+ super(WalletTestCase, self).tearDown()
+ shutil.rmtree(self.user_dir)
+ # Restore the "real" stdout
+ sys.stdout = self._saved_stdout
+
+
+class TestWalletStorage(WalletTestCase):
+
+ def test_read_dictionary_from_file(self):
+
+ some_dict = {"a":"b", "c":"d"}
+ contents = json.dumps(some_dict)
+ with open(self.wallet_path, "w") as f:
+ contents = f.write(contents)
+
+ storage = WalletStorage(self.wallet_path, manual_upgrades=True)
+ self.assertEqual("b", storage.get("a"))
+ self.assertEqual("d", storage.get("c"))
+
+ def test_write_dictionary_to_file(self):
+
+ storage = WalletStorage(self.wallet_path)
+
+ some_dict = {
+ u"a": u"b",
+ u"c": u"d",
+ u"seed_version": FINAL_SEED_VERSION}
+
+ for key, value in some_dict.items():
+ storage.put(key, value)
+ storage.write()
+
+ contents = ""
+ with open(self.wallet_path, "r") as f:
+ contents = f.read()
+ self.assertEqual(some_dict, json.loads(contents))
diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
new file mode 100644
index 000000000..1a38c9223
--- /dev/null
+++ b/electrum/tests/test_wallet_vertical.py
@@ -0,0 +1,1648 @@
+import unittest
+from unittest import mock
+import shutil
+import tempfile
+from typing import Sequence
+
+from electrum import storage, bitcoin, keystore, constants
+from electrum import Transaction
+from electrum import SimpleConfig
+from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
+from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet
+from electrum.util import bfh, bh2u
+from electrum.transaction import TxOutput
+
+from electrum.plugins.trustedcoin import trustedcoin
+
+from . import TestCaseForTestnet
+from . import SequentialTestCase
+from .test_bitcoin import needs_test_with_all_ecc_implementations
+
+
+_UNICODE_HORROR_HEX = 'e282bf20f09f988020f09f98882020202020e3818620e38191e3819fe381be20e3828fe3828b2077cda2cda2cd9d68cda16fcda2cda120ccb8cda26bccb5cd9f6eccb4cd98c7ab77ccb8cc9b73cd9820cc80cc8177cd98cda2e1b8a9ccb561d289cca1cda27420cca7cc9568cc816fccb572cd8fccb5726f7273cca120ccb6cda1cda06cc4afccb665cd9fcd9f20ccb6cd9d696ecda220cd8f74cc9568ccb7cca1cd9f6520cd9fcd9f64cc9b61cd9c72cc95cda16bcca2cca820cda168ccb465cd8f61ccb7cca2cca17274cc81cd8f20ccb4ccb7cda0c3b2ccb5ccb666ccb82075cca7cd986ec3adcc9bcd9c63cda2cd8f6fccb7cd8f64ccb8cda265cca1cd9d3fcd9e'
+UNICODE_HORROR = bfh(_UNICODE_HORROR_HEX).decode('utf-8')
+# '₿ 😀 😈 う けたま わる w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝?͞'
+
+
+class WalletIntegrityHelper:
+
+ gap_limit = 1 # make tests run faster
+
+ @classmethod
+ def check_seeded_keystore_sanity(cls, test_obj, ks):
+ test_obj.assertTrue(ks.is_deterministic())
+ test_obj.assertFalse(ks.is_watching_only())
+ test_obj.assertFalse(ks.can_import())
+ test_obj.assertTrue(ks.has_seed())
+
+ @classmethod
+ def check_xpub_keystore_sanity(cls, test_obj, ks):
+ test_obj.assertTrue(ks.is_deterministic())
+ test_obj.assertTrue(ks.is_watching_only())
+ test_obj.assertFalse(ks.can_import())
+ test_obj.assertFalse(ks.has_seed())
+
+ @classmethod
+ def create_standard_wallet(cls, ks, gap_limit=None):
+ store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
+ store.put('keystore', ks.dump())
+ store.put('gap_limit', gap_limit or cls.gap_limit)
+ w = Standard_Wallet(store)
+ w.synchronize()
+ return w
+
+ @classmethod
+ def create_imported_wallet(cls, privkeys=False):
+ store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
+ if privkeys:
+ k = keystore.Imported_KeyStore({})
+ store.put('keystore', k.dump())
+ w = Imported_Wallet(store)
+ return w
+
+ @classmethod
+ def create_multisig_wallet(cls, keystores: Sequence, multisig_type: str, gap_limit=None):
+ """Creates a multisig wallet."""
+ store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
+ for i, ks in enumerate(keystores):
+ cosigner_index = i + 1
+ store.put('x%d/' % cosigner_index, ks.dump())
+ store.put('wallet_type', multisig_type)
+ store.put('gap_limit', gap_limit or cls.gap_limit)
+ w = Multisig_Wallet(store)
+ w.synchronize()
+ return w
+
+
+class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_seed_standard(self, mock_write):
+ seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
+
+ ks = keystore.from_seed(seed_words, '', False)
+
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6')
+ self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2pkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf')
+ self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_seed_segwit(self, mock_write):
+ seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
+
+ ks = keystore.from_seed(seed_words, '', False)
+
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2')
+ self.assertEqual(ks.xpub, 'zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2wpkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_seed_segwit_passphrase(self, mock_write):
+ seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
+
+ ks = keystore.from_seed(seed_words, UNICODE_HORROR, False)
+
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'zprvAZDmEQiCLUcZXPfrBXoksCD2R6RMAzAre7SUyBotibisy9c7vGhLYvHaP3d9rYU12DKAWdZfscPNA7qEPgTkCDqX5sE93ryAJAQvkDbfLxU')
+ self.assertEqual(ks.xpub, 'zpub6nD7dvF6ArArjskKHZLmEL9ky8FqaSti1LN5maDWGwFrqwwGTp1b6ic4EHwciFNaYDmCXcQYxXSiF9BjcLCMPcaYkVN2nQD6QjYQ8vpSR3Z')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2wpkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1qx94dutas7ysn2my645cyttujrms5d9p57f6aam')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1qcywwsy87sdp8vz5rfjh3sxdv6rt95kujdqq38g')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_seed_old(self, mock_write):
+ seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'old')
+
+ ks = keystore.from_seed(seed_words, '', False)
+
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
+ self.assertTrue(isinstance(ks, keystore.Old_KeyStore))
+
+ self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2pkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo')
+ self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_seed_2fa(self, mock_write):
+ seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
+ self.assertEqual(bitcoin.seed_type(seed_words), '2fa')
+
+ xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')
+
+ ks1 = keystore.from_xprv(xprv1)
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xprv, 'xprv9uraXy9F3HP7i8QDqwNTBiD8Jf4bPD4Epif8cS8qbUbgeidUesyZpKmzfcSeHutsGfFnjgih7kzwTB5UQVRNB5LoXaNc8pFusKYx3KVVvYR')
+ self.assertEqual(ks1.xpub, 'xpub68qvwUg8sewQvcUgwxuTYr9rrgu5nfn6BwajQpYT9p8fXWxdCRHpN86UWruWJAD1ede8Sv8ERrTa22Gyc4SBfm7zFpcyoVWVBKCVwnw6s1J')
+ self.assertEqual(ks1.xpub, xpub1)
+
+ ks2 = keystore.from_xprv(xprv2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+ self.assertEqual(ks2.xprv, 'xprv9uraXy9F3HP7kKSiRAvLV7Nrjj7YzspDys7dvGLLu4tLZT49CEBxPWp88dHhVxvZ69SHrPQMUCWjj4Ka2z9kNvs1HAeEf3extGGeSWqEVqf')
+ self.assertEqual(ks2.xpub, 'xpub68qvwUg8sewQxoXBXCTLrFKbHkx3QLY5M63EiejxTQRKSFPHjmWCwK8byvZMM2wZNYA3SmxXoma3M1zxhGESHZwtB7SwrxRgKXAG8dCD2eS')
+ self.assertEqual(ks2.xpub, xpub2)
+
+ long_user_id, short_id = trustedcoin.get_user_id(
+ {'x1/': {'xpub': xpub1},
+ 'x2/': {'xpub': xpub2}})
+ xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id)
+ ks3 = keystore.from_xpub(xpub3)
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3)
+ self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2, ks3], '2of3')
+ self.assertEqual(w.txin_type, 'p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')
+ self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_seed_bip44_standard(self, mock_write):
+ seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+
+ ks = keystore.from_bip39_seed(seed_words, '', "m/44'/0'/0'")
+
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD')
+ self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2pkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo')
+ self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_seed_bip44_standard_passphrase(self, mock_write):
+ seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+
+ ks = keystore.from_bip39_seed(seed_words, UNICODE_HORROR, "m/44'/0'/0'")
+
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'xprv9z8izheguGnLopSqkY7GcGFrP2Gu6rzBvvHo6uB9B8DWJhsows6WDZAsbBTaP3ncP2AVbTQphyEQkahrB9s1L7ihZtfz5WGQPMbXwsUtSik')
+ self.assertEqual(ks.xpub, 'xpub6D85QDBajeLe2JXJrZeGyQCaw47PWKi3J9DPuHakjTkVBWCxVQQkmMVMSSfnw39tj9FntbozpRtb1AJ8ubjeVSBhyK4M5mzdvsXZzKPwodT')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2pkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '1F88g2naBMhDB7pYFttPWGQgryba3hPevM')
+ self.assertEqual(w.get_change_addresses()[0], '1H4QD1rg2zQJ4UjuAVJr5eW1fEM8WMqyxh')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_seed_bip49_p2sh_segwit(self, mock_write):
+ seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+
+ ks = keystore.from_bip39_seed(seed_words, '', "m/49'/0'/0'")
+
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7')
+ self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2wpkh-p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W')
+ self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_seed_bip84_native_segwit(self, mock_write):
+ # test case from bip84
+ seed_words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+
+ ks = keystore.from_bip39_seed(seed_words, '', "m/84'/0'/0'")
+
+ self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
+
+ self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE')
+ self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs')
+
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(w.txin_type, 'p2wpkh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_multisig_seed_standard(self, mock_write):
+ seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
+
+ ks1 = keystore.from_seed(seed_words, '', True)
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6')
+ self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c')
+
+ # electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud
+ ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec')
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2], '2of2')
+ self.assertEqual(w.txin_type, 'p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN')
+ self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_electrum_multisig_seed_segwit(self, mock_write):
+ seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'
+ self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
+
+ ks1 = keystore.from_seed(seed_words, '', True)
+ WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4')
+ self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg')
+
+ # electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
+ ks2 = keystore.from_xpub('Zpub6y4oYeETXAbzLNg45wcFDGwEG3vpgsyMJybiAfi2pJtNF3i3fJVxK2BeZJaw7VeKZm192QHvXP3uHDNpNmNDbQft9FiMzkKUhNXQafUMYUY')
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2], '2of2')
+ self.assertEqual(w.txin_type, 'p2wsh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_multisig_seed_bip45_standard(self, mock_write):
+ seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+
+ ks1 = keystore.from_bip39_seed(seed_words, '', "m/45'/0")
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xprv, 'xprv9vyEFyXf7pYVv4eDU3hhuCEAHPHNGuxX73nwtYdpbLcqwJCPwFKknAK8pHWuHHBirCzAPDZ7UJHrYdhLfn1NkGp9rk3rVz2aEqrT93qKRD9')
+ self.assertEqual(ks1.xpub, 'xpub69xafV4YxC6o8Yiga5EiGLAtqR7rgNgNUGiYgw3S9g9pp6XYUne1KxdcfYtxwmA3eBrzMFuYcNQKfqsXCygCo4GxQFHfywxpUbKNfYvGJka')
+
+ # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact
+ # der: m/45'/0
+ ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg')
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2], '2of2')
+ self.assertEqual(w.txin_type, 'p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN')
+ self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_multisig_seed_p2sh_segwit(self, mock_write):
+ # bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor
+ # der: m/49'/0'/0'
+ # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
+ ks1 = keystore.from_xprv('YprvAUXFReVvDjrPerocC3FxVH748sJUTvYjkAhtKop5VnnzVzMEHr1CHrYQKZwfJn1As3X4LYMav6upxd5nDiLb6SCjRZrBH76EFvyQAG4cn79')
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xpub, 'Ypub6hWbqA2p47QgsLt5J4nxrR3ngu8xsPGb7PdV8CDh48KyNngNqPKSqertAqYhQ4umELu1UsZUCYfj9XPA6AdSMZWDZQobwF7EJ8uNrECaZg1')
+
+ # bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost
+ # der: m/49'/0'/0'
+ ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau')
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2], '2of2')
+ self.assertEqual(w.txin_type, 'p2wsh-p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns')
+ self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip32_extended_version_bytes(self, mock_write):
+ seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+ bip32_seed = keystore.bip39_to_seed(seed_words, '')
+ self.assertEqual('0df68c16e522eea9c1d8e090cfb2139c3b3a2abed78cbcb3e20be2c29185d3b8df4e8ce4e52a1206a688aeb88bfee249585b41a7444673d1f16c0d45755fa8b9',
+ bh2u(bip32_seed))
+
+ def create_keystore_from_bip32seed(xtype):
+ ks = keystore.BIP32_KeyStore({})
+ ks.add_xprv_from_seed(bip32_seed, xtype=xtype, derivation='m/')
+ return ks
+
+ ks = create_keystore_from_bip32seed(xtype='standard')
+ self.assertEqual('033a05ec7ae9a9833b0696eb285a762f17379fa208b3dc28df1c501cf84fe415d0', ks.derive_pubkey(0, 0))
+ self.assertEqual('02bf27f41683d84183e4e930e66d64fc8af5508b4b5bf3c473c505e4dbddaeed80', ks.derive_pubkey(1, 0))
+
+ ks = create_keystore_from_bip32seed(xtype='standard') # p2pkh
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K3nyWMZVjzGL4KKAE1zahmhTHuV5pdw4eK3o3igC5QywgQG7UTRe6TGBniPDpPFWzXMeMUFbBj8uYsfXGjyMmF54wdNt8QBm')
+ self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcGH3yTb2kMQGnsLziRTJZ8vNthsVSCGbdBr8CGDWKxnGAFYgyKTzBtwvPPmfVAWJuFmxRXjSbUTg87wDkWQ5GmzpfUcN9t8Z')
+ self.assertEqual(w.get_receiving_addresses()[0], '19fWEVaXqgJFFn7JYNr6ouxyjZy3uK7CdK')
+ self.assertEqual(w.get_change_addresses()[0], '1EEX7da31qndYyeKdbM665w1ze5gbkkAZZ')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wpkh-p2sh')
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'yprvABrGsX5C9janu6AdBvHNCMRZVHJfxcaCgoyWgsyi1wSXN9cGyLMe33bpRU54TLJ1ruJbTrpNqusYQeFvBx1CXNb9k1DhKtBFWo8b1sLbXhN')
+ self.assertEqual(ks.xpub, 'ypub6QqdH2c5z7967aF6HwpNZVNJ3K9AN5J442u7VGPKaGyWEwwRWsftaqvJGkeZKNe7Jb3C9FG3dAfT94ZzFRrcGhMizGvB6Jtm3itJsEFhxMC')
+ self.assertEqual(w.get_receiving_addresses()[0], '34SAT5gGF5UaBhhSZ8qEuuxYvZ2cm7Zi23')
+ self.assertEqual(w.get_change_addresses()[0], '38unULZaetSGSKvDx7Krukh8zm8NQnxGiA')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wpkh')
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'zprvAWgYBBk7JR8GkPMk2H4zQSX4fFT7uEZhbvVjUGsbPwpQRFRWDzXCf7FxSg2eTEwwGYRQDLQwJaE6HvsUueRDKcGkcLv7unzjnXCEQVWhrF9')
+ self.assertEqual(ks.xpub, 'zpub6jftahH18ngZxsSD8JbzmaToDHHcJhHYy9RLGfHCxHMPJ3kemXqTCuaSHxc9KHJ2iE9ztirc5q212MBYy8Gd4w3KrccbgDiFKSwxFpYKEH6')
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1qtuynwzd0d6wptvyqmc6ehkm70zcamxpshyzu5e')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1qjy5zunxh6hjysele86qqywfa437z4xwmleq8wk')
+
+ ks = create_keystore_from_bip32seed(xtype='standard') # p2sh
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K3nyWMZVjzGL4KKAE1zahmhTHuV5pdw4eK3o3igC5QywgQG7UTRe6TGBniPDpPFWzXMeMUFbBj8uYsfXGjyMmF54wdNt8QBm')
+ self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcGH3yTb2kMQGnsLziRTJZ8vNthsVSCGbdBr8CGDWKxnGAFYgyKTzBtwvPPmfVAWJuFmxRXjSbUTg87wDkWQ5GmzpfUcN9t8Z')
+ self.assertEqual(w.get_receiving_addresses()[0], '3F4nm8Vunb7mxVvqhUP238PYge2hpU5qYv')
+ self.assertEqual(w.get_change_addresses()[0], '3N8jvKGmxzVHENn6B4zTdZt3N9bmRKjj96')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wsh-p2sh')
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'YprvANkMzkodih9AKfL18akM2RmND5LwAyFo15dBc9FFPiGvzLBBjjjv8ATkEB2Y1mWv6NNaLSpVj8G3XosgVBA9frhpaUL6jHeFQXQTbqVPcv2')
+ self.assertEqual(ks.xpub, 'Ypub6bjiQGLXZ4hTY9QUEcHMPZi6m7BRaRyeNJYnQXerx3ous8WLHH4AfxnE5Tc2sos1Y47B1qGAWP3xGEBkYf1ZRBUPpk2aViMkwTABT6qoiBb')
+ self.assertEqual(w.get_receiving_addresses()[0], '3L1BxLLASGKE3DR1ruraWm3hZshGCKqcJx')
+ self.assertEqual(w.get_change_addresses()[0], '3NDGcbZVXTpaQWRhiuVPpXsNt4g2JiCX4E')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wsh')
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'ZprvAhadJRUYsNgeAxX7xwXyEWrsP3VP7bFHvC9QPY98miep3RzQzPuUkE7tFNz81gAqW1VP5vR4BncbR6VFCsaAU6PRSp2XKCTjgFU6zRpk6Xp')
+ self.assertEqual(ks.xpub, 'Zpub6vZyhw1ShkEwPSbb4y4ybeobw5KsX3y9HR51BvYkL4BnvEKZXwDjJ2SN6fZcsiWvwhDymJriy3QW9WoKGMRaDR9zh5j15dBFDBDpqjK1ekQ')
+ self.assertEqual(w.get_receiving_addresses()[0], 'bc1q84x0yrztvcjg88qef4d6978zccxulcmc9y88xcg4ghjdau999x7q7zv2qe')
+ self.assertEqual(w.get_change_addresses()[0], 'bc1q0fj5mra96hhnum80kllklc52zqn6kppt3hyzr49yhr3ecr42z3tsrkg3gs')
+
+
+class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet):
+
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_write):
+ # bip39 seed: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose
+ # der: m/49'/1'/0'
+ # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
+ ks1 = keystore.from_xprv('Uprv9BEixD3As2LK5h6G2SNT3cTqbZpsWYPceKTSuVAm1yuSybxSvQz2MV1o8cHTtctQmj4HAenb3eh5YJv4YRZjv35i8fofVnNbs4Dd2B4i5je')
+ self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
+ self.assertEqual(ks1.xpub, 'Upub5QE5Mia4hPtcJBAj8TuTQkQa9bfMv17U1YP3hsaNaKSRrQHbTxJGuHLGyv3MbKZixuPyjfXGUdbTjE4KwyFcX8YD7PX5ybTDbP11UT8UpZR')
+
+ # bip39 seed: square page wood spy oil story rebel give milk screen slide shuffle
+ # der: m/49'/1'/0'
+ ks2 = keystore.from_xpub('Upub5QRzUGRJuWJe5MxGzwgQAeyJjzcdGTXkkq77w6EfBkCyf5iWppSaZ4caY2MgWcU9LP4a4uE5apUFN4wLoENoe9tpu26mrUxeGsH84dN3JFh')
+ WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
+ self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
+
+ w = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2], '2of2')
+ self.assertEqual(w.txin_type, 'p2wsh-p2sh')
+
+ self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt')
+ self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7')
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bip32_extended_version_bytes(self, mock_write):
+ seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant'
+ self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
+ bip32_seed = keystore.bip39_to_seed(seed_words, '')
+ self.assertEqual('0df68c16e522eea9c1d8e090cfb2139c3b3a2abed78cbcb3e20be2c29185d3b8df4e8ce4e52a1206a688aeb88bfee249585b41a7444673d1f16c0d45755fa8b9',
+ bh2u(bip32_seed))
+
+ def create_keystore_from_bip32seed(xtype):
+ ks = keystore.BIP32_KeyStore({})
+ ks.add_xprv_from_seed(bip32_seed, xtype=xtype, derivation='m/')
+ return ks
+
+ ks = create_keystore_from_bip32seed(xtype='standard')
+ self.assertEqual('033a05ec7ae9a9833b0696eb285a762f17379fa208b3dc28df1c501cf84fe415d0', ks.derive_pubkey(0, 0))
+ self.assertEqual('02bf27f41683d84183e4e930e66d64fc8af5508b4b5bf3c473c505e4dbddaeed80', ks.derive_pubkey(1, 0))
+
+ ks = create_keystore_from_bip32seed(xtype='standard') # p2pkh
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'tprv8ZgxMBicQKsPecD328MF9ux3dSaSFWci7FNQmuWH7uZ86eY8i3XpvjK8KSH8To2QphiZiUqaYc6nzDC6bTw8YCB9QJjaQL5pAApN4z7vh2B')
+ self.assertEqual(ks.xpub, 'tpubD6NzVbkrYhZ4Y5Epun1qZKcACU6NQqocgYyC4RYaYBMWw8nuLSMR7DvzVamkqxwRgrTJ1MBMhc8wwxT2vbHqMu8RBXy4BvjWMxR5EdZroxE')
+ self.assertEqual(w.get_receiving_addresses()[0], 'mpBTXYfWehjW2tavFwpUdqBJbZZkup13k2')
+ self.assertEqual(w.get_change_addresses()[0], 'mtkUQgf1psDtL67wMAKTv19LrdgPWy6GDQ')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wpkh-p2sh')
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'uprv8tXDerPXZ1QsVuQ9rV8sN13YoQitC8cD2MtdZJQAVuw19kMMxhhPYnyGLeEiThgLELqNTxS91GTLsVofKAM9LRrkGeRzzEuJRtt1Tcostr7')
+ self.assertEqual(ks.xpub, 'upub57Wa4MvRPNyAiPUcxWfsj8zHMSZNbbL4PapEMgon4FTz2YgWWF1e6bHkBvpDKk2Rg2Zy9LsonXFFbv7jNeCZ5kdKWv8UkfcoxpdjJrZuBX6')
+ self.assertEqual(w.get_receiving_addresses()[0], '2MuzNWpcHrXyvPVKzEGT7Xrwp8uEnXXjWnK')
+ self.assertEqual(w.get_change_addresses()[0], '2MzTzY5VcGLwce7YmdEwjXhgQD7LYEKLJTm')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wpkh')
+ w = WalletIntegrityHelper.create_standard_wallet(ks)
+ self.assertEqual(ks.xprv, 'vprv9DMUxX4ShgxMMCbGgqvVa693yNsL8kbhwUQrLhJ3svJtCrAbDMrxArdQMrCJTcLFdyxBDS2hTvotknRE2rmA8fYM8z8Ra9inhcwerEsG6Ev')
+ self.assertEqual(ks.xpub, 'vpub5SLqN2bLY4WeZgfjnsTVwE5nXQhpYDKZJhLT95hfSFqs5eVjkuBCiewtD8moKegM5fgmtpUNFBboVCjJ6LcZszJvPFpuLaSJEYhNhUAnrCS')
+ self.assertEqual(w.get_receiving_addresses()[0], 'tb1qtuynwzd0d6wptvyqmc6ehkm70zcamxpsaze002')
+ self.assertEqual(w.get_change_addresses()[0], 'tb1qjy5zunxh6hjysele86qqywfa437z4xwm4lm549')
+
+ ks = create_keystore_from_bip32seed(xtype='standard') # p2sh
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'tprv8ZgxMBicQKsPecD328MF9ux3dSaSFWci7FNQmuWH7uZ86eY8i3XpvjK8KSH8To2QphiZiUqaYc6nzDC6bTw8YCB9QJjaQL5pAApN4z7vh2B')
+ self.assertEqual(ks.xpub, 'tpubD6NzVbkrYhZ4Y5Epun1qZKcACU6NQqocgYyC4RYaYBMWw8nuLSMR7DvzVamkqxwRgrTJ1MBMhc8wwxT2vbHqMu8RBXy4BvjWMxR5EdZroxE')
+ self.assertEqual(w.get_receiving_addresses()[0], '2N6czpsRwQ3d8AHZPNbztf5NotzEsaZmVQ8')
+ self.assertEqual(w.get_change_addresses()[0], '2NDgwz4CoaSzdSAQdrCcLFWsJaVowCNgiPA')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wsh-p2sh')
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'Uprv95RJn67y7xyEvUZXo9brC5PMXCm9QVHoLdYJUZfhsgmQmvvGj75fduqC9MCC28uETouMLYSFtUqqzfRRcPW6UuyR77YQPeNJKd9t3XutF8b')
+ self.assertEqual(ks.xpub, 'Upub5JQfBberxLXY8xdzuB8rZDL65Ebdox1ehrTuGx5KS2JPejFRGePvBi9fzdmgtBFKuVdx1vsvfjdkj5jVfsMWEEjzMPEtA55orYubtrCZmRr')
+ self.assertEqual(w.get_receiving_addresses()[0], '2NBZQ25GC3ipaF13ZY3UT8i2xnDuS17pJqx')
+ self.assertEqual(w.get_change_addresses()[0], '2NDmUgLVX8vKvcJ4FQ37GSUre6QtBzKkb6k')
+
+ ks = create_keystore_from_bip32seed(xtype='p2wsh')
+ w = WalletIntegrityHelper.create_multisig_wallet([ks], '1of1')
+ self.assertEqual(ks.xprv, 'Vprv16YtLrHXxePM6noKqtFtMtmUgBE9bEpF3fPLmpvuPksssLostujtdHBwqhEeVuzESz22UY8hyPx9ed684SQpCmUKSVhpxPFbvVNY7qnviNR')
+ self.assertEqual(ks.xpub, 'Vpub5dEvVGKn7251zFq7jXvUmJRbFCk5ka19cxz84LyCp2gGhq4eXJZUomop1qjGt5uFK8kkmQUV8PzJcNM4PZmX2URbDiwJjyuJ8GyFHRrEmmG')
+ self.assertEqual(w.get_receiving_addresses()[0], 'tb1q84x0yrztvcjg88qef4d6978zccxulcmc9y88xcg4ghjdau999x7qf2696k')
+ self.assertEqual(w.get_change_addresses()[0], 'tb1q0fj5mra96hhnum80kllklc52zqn6kppt3hyzr49yhr3ecr42z3ts5777jl')
+
+
+class TestWalletSending(TestCaseForTestnet):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.electrum_path = tempfile.mkdtemp()
+ cls.config = SimpleConfig({'electrum_path': cls.electrum_path})
+
+ @classmethod
+ def tearDownClass(cls):
+ super().tearDownClass()
+ shutil.rmtree(cls.electrum_path)
+
+ def create_standard_wallet_from_seed(self, seed_words):
+ ks = keystore.from_seed(seed_words, '', False)
+ return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2)
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_write):
+ wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver')
+ wallet2 = self.create_standard_wallet_from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song')
+
+ # bootstrap wallet1
+ funding_tx = Transaction('01000000014576dacce264c24d81887642b726f5d64aa7825b21b350c7b75a57f337da6845010000006b483045022100a3f8b6155c71a98ad9986edd6161b20d24fad99b6463c23b463856c0ee54826d02200f606017fd987696ebbe5200daedde922eee264325a184d5bbda965ba5160821012102e5c473c051dae31043c335266d0ef89c1daab2f34d885cc7706b267f3269c609ffffffff0240420f00000000001600148a28bddb7f61864bdcf58b2ad13d5aeb3abc3c42a2ddb90e000000001976a914c384950342cb6f8df55175b48586838b03130fad88ac00000000')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 1000000
+ self.assertEqual('add2535aedcbb5ba79cc2260868bb9e57f328738ca192937f2c92e0e94c19203', funding_txid)
+ wallet1.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet1 -> wallet2
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)]
+ tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet1.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet1.is_mine(wallet1.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('010000000001010392c1940e2ec9f2372919ca3887327fe5b98b866022cc79bab5cbed5a53d2ad0000000000feffffff0290d00300000000001976a914ea7804a2c266063572cc009a63dc25dcc0e9d9b588ac285e0b0000000000160014690b59a8140602fb23cc2904ece9cc4daf361052024730440220608a5339ca894592da82119e1e4a1d09335d70a552c683687223b8ed724465e902201b3f0feccf391b1b6257e4b18970ae57d7ca060af2dae519b3690baad2b2a34e0121030faee9b4a25b7db82023ca989192712cdd4cb53d3d9338591c7909e581ae1c0c00000000',
+ str(tx_copy))
+ self.assertEqual('3c06ae4d9be8226a472b3e7f7c127c7e3016f525d658d26106b80b4c7e3228e2', tx_copy.txid())
+ self.assertEqual('d8d930ae91dce73118c3fffabbdfcfb87f5d91673fb4c7dfd0fbe7cf03bf426b', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) # TX_HEIGHT_UNCONF_PARENT but nvm
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet2 -> wallet1
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)]
+ tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet2.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet2.is_mine(wallet2.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('0100000001e228327e4c0bb80661d258d625f516307e7c127c7f3e2b476a22e89b4dae063c000000006b483045022100d3895b31e7c9766987c6f53794c7394f534f4acecefda5479d963236f9703d0b022026dd4e40700ceb788f136faf54bf85b966648dc7c2a608d8110604f2d22d59070121030b482838721a38d94847699fed8818b5c5f56500ef72f13489e365b65e5749cffeffffff02a0860100000000001600148a28bddb7f61864bdcf58b2ad13d5aeb3abc3c4268360200000000001976a914ca4c60999c46c2108326590b125aefd476dcb11888ac00000000',
+ str(tx_copy))
+ self.assertEqual('5f25707571eb776bdf14142f9966bf2a681906e0a79501edbb99a972c2ceb972', tx_copy.txid())
+ self.assertEqual('5f25707571eb776bdf14142f9966bf2a681906e0a79501edbb99a972c2ceb972', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet level checks
+ self.assertEqual((0, funding_output_value - 250000 - 5000 + 100000, 0), wallet1.get_balance())
+ self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_write):
+ wallet1a = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure', '', True),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4XJzYkhsCbDCcZRmDAKSD7bXi9mdCni7acVt45fxbTVZyU6jRGh29ULKTjoapkfFsSJvQHitcVKbQgzgkkYsAmaovcro7Mhf')
+ ],
+ '2of3', gap_limit=2
+ )
+ wallet1b = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song', '', True),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YARFMEZPckrqJkw59GZD1PXtQnw14ukvWDofR7Z1HMeSCxfYEZVvg4VdZ8zGok5VxHwdrLqew5cMdQntWc5mT7mh1CSgrnX')
+ ],
+ '2of3', gap_limit=2
+ )
+ # ^ third seed: ghost into match ivory badge robot record tackle radar elbow traffic loud
+ wallet2 = self.create_standard_wallet_from_seed('powerful random nobody notice nothing important anyway look away hidden message over')
+
+ # bootstrap wallet1
+ funding_tx = Transaction('010000000001014121f99dc02f0364d2dab3d08905ff4c36fc76c55437fd90b769c35cc18618280100000000fdffffff02d4c22d00000000001600143fd1bc5d32245850c8cb5be5b09c73ccbb9a0f75001bb7000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887024830450221008781c78df0c9d4b5ea057333195d5d76bc29494d773f14fa80e27d2f288b2c360220762531614799b6f0fb8d539b18cb5232ab4253dd4385435157b28a44ff63810d0121033de77d21926e09efd04047ae2d39dbd3fb9db446e8b7ed53e0f70f9c9478f735dac11300')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 12000000
+ self.assertEqual('b25cd55687c9e528c2cfd546054f35fb6741f7cf32d600f07dfecdf2e1d42071', funding_txid)
+ wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet1 -> wallet2
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)]
+ tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
+ self.assertFalse(tx.is_complete())
+ wallet1b.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet1a.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet1a.is_mine(wallet1a.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('01000000017120d4e1f2cdfe7df000d632cff74167fb354f0546d5cfc228e5c98756d55cb201000000fdfe0000483045022100f9ce5616683e613ae14b98d56436454b003348a8172e2ed598018e3d206e57d7022030c65c6551e839f9e9409812be624dbb4e36bd4152c9ed9b0988c10fd8201d1401483045022100d5cb94d4d1dcf01bb9e9280e8178a7e9ada3ad14378ca543afcc9f5667b27cb2022018e76b74800a21934e73b226b34cbbe45c877fba64693da8a20d3cb330f2eafd014c69522102afb4af9a91264e1c6dce3ebe5312801723270ac0ba8134b7b49129328fcb0f2821030b482838721a38d94847699fed8818b5c5f56500ef72f13489e365b65e5749cf2103e5db7969ae2f2576e6a061bf3bb2db16571e77ffb41e0b27170734359235cbce53aefeffffff0250a50500000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac2862b1000000000017a9142e517854aa54668128c0e9a3fdd4dec13ad571368700000000',
+ str(tx_copy))
+ self.assertEqual('26f3bdd0402e1cff19126244ebe3d32722cef0db507c7229ca8754f5e06ef25d', tx_copy.txid())
+ self.assertEqual('26f3bdd0402e1cff19126244ebe3d32722cef0db507c7229ca8754f5e06ef25d', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet2 -> wallet1
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
+ tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet2.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet2.is_mine(wallet2.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('01000000015df26ee0f55487ca29727c50dbf0ce2227d3e3eb44621219ff1c2e40d0bdf326000000008b483045022100bd9f61ba82507d3a28922fb8be129e14699dfa54ddd03cc9494f696d38ac4121022071afca6fad5bc5c09b0a675e6444be3e97dbbdbc283764ee5f4e27a032d933d80141045f7ba332df2a7b4f5d13f246e307c9174cfa9b8b05f3b83410a3c23ef8958d610be285963d67c7bc1feb082f168fa9877c25999963ff8b56b242a852b23e25edfeffffff02a08601000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887280b0400000000001976a914ca14915184a2662b5d1505ce7142c8ca066c70e288ac00000000',
+ str(tx_copy))
+ self.assertEqual('c573b3f8464a4ed40dfc79d0889a780f44e917beef7a75883b2427c2987f3e95', tx_copy.txid())
+ self.assertEqual('c573b3f8464a4ed40dfc79d0889a780f44e917beef7a75883b2427c2987f3e95', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet level checks
+ self.assertEqual((0, funding_output_value - 370000 - 5000 + 100000, 0), wallet1a.get_balance())
+ self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_write):
+ wallet1a = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', '', True),
+ keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
+ keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra')
+ ],
+ '2of3', gap_limit=2
+ )
+ wallet1b = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('snow nest raise royal more walk demise rotate smooth spirit canyon gun', '', True),
+ keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra'),
+ keystore.from_xpub('Vpub5gSKXzxK7FeKQedu2q1z9oJWxqvX72AArW3HSWpEhc8othDH8xMDu28gr7gf17sp492BuJod8Tn7anjvJrKpETwqnQqX7CS8fcYyUtedEMk')
+ ],
+ '2of3', gap_limit=2
+ )
+ # ^ third seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
+ wallet2a = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ # bip39: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose, der: m/1234'/1'/0', p2wsh-p2sh multisig
+ keystore.from_xprv('Uprv9CvELvByqm8k2dpecJVjgLMX1z5DufEjY4fBC5YvdGF5WjGCa7GVJJ2fYni1tyuF7Hw83E6W2ZBjAhaFLZv2ri3rEsubkCd5avg4EHKoDBN'),
+ keystore.from_xpub('Upub5Qb8ik4Cnu8g97KLXKgVXHqY6tH8emQvqtBncjSKsyfTZuorPtTZgX7ovKKZHuuVGBVd1MTTBkWez1XXt2weN1sWBz6SfgRPQYEkNgz81QF')
+ ],
+ '2of2', gap_limit=2
+ )
+ wallet2b = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ # bip39: square page wood spy oil story rebel give milk screen slide shuffle, der: m/1234'/1'/0', p2wsh-p2sh multisig
+ keystore.from_xprv('Uprv9BbnKEXJxXaNvdEsRJ9VA9toYrSeFJh5UfGBpM2iKe8Uh7UhrM9K8ioL53s8gvCoGfirHHaqpABDAE7VUNw8LNU1DMJKVoWyeNKu9XcDC19'),
+ keystore.from_xpub('Upub5RuakRisg8h3F7u7iL2k3UJFa1uiK7xauHamzTxYBbn4PXbM7eajr6M9Q2VCr6cVGhfhqWQqxnABvtSATuVM1xzxk4nA189jJwzaMn1QX7V')
+ ],
+ '2of2', gap_limit=2
+ )
+
+ # bootstrap wallet1
+ funding_tx = Transaction('01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c11300')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 200000
+ self.assertEqual('d2bd6c9d332db8e2c50aa521cd50f963fba214645aab2f7556e061a412103e21', funding_txid)
+ wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet1 -> wallet2
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)]
+ tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ txid = tx.txid()
+ tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
+ self.assertEqual(txid, tx.txid())
+ self.assertFalse(tx.is_complete())
+ wallet1b.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet1a.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet1a.is_mine(wallet1a.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('01000000000101213e1012a461e056752fab5a6414a2fb63f950cd21a50ac5e2b82d339d6cbdd20000000000feffffff023075000000000000220020cc5e4cc05a76d0648cd0742768556317e9f8cc729aed077134287909035dba88888402000000000017a914187842cea9c15989a51ce7ca889a08b824bf8743870400483045022100ea2fbd3d8681cfafdcae1bdaaa64f92fb9872fb8f6bf03a2b7effcf7390b66c8022021a79eff7975479934f958f3766d6ac61d708c79b785e398b3bcd84b1039e9b501483045022100dbc4f1ec18f0e0deb4ff88d7d5b3d3b7b500a80d0c0f33efbd3262f0c8689095022074fd226c0b52e3716ad907d14cba9c79aca482a8f4a51662ca83a5b9db49e15b016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153ae00000000',
+ str(tx_copy))
+ self.assertEqual('6e9c3cd8788bdb970a124ea06136d52bc01cec4f9b1e217627d5e90ebe77d049', tx_copy.txid())
+ self.assertEqual('c58650fb77d04577fccb3e201deecbf691ab52ffb61cd2e57996c4d51f7e980b', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual(txid, tx_copy.txid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet2 -> wallet1
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
+ tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ txid = tx.txid()
+ tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
+ self.assertEqual(txid, tx.txid())
+ self.assertFalse(tx.is_complete())
+ wallet2b.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet2a.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet2a.is_mine(wallet2a.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('0100000000010149d077be0ee9d52776211e9b4fec1cc02bd53661a04e120a97db8b78d83c9c6e01000000232200204311edae835c7a5aa712c8ca644180f13a3b2f3b420fa879b181474724d6163cfeffffff0260ea00000000000017a9143025051b6b5ccd4baf30dfe2de8aa84f0dd567ed87a0860100000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c0400483045022100c254468bbe6b8bd1c8c01b6a223e46cc5c6b56fbba87d59575385ad249133b0e02207139688f8d6ae8076c92a266d98454d25c040d04c8e513a37bf7c32dad3e48210147304402204af5edbab2d674f6a9edef8c97b2f7fdf8ababedc7b287710cc7a64d4699358b022064e2d07f4bb32373be31b2003dc56b7b831a7c01419326efb3011c64b898b3f00147522102119f899075a131d4d519d4cdcf5de5907dc2df3b93d54b53ded852211d2b6cb12102fdb0f6775d4b6619257c43343ba5e7807b0164f1eb3f00f2b594ab9e53ab812652ae00000000',
+ str(tx_copy))
+ self.assertEqual('84b0dcb43022385f7a10e2710e5625a2be3cd6e390387b6100b55500d5eea8f6', tx_copy.txid())
+ self.assertEqual('7e561e25da843326e61fd20a40b72fcaeb8690176fc7c3fcbadb3a0146c8396c', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual(txid, tx_copy.txid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet level checks
+ self.assertEqual((0, funding_output_value - 165000 - 5000 + 100000, 0), wallet1a.get_balance())
+ self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_write):
+ wallet1a = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('phone guilt ancient scan defy gasp off rotate approve ill word exchange', '', True),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YPZ3ntVjqSCxiUUv2jikrUBU73Q3iJ7Y8iR41oYf991L5fanv7ciHjbjokdK2bjYqg1BzEUDxucU9qM5WRdBiY738wmgLP4')
+ ],
+ '1of2', gap_limit=2
+ )
+ # ^ second seed: kingdom now gift initial age right velvet exotic harbor enforce kingdom kick
+ wallet2 = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: uniform tank success logic lesson awesome stove elegant regular desert drip device, der: m/49'/1'/0'
+ keystore.from_xprv('uprv91HGbrNZTK4x8u22nbdYGzEuWPxjaHMREUi7CNhY64KsG5ZGnVM99uCa16EMSfrnaPTFxjbRdBZ2WiBkokoM8anzAy3Vpc52o88WPkitnxi'),
+ gap_limit=2
+ )
+
+ # bootstrap wallet1
+ funding_tx = Transaction('010000000001027e20990282eb29588375ad04936e1e991af3bc5b9c6f1ab62eca8c25becaef6a01000000171600140e6a17fadc8bafba830f3467a889f6b211d69a00fdffffff51847fd6bcbdfd1d1ea2c2d95c2d8de1e34c5f2bd9493e88a96a4e229f564e800100000017160014ecdf9fa06856f9643b1a73144bc76c24c67774a6fdffffff021e8501000000000017a91451991bfa68fbcb1e28aa0b1e060b7d24003352e38700093d000000000017a914b0b9f31bace76cdfae2c14abc03e223403d7dc4b870247304402205e19721b92c6afd70cd932acb50815a36ee32ab46a934147d62f02c13aeacf4702207289c4a4131ef86e27058ff70b6cb6bf0e8e81c6cbab6dddd7b0a9bc732960e4012103fe504411c21f7663caa0bbf28931f03fae7e0def7bc54851e0194dfb1e2c85ef02483045022100e969b65096fba4f8b24eb5bc622d2282076241621f3efe922cc2067f7a8a6be702203ec4047dd2a71b9c83eb6a0875a6d66b4d65864637576c06ed029d3d1a8654b0012102bbc8100dca67ba0297aba51296a4184d714204a5fc2eda34708360f37019a3dccfcc1300')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 4000000
+ self.assertEqual('1137c12de4ce0f5b08de8846ba14c0814351a7f0f31457c8ea51a5d4b3c891a3', funding_txid)
+ wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet1 -> wallet2
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)]
+ tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet1a.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet1a.is_mine(wallet1a.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('0100000001a391c8b3d4a551eac85714f3f0a7514381c014ba4688de085b0fcee42dc13711010000009200483045022100fcf03aeb97b66791372c18aa0dd651817cf458d941dd628c966f0305a023360f022016c534530e267b6a52f90e62aa9fb50ace609ffb21e472d3ba7b29db9b30050e014751210245c90e040d4f9d1fc136b3d4d6b7535bbb5df2bd27666c21977042cc1e05b5b02103c9a6bebfce6294488315e58137a279b2efe09f1f528ecf93b40675ded3cf0e5f52aefeffffff0240420f000000000017a9149573eb50f3136dff141ac304190f41c8becc92ce8738b32d000000000017a914b815d1b430ae9b632e3834ed537f7956325ee2a98700000000',
+ str(tx_copy))
+ self.assertEqual('1b7e94860b9681d4e371928d40fdbd4641e991aa74f1a211f239c887047e4a2a', tx_copy.txid())
+ self.assertEqual('1b7e94860b9681d4e371928d40fdbd4641e991aa74f1a211f239c887047e4a2a', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet2 -> wallet1
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)]
+ tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ self.assertEqual(wallet2.txin_type, tx.inputs()[0]['type'])
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet2.is_mine(wallet2.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('010000000001012a4a7e0487c839f211a2f174aa91e94146bdfd408d9271e3d481960b86947e1b00000000171600149fad840ed174584ee054bd26f3e411817338c5edfeffffff02e09304000000000017a914b0b9f31bace76cdfae2c14abc03e223403d7dc4b87d89a0a000000000017a9148ccd0efb2be5b412c4033715f560ed8f446c8ceb87024830450221009c816c3e0c40b37085244f0976f65635b8d711952bad9843c5f51e386fd37cc402202c34a4a7227182742d9f93e9f28c4bd30ded6514550f39614cb5ad00e46690070121038362bbf0b4918b37e9d7c75930ed3a78e3d445724cb5c37ade4a59b6e411fe4e00000000',
+ str(tx_copy))
+ self.assertEqual('f65edb0843ff44436dc5964fb6b298e157502b9b4a83dac6b82dd2d2a3247d0a', tx_copy.txid())
+ self.assertEqual('63efc09db4c7445eaaca9a5e7732202f42aec81a53b05d819f1918ce0cf3b84d', tx_copy.wtxid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+
+ wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+
+ # wallet level checks
+ self.assertEqual((0, funding_output_value - 1000000 - 5000 + 300000, 0), wallet1a.get_balance())
+ self.assertEqual((0, 1000000 - 5000 - 300000, 0), wallet2.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bump_fee_p2pkh(self, mock_write):
+ wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean')
+
+ # bootstrap wallet
+ funding_tx = Transaction('010000000001011f4db0ecd81f4388db316bc16efb4e9daf874cf4950d54ecb4c0fb372433d68500000000171600143d57fd9e88ef0e70cddb0d8b75ef86698cab0d44fdffffff0280969800000000001976a91472e34cebab371967b038ce41d0e8fa1fb983795e88ac86a0ae020000000017a9149188bc82bdcae077060ebb4f02201b73c806edc887024830450221008e0725d531bd7dee4d8d38a0f921d7b1213e5b16c05312a80464ecc2b649598d0220596d309cf66d5f47cb3df558dbb43c5023a7796a80f5a88b023287e45a4db6b9012102c34d61ceafa8c216f01e05707672354f8119334610f7933a3f80dd7fb6290296bd391400')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 10000000
+ self.assertEqual('03052739fcfa2ead5f8e57e26021b0c2c546bcd3d74c6e708d5046dc58d90762', funding_txid)
+ wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
+ coins = wallet.get_spendable_coins(domain=None, config=self.config)
+ tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325501
+ wallet.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet.is_mine(wallet.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual('01000000016207d958dc46508d706e4cd7d3bc46c5c2b02160e2578e5fad2efafc39270503000000006b483045022100df74e6a88085be1ff3a3fd96cf2ef03b5e33fa06788f56aa71649f0177d1bfc402206e36a7e6124863ac746d5288d6d47c1d1eac5d4ac3818e561a7a0f2c0a269429012102a807c07bd7975211078e916bdda061d97e98d59a3631a804aada2f9a3f5b587afdffffff02a02526000000000017a9145a71fc1a7a98ddd67be935ade1600981c0d066f987585d7200000000001976a914aab9af3fbee0ab4e5c00d53e92f66d4bcb44f1bd88acbd391400',
+ str(tx_copy))
+ self.assertEqual('44e6dd9529a253181112fc40cadd8ebb4c4359aacb91aa24c45556a1d00839b0', tx_copy.txid())
+ self.assertEqual('44e6dd9529a253181112fc40cadd8ebb4c4359aacb91aa24c45556a1d00839b0', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 2500000 - 5000, 0), wallet.get_balance())
+
+ # bump tx
+ tx = wallet.bump_fee(tx=Transaction(tx.serialize()), delta=5000)
+ tx.locktime = 1325501
+ self.assertFalse(tx.is_complete())
+
+ wallet.sign_transaction(tx, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ tx_copy = Transaction(tx.serialize())
+ self.assertEqual('01000000016207d958dc46508d706e4cd7d3bc46c5c2b02160e2578e5fad2efafc39270503000000006a473044022055b7e6b7e89a55740f7aa2ad1ffcd4b5c913f0de63cf512438921534bc9c3a8d022043b3b27bdc2da4cc6265e4cc9673a3780ccd5cd6f0ee2eaedb51720c15b7a00a012102a807c07bd7975211078e916bdda061d97e98d59a3631a804aada2f9a3f5b587afdffffff02a02526000000000017a9145a71fc1a7a98ddd67be935ade1600981c0d066f987d0497200000000001976a914aab9af3fbee0ab4e5c00d53e92f66d4bcb44f1bd88acbd391400',
+ str(tx_copy))
+ self.assertEqual('f26edcf20991dccedf16058adbee923db7057c9b102db660156b8142b6a59bc7', tx_copy.txid())
+ self.assertEqual('f26edcf20991dccedf16058adbee923db7057c9b102db660156b8142b6a59bc7', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 2500000 - 10000, 0), wallet.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_cpfp_p2pkh(self, mock_write):
+ wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean')
+
+ # bootstrap wallet
+ funding_tx = Transaction('010000000001010f40064d66d766144e17bb3276d96042fd5aee2196bcce7e415f839e55a83de800000000171600147b6d7c7763b9185b95f367cf28e4dc6d09441e73fdffffff02404b4c00000000001976a9141df43441a3a3ee563e560d3ddc7e07cc9f9c3cdb88ac009871000000000017a9143873281796131b1996d2f94ab265327ee5e9d6e28702473044022029c124e5a1e2c6fa12e45ccdbdddb45fec53f33b982389455b110fdb3fe4173102203b3b7656bca07e4eae3554900aa66200f46fec0af10e83daaa51d9e4e62a26f4012103c8f0460c245c954ef563df3b1743ea23b965f98b120497ac53bd6b8e8e9e0f9bbe391400')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 5000000
+ self.assertEqual('9973bf8918afa349b63934432386f585613b51034db6c8628b61ba2feb8a3668', funding_txid)
+ wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # cpfp tx
+ tx = wallet.cpfp(funding_tx, fee=50000)
+ tx.set_rbf(True)
+ tx.locktime = 1325502
+ wallet.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual('010000000168368aeb2fba618b62c8b64d03513b6185f58623433439b649a3af1889bf7399000000006a47304402203a0b369e46c5fbacb83044b7ab9d69ff7998774041d6870993504915bc495d210220272833b870d8abca516adb7dc4cb27892b1b6e4b52fbfeb592a72c3e795eb213012102a7536f0bfbc60c5a8e86e2b9df26431fc062f9f454016dbc26f2467e0bc98b3ffdffffff01f0874b00000000001976a9141df43441a3a3ee563e560d3ddc7e07cc9f9c3cdb88acbe391400',
+ str(tx_copy))
+ self.assertEqual('47500a425518b5542d94db1157f473b8cf322d31ea97a1a642fec19386cdb761', tx_copy.txid())
+ self.assertEqual('47500a425518b5542d94db1157f473b8cf322d31ea97a1a642fec19386cdb761', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 50000, 0), wallet.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_bump_fee_p2wpkh(self, mock_write):
+ wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
+
+ # bootstrap wallet
+ funding_tx = Transaction('01000000000102acd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba020000001716001455c7f5e0631d8e6f5f05dddb9f676cec48845532fdffffffd146691ef6a207b682b13da5f2388b1f0d2a2022c8cfb8dc27b65434ec9ec8f701000000171600147b3be8a7ceaf15f57d7df2a3d216bc3c259e3225fdffffff02a9875b000000000017a914ea5a99f83e71d1c1dfc5d0370e9755567fe4a141878096980000000000160014d4ca56fcbad98fb4dcafdc573a75d6a6fffb09b702483045022100dde1ba0c9a2862a65791b8d91295a6603207fb79635935a67890506c214dd96d022046c6616642ef5971103c1db07ac014e63fa3b0e15c5729eacdd3e77fcb7d2086012103a72410f185401bb5b10aaa30989c272b554dc6d53bda6da85a76f662723421af024730440220033d0be8f74e782fbcec2b396647c7715d2356076b442423f23552b617062312022063c95cafdc6d52ccf55c8ee0f9ceb0f57afb41ea9076eb74fe633f59c50c6377012103b96a4954d834fbcfb2bbf8cf7de7dc2b28bc3d661c1557d1fd1db1bfc123a94abb391400')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 10000000
+ self.assertEqual('52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0', funding_txid)
+ wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
+ coins = wallet.get_spendable_coins(domain=None, config=self.config)
+ tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325499
+ wallet.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet.is_mine(wallet.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual('01000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520100000000fdffffff02a02526000000000017a9145a71fc1a7a98ddd67be935ade1600981c0d066f987585d720000000000160014f0fe5c1867a174a12e70165e728a072619455ed50247304402205442705e988abe74bf391b293bb1b886674284a92ed0788c33024f9336d60aef022013a93049d3bed693254cd31a704d70bb988a36750f0b74d0a5b4d9e29c54ca9d0121028d4c44ca36d2c4bff3813df8d5d3c0278357521ecb892cd694c473c03970e4c5bb391400',
+ str(tx_copy))
+ self.assertEqual('b019bbad45a46ed25365e46e4cae6428fb12ae425977eb93011ffb294cb4977e', tx_copy.txid())
+ self.assertEqual('ba87313e2b3b42f1cc478843d4d53c72d6e06f6c66ac8cfbe2a59cdac2fd532d', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 2500000 - 5000, 0), wallet.get_balance())
+
+ # bump tx
+ tx = wallet.bump_fee(tx=Transaction(tx.serialize()), delta=5000)
+ tx.locktime = 1325500
+ self.assertFalse(tx.is_complete())
+
+ wallet.sign_transaction(tx, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ tx_copy = Transaction(tx.serialize())
+ self.assertEqual('01000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520100000000fdffffff02a02526000000000017a9145a71fc1a7a98ddd67be935ade1600981c0d066f987d049720000000000160014f0fe5c1867a174a12e70165e728a072619455ed5024730440220517fed3a902b5b41fa718ffd5f229b835b8ed26f23433c4ea437d24eff66d15b0220526854a6ebcd351ab2373d0e7c4e20f17c420520b5d570c2df7ca1d773d6a55d0121028d4c44ca36d2c4bff3813df8d5d3c0278357521ecb892cd694c473c03970e4c5bc391400',
+ str(tx_copy))
+ self.assertEqual('9a1c0ef7e871798b86074c7f8dd1e81b6d9a758ff07e0059eee31dc6fbf4f438', tx_copy.txid())
+ self.assertEqual('59144d30c911ac33359b0a32d5a3fdd2ca806982c85838e193eb95f5d315e813', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 2500000 - 10000, 0), wallet.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_cpfp_p2wpkh(self, mock_write):
+ wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
+
+ # bootstrap wallet
+ funding_tx = Transaction('01000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520000000017160014ba9ca815474a674ff1efb3fc82cf0f3460de8c57fdffffff0230390f000000000017a9148b59abaca8215c0d4b18cbbf715550aa2b50c85b87404b4c000000000016001483c3bc7234f17a209cc5dcce14903b54ee4dab9002473044022038a05f7d38bcf810dfebb39f1feda5cc187da4cf5d6e56986957ddcccedc75d302203ab67ccf15431b4e2aeeab1582b9a5a7821e7ac4be8ebf512505dbfdc7e094fd0121032168234e0ba465b8cedc10173ea9391725c0f6d9fa517641af87926626a5144abd391400')
+ funding_txid = funding_tx.txid()
+ funding_output_value = 5000000
+ self.assertEqual('c36a6e1cd54df108e69574f70bc9b88dc13beddc70cfad9feb7f8f6593255d4a', funding_txid)
+ wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # cpfp tx
+ tx = wallet.cpfp(funding_tx, fee=50000)
+ tx.set_rbf(True)
+ tx.locktime = 1325501
+ wallet.sign_transaction(tx, password=None)
+
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+ self.assertEqual(tx.wtxid(), tx_copy.wtxid())
+ self.assertEqual('010000000001014a5d2593658f7feb9fadcf70dced3bc18db8c90bf77495e608f14dd51c6e6ac30100000000fdffffff01f0874b000000000016001483c3bc7234f17a209cc5dcce14903b54ee4dab900248304502210098fbe458a9f1c595d6bf63962fad00300a7b60c6dd8b2e7625f3804a3bf1086602204bc8a46fb162be8f85a23644eccf9f4223fa092f5c861144676a34dc83a7c39d012102a6ff1ffc189b4776b78e20edca969cc45da3e610cc0cc79925604be43fee469fbd391400',
+ str(tx_copy))
+ self.assertEqual('38a21c67336232c88ae15311f329197c69ee70e872f8acb5bc9c2b6417c35ad8', tx_copy.txid())
+ self.assertEqual('b5b8264ed5f3e03d48ef82fa2a25278cd9c0563fa78e557f370b7e0558293172', tx_copy.wtxid())
+
+ wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual((0, funding_output_value - 50000, 0), wallet.get_balance())
+
+ @needs_test_with_all_ecc_implementations
+ def test_sweep_p2pk(self):
+
+ class NetworkMock:
+ relay_fee = 1000
+ def get_local_height(self): return 1325785
+ def listunspent_for_scripthash(self, scripthash):
+ if scripthash == '460e4fb540b657d775d84ff4955c9b13bd954c2adc26a6b998331343f85b6a45':
+ return [{'tx_hash': 'ac24de8b58e826f60bd7b9ba31670bdfc3e8aedb2f28d0e91599d741569e3429', 'tx_pos': 1, 'height': 1325785, 'value': 1000000}]
+ else:
+ return []
+
+ privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ]
+ network = NetworkMock()
+ dest_addr = 'tb1q3ws2p0qjk5vrravv065xqlnkckvzcpclk79eu2'
+ tx = sweep(privkeys, network, config=None, recipient=dest_addr, fee=5000)
+
+ tx_copy = Transaction(tx.serialize())
+ self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400',
+ str(tx_copy))
+ self.assertEqual('7f827fc5256c274fd1094eb7e020c8ded0baf820356f61aa4f14a9093b0ea0ee', tx_copy.txid())
+ self.assertEqual('7f827fc5256c274fd1094eb7e020c8ded0baf820356f61aa4f14a9093b0ea0ee', tx_copy.wtxid())
+
+
+class TestWalletOfflineSigning(TestCaseForTestnet):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.electrum_path = tempfile.mkdtemp()
+ cls.config = SimpleConfig({'electrum_path': cls.electrum_path})
+
+ @classmethod
+ def tearDownClass(cls):
+ super().tearDownClass()
+ shutil.rmtree(cls.electrum_path)
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_xpub_p2pkh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/44'/1'/0'
+ keystore.from_xprv('tprv8gfKwjuAaqtHgqxMh1tosAQ28XvBMkcY5NeFRA3pZMpz6MR4H4YZ3MJM4fvNPnRKeXR1Td2vQGgjorNXfo94WvT5CYDsPAqjHxSn436G1Eu'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_standard_wallet(
+ keystore.from_xpub('tpubDDMN69wQjDZxaJz9afZQGa48hZS7X5oSegF2hg67yddNvqfpuTN9DqvDEp7YyVf7AzXnqBqHdLhzTAStHvsoMDDb8WoJQzNrcHgDJHVYgQF'),
+ gap_limit=4
+ )
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000116e9c9dac2651672316aab3b9553257b6942c5f762c5d795776d9cfa504f183c000000000000fdffffff8085019852fada9da84b58dcf753d292dde314a19f5a5527f6588fa2566142130000000000fdffffffa4154a48db20ce538b28722a89c6b578bd5b5d60d6d7b52323976339e39405230000000000fdffffff0b5ef43f843a96364aebd708e25ea1bdcf2c7df7d0d995560b8b1be5f357b64f0100000000fdffffffd41dfe1199c76fdb3f20e9947ea31136d032d9da48c5e45d85c8f440e2351a510100000000fdffffff5bd015d17e4a1837b01c24ebb4a6b394e3da96a85442bd7dc6abddfbf16f20510000000000fdffffff13a3e7f80b1bd46e38f2abc9e2f335c18a4b0af1778133c7f1c3caae9504345c0200000000fdffffffdf4fc1ab21bca69d18544ddb10a913cd952dbc730ab3d236dd9471445ff405680100000000fdffffffe0424d78a30d5e60ac6b26e2274d7d6e7c6b78fe0b49bdc3ac4dd2147c9535750100000000fdffffff7ab6dd6b3c0d44b0fef0fdc9ab0ad6eee23eef799eee29c005d52bc4461998760000000000fdffffff48a77e5053a21acdf4f235ce00c82c9bc1704700f54d217f6a30704711b9737d0000000000fdffffff86918b39c1d9bb6f34d9b082182f73cedd15504331164dc2b186e95c568ccb870000000000fdffffff15a847356cbb44be67f345965bb3f2589e2fec1c9a0ada21fd28225dcc602e8f0100000000fdffffff9a2875297f81dfd3b77426d63f621db350c270cc28c634ad86b9969ee33ac6960000000000fdffffffd6eeb1d1833e00967083d1ab86fa5a2e44355bd613d9277135240fe6f60148a20100000000fdffffffd8a6e5a9b68a65ff88220ca33e36faf6f826ae8c5c8a13fe818a5e63828b68a40100000000fdffffff73aab8471f82092e45ed1b1afeffdb49ea1ec74ce4853f971812f6a72a7e85aa0000000000fdffffffacd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba0000000000fdffffff1eddd5e13bef1aba1ff151762b5860837daa9b39db1eae8ea8227c81a5a1c8ba0000000000fdffffff67a096ff7c343d39e96929798097f6d7a61156bbdb905fbe534ba36f273271d40100000000fdffffff109a671eb7daf6dcd07c0ceff99f2de65864ab36d64fb3a890bab951569adeee0100000000fdffffff4f1bdc64da8056d08f79db7f5348d1de55946e57aa7c8279499c703889b6e0fd0200000000fdffffff042f280000000000001600149c756aa33f4f89418b33872a973274b5445c727b80969800000000001600146c540c1c9f546004539f45318b8d9f4d7b4857ef80969800000000001976a91422a6daa4a7b695c8a2dd104d47c5dc73d655c96f88ac809698000000000017a914a6885437e0762013facbda93894202a0fe86e35f8702473044022075ef5f04d7a63347064938e15a0c74277a79e5c9d32a26e39e8a517a44d565cc022015246790fb5b29c9bf3eded1b95699b1635bcfc6d521886fddf1135ba1b988ec012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe02473044022061aa9b0d9649ffd7259bc54b35f678565dbbe11507d348dd8885522eaf1fa70c02202cc79de09e8e63e8d57fde6ef66c079ddac4d9828e1936a9db833d4c142615c3012103a8f58fc1f5625f18293403104874f2d38c9279f777e512570e4199c7d292b81b0247304402207744dc1ab0bf77c081b58540c4321d090c0a24a32742a361aa55ad86f0c7c24e02201a9b0dd78b63b495ab5a0b5b161c54cb085d70683c90e188bb4dc2e41e142f6601210361fb354f8259abfcbfbdda36b7cb4c3b05a3ca3d68dd391fd8376e920d93870d0247304402204803e423c321acc6c12cb0ebf196d2906842fdfed6de977cc78277052ee5f15002200634670c1dc25e6b1787a65d3e09c8e6bb0340238d90b9d98887e8fd53944e080121031104c60d027123bf8676bcaefaa66c001a0d3d379dc4a9492a567a9e1004452d02473044022050e4b5348d30011a22b6ae8b43921d29249d88ea71b1fbaa2d9c22dfdef58b7002201c5d5e143aa8835454f61b0742226ebf8cd466bcc2cdcb1f77b92e473d3b13190121030496b9d49aa8efece4f619876c60a77d2c0dc846390ecdc5d9acbfa1bb3128760247304402204d6a9b986e1a0e3473e8aef84b3eb7052442a76dfd7631e35377f141496a55490220131ab342853c01e31f111436f8461e28bc95883b871ca0e01b5f57146e79d7bb012103262ffbc88e25296056a3c65c880e3686297e07f360e6b80f1219d65b0900e84e02483045022100c8ffacf92efa1dddef7e858a241af7a80adcc2489bcc325195970733b1f35fac022076f40c26023a228041a9665c5290b9918d06f03b716e4d8f6d47e79121c7eb37012102d9ba7e02d7cd7dd24302f823b3114c99da21549c663f72440dc87e8ba412120902483045022100b55545d84e43d001bbc10a981f184e7d3b98a7ed6689863716cab053b3655a2f0220537eb76a695fbe86bf020b4b6f7ae93b506d778bbd0885f0a61067616a2c8bce0121034a57f2fa2c32c9246691f6a922fb1ebdf1468792bae7eff253a99fc9f2a5023902483045022100f1d4408463dbfe257f9f778d5e9c8cdb97c8b1d395dbd2e180bc08cad306492c022002a024e19e1a406eaa24467f033659de09ab58822987281e28bb6359288337bd012103e91daa18d924eea62011ce596e15b6d683975cf724ea5bf69a8e2022c26fc12f0247304402204f1e12b923872f396e5e1a3aa94b0b2e86b4ce448f4349a017631db26d7dff8a022069899a05de2ad2bbd8e0202c56ab1025a7db9a4998eea70744e3c367d2a7eb71012103b0eee86792dbef1d4a49bc4ea32d197c8c15d27e6e0c5c33e58e409e26d4a39a0247304402201787dacdb92e0df6ad90226649f0e8321287d0bd8fddc536a297dd19b5fc103e022001fe89300a76e5b46d0e3f7e39e0ee26cc83b71d59a2a5da1dd7b13350cd0c07012103afb1e43d7ec6b7999ef0f1093069e68fe1dfe5d73fc6cfb4f7a5022f7098758c02483045022100acc1212bba0fe4fcc6c3ae5cf8e25f221f140c8444d3c08dfc53a93630ac25da02203f12982847244bd9421ef340293f3a38d2ab5d028af60769e46fcc7d81312e7e012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024830450221009c04934102402949484b21899271c3991c007b783b8efc85a3c3d24641ac7c24022006fb1895ce969d08a2cb29413e1a85427c7e85426f7a185108ca44b5a0328cb301210360248db4c7d7f76fe231998d2967104fee04df8d8da34f10101cc5523e82648c02483045022100b11fe61b393fa5dbe18ab98f65c249345b429b13f69ee2d1b1335725b24a0e73022010960cdc5565cbc81885c8ed95142435d3c202dfa5a3dc5f50f3914c106335ce0121029c878610c34c21381cda12f6f36ab88bf60f5f496c1b82c357b8ac448713e7b50247304402200ca080db069c15bbf98e1d4dff68d0aea51227ff5d17a8cf67ceae464c22bbb0022051e7331c0918cbb71bb2cef29ca62411454508a16180b0fb5df94248890840df0121028f0be0cde43ff047edbda42c91c37152449d69789eb812bb2e148e4f22472c0f0247304402201fefe258938a2c481d5a745ef3aa8d9f8124bbe7f1f8c693e2ddce4ddc9a927c02204049e0060889ede8fda975edf896c03782d71ba53feb51b04f5ae5897d7431dc012103946730b480f52a43218a9edce240e8b234790e21df5e96482703d81c3c19d3f1024730440220126a6a56dbe69af78d156626fc9cf41d6aac0c07b8b5f0f8491f68db5e89cb5002207ee6ed6f2f41da256f3c1e79679a3de6cf34cc08b940b82be14aefe7da031a6b012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024730440220363204a1586d7f13c148295122cbf9ec7939685e3cadab81d6d9e921436d21b7022044626b8c2bd4aa7c167d74bc4e9eb9d0744e29ce0ad906d78e10d6d854f23d170121037fb9c51716739bb4c146857fab5a783372f72a65987d61f3b58c74360f4328dd0247304402207925a4c2a3a6b76e10558717ee28fcb8c6fde161b9dc6382239af9f372ace99902204a58e31ce0b4a4804a42d2224331289311ded2748062c92c8aca769e81417a4c012102e18a8c235b48e41ef98265a8e07fa005d2602b96d585a61ad67168d74e7391cb02483045022100bbfe060479174a8d846b5a897526003eb2220ba307a5fee6e1e8de3e4e8b38fd02206723857301d447f67ac98a5a5c2b80ef6820e98fae213db1720f93d91161803b01210386728e2ac3ecee15f58d0505ee26f86a68f08c702941ffaf2fb7213e5026aea10247304402203a2613ae68f697eb02b5b7d18e3c4236966dac2b3a760e3021197d76e9ad4239022046f9067d3df650fcabbdfd250308c64f90757dec86f0b08813c979a42d06a6ec012102a1d7ee1cb4dc502f899aaafae0a2eb6cbf80d9a1073ae60ddcaabc3b1d1f15df02483045022100ab1bea2cc5388428fd126c7801550208701e21564bd4bd00cfd4407cfafc1acd0220508ee587f080f3c80a5c0b2175b58edd84b755e659e2135b3152044d75ebc4b501210236dd1b7f27a296447d0eb3750e1bdb2d53af50b31a72a45511dc1ec3fe7a684a19391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('98574bc5f6e75769eb0c93d41453cc1dfbd15c14e63cc3c42f37cdbd08858762', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual('d9c21696eca80321933e7444ca928aaf25eeda81aaa2f4e5c085d4d0a9cf7aa7', tx.txid())
+ self.assertEqual('d9c21696eca80321933e7444ca928aaf25eeda81aaa2f4e5c085d4d0a9cf7aa7', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_xpub_p2wpkh_p2sh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/49'/1'/0'
+ keystore.from_xprv('uprv8zHHrMQMQ26utWwNJ5MK2SXpB9hbmy7pbPaneii69xT8cZTyFpxQFxkknGWKP8dxBTZhzy7yP6cCnLrRCQjzJDk3G61SjZpxhFQuB2NR8a5'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_standard_wallet(
+ keystore.from_xpub('upub5DGeFrwFEPfD711qQ6tKPaUYjBY6BRqfxcWPT77hiHz7VMo7oNGeom5EdXoKXEazePyoN3ueJMqHBfp3MwmsaD8k9dFHoa8KGeVXev7Pbg2'),
+ gap_limit=4
+ )
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000116e9c9dac2651672316aab3b9553257b6942c5f762c5d795776d9cfa504f183c000000000000fdffffff8085019852fada9da84b58dcf753d292dde314a19f5a5527f6588fa2566142130000000000fdffffffa4154a48db20ce538b28722a89c6b578bd5b5d60d6d7b52323976339e39405230000000000fdffffff0b5ef43f843a96364aebd708e25ea1bdcf2c7df7d0d995560b8b1be5f357b64f0100000000fdffffffd41dfe1199c76fdb3f20e9947ea31136d032d9da48c5e45d85c8f440e2351a510100000000fdffffff5bd015d17e4a1837b01c24ebb4a6b394e3da96a85442bd7dc6abddfbf16f20510000000000fdffffff13a3e7f80b1bd46e38f2abc9e2f335c18a4b0af1778133c7f1c3caae9504345c0200000000fdffffffdf4fc1ab21bca69d18544ddb10a913cd952dbc730ab3d236dd9471445ff405680100000000fdffffffe0424d78a30d5e60ac6b26e2274d7d6e7c6b78fe0b49bdc3ac4dd2147c9535750100000000fdffffff7ab6dd6b3c0d44b0fef0fdc9ab0ad6eee23eef799eee29c005d52bc4461998760000000000fdffffff48a77e5053a21acdf4f235ce00c82c9bc1704700f54d217f6a30704711b9737d0000000000fdffffff86918b39c1d9bb6f34d9b082182f73cedd15504331164dc2b186e95c568ccb870000000000fdffffff15a847356cbb44be67f345965bb3f2589e2fec1c9a0ada21fd28225dcc602e8f0100000000fdffffff9a2875297f81dfd3b77426d63f621db350c270cc28c634ad86b9969ee33ac6960000000000fdffffffd6eeb1d1833e00967083d1ab86fa5a2e44355bd613d9277135240fe6f60148a20100000000fdffffffd8a6e5a9b68a65ff88220ca33e36faf6f826ae8c5c8a13fe818a5e63828b68a40100000000fdffffff73aab8471f82092e45ed1b1afeffdb49ea1ec74ce4853f971812f6a72a7e85aa0000000000fdffffffacd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba0000000000fdffffff1eddd5e13bef1aba1ff151762b5860837daa9b39db1eae8ea8227c81a5a1c8ba0000000000fdffffff67a096ff7c343d39e96929798097f6d7a61156bbdb905fbe534ba36f273271d40100000000fdffffff109a671eb7daf6dcd07c0ceff99f2de65864ab36d64fb3a890bab951569adeee0100000000fdffffff4f1bdc64da8056d08f79db7f5348d1de55946e57aa7c8279499c703889b6e0fd0200000000fdffffff042f280000000000001600149c756aa33f4f89418b33872a973274b5445c727b80969800000000001600146c540c1c9f546004539f45318b8d9f4d7b4857ef80969800000000001976a91422a6daa4a7b695c8a2dd104d47c5dc73d655c96f88ac809698000000000017a914a6885437e0762013facbda93894202a0fe86e35f8702473044022075ef5f04d7a63347064938e15a0c74277a79e5c9d32a26e39e8a517a44d565cc022015246790fb5b29c9bf3eded1b95699b1635bcfc6d521886fddf1135ba1b988ec012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe02473044022061aa9b0d9649ffd7259bc54b35f678565dbbe11507d348dd8885522eaf1fa70c02202cc79de09e8e63e8d57fde6ef66c079ddac4d9828e1936a9db833d4c142615c3012103a8f58fc1f5625f18293403104874f2d38c9279f777e512570e4199c7d292b81b0247304402207744dc1ab0bf77c081b58540c4321d090c0a24a32742a361aa55ad86f0c7c24e02201a9b0dd78b63b495ab5a0b5b161c54cb085d70683c90e188bb4dc2e41e142f6601210361fb354f8259abfcbfbdda36b7cb4c3b05a3ca3d68dd391fd8376e920d93870d0247304402204803e423c321acc6c12cb0ebf196d2906842fdfed6de977cc78277052ee5f15002200634670c1dc25e6b1787a65d3e09c8e6bb0340238d90b9d98887e8fd53944e080121031104c60d027123bf8676bcaefaa66c001a0d3d379dc4a9492a567a9e1004452d02473044022050e4b5348d30011a22b6ae8b43921d29249d88ea71b1fbaa2d9c22dfdef58b7002201c5d5e143aa8835454f61b0742226ebf8cd466bcc2cdcb1f77b92e473d3b13190121030496b9d49aa8efece4f619876c60a77d2c0dc846390ecdc5d9acbfa1bb3128760247304402204d6a9b986e1a0e3473e8aef84b3eb7052442a76dfd7631e35377f141496a55490220131ab342853c01e31f111436f8461e28bc95883b871ca0e01b5f57146e79d7bb012103262ffbc88e25296056a3c65c880e3686297e07f360e6b80f1219d65b0900e84e02483045022100c8ffacf92efa1dddef7e858a241af7a80adcc2489bcc325195970733b1f35fac022076f40c26023a228041a9665c5290b9918d06f03b716e4d8f6d47e79121c7eb37012102d9ba7e02d7cd7dd24302f823b3114c99da21549c663f72440dc87e8ba412120902483045022100b55545d84e43d001bbc10a981f184e7d3b98a7ed6689863716cab053b3655a2f0220537eb76a695fbe86bf020b4b6f7ae93b506d778bbd0885f0a61067616a2c8bce0121034a57f2fa2c32c9246691f6a922fb1ebdf1468792bae7eff253a99fc9f2a5023902483045022100f1d4408463dbfe257f9f778d5e9c8cdb97c8b1d395dbd2e180bc08cad306492c022002a024e19e1a406eaa24467f033659de09ab58822987281e28bb6359288337bd012103e91daa18d924eea62011ce596e15b6d683975cf724ea5bf69a8e2022c26fc12f0247304402204f1e12b923872f396e5e1a3aa94b0b2e86b4ce448f4349a017631db26d7dff8a022069899a05de2ad2bbd8e0202c56ab1025a7db9a4998eea70744e3c367d2a7eb71012103b0eee86792dbef1d4a49bc4ea32d197c8c15d27e6e0c5c33e58e409e26d4a39a0247304402201787dacdb92e0df6ad90226649f0e8321287d0bd8fddc536a297dd19b5fc103e022001fe89300a76e5b46d0e3f7e39e0ee26cc83b71d59a2a5da1dd7b13350cd0c07012103afb1e43d7ec6b7999ef0f1093069e68fe1dfe5d73fc6cfb4f7a5022f7098758c02483045022100acc1212bba0fe4fcc6c3ae5cf8e25f221f140c8444d3c08dfc53a93630ac25da02203f12982847244bd9421ef340293f3a38d2ab5d028af60769e46fcc7d81312e7e012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024830450221009c04934102402949484b21899271c3991c007b783b8efc85a3c3d24641ac7c24022006fb1895ce969d08a2cb29413e1a85427c7e85426f7a185108ca44b5a0328cb301210360248db4c7d7f76fe231998d2967104fee04df8d8da34f10101cc5523e82648c02483045022100b11fe61b393fa5dbe18ab98f65c249345b429b13f69ee2d1b1335725b24a0e73022010960cdc5565cbc81885c8ed95142435d3c202dfa5a3dc5f50f3914c106335ce0121029c878610c34c21381cda12f6f36ab88bf60f5f496c1b82c357b8ac448713e7b50247304402200ca080db069c15bbf98e1d4dff68d0aea51227ff5d17a8cf67ceae464c22bbb0022051e7331c0918cbb71bb2cef29ca62411454508a16180b0fb5df94248890840df0121028f0be0cde43ff047edbda42c91c37152449d69789eb812bb2e148e4f22472c0f0247304402201fefe258938a2c481d5a745ef3aa8d9f8124bbe7f1f8c693e2ddce4ddc9a927c02204049e0060889ede8fda975edf896c03782d71ba53feb51b04f5ae5897d7431dc012103946730b480f52a43218a9edce240e8b234790e21df5e96482703d81c3c19d3f1024730440220126a6a56dbe69af78d156626fc9cf41d6aac0c07b8b5f0f8491f68db5e89cb5002207ee6ed6f2f41da256f3c1e79679a3de6cf34cc08b940b82be14aefe7da031a6b012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024730440220363204a1586d7f13c148295122cbf9ec7939685e3cadab81d6d9e921436d21b7022044626b8c2bd4aa7c167d74bc4e9eb9d0744e29ce0ad906d78e10d6d854f23d170121037fb9c51716739bb4c146857fab5a783372f72a65987d61f3b58c74360f4328dd0247304402207925a4c2a3a6b76e10558717ee28fcb8c6fde161b9dc6382239af9f372ace99902204a58e31ce0b4a4804a42d2224331289311ded2748062c92c8aca769e81417a4c012102e18a8c235b48e41ef98265a8e07fa005d2602b96d585a61ad67168d74e7391cb02483045022100bbfe060479174a8d846b5a897526003eb2220ba307a5fee6e1e8de3e4e8b38fd02206723857301d447f67ac98a5a5c2b80ef6820e98fae213db1720f93d91161803b01210386728e2ac3ecee15f58d0505ee26f86a68f08c702941ffaf2fb7213e5026aea10247304402203a2613ae68f697eb02b5b7d18e3c4236966dac2b3a760e3021197d76e9ad4239022046f9067d3df650fcabbdfd250308c64f90757dec86f0b08813c979a42d06a6ec012102a1d7ee1cb4dc502f899aaafae0a2eb6cbf80d9a1073ae60ddcaabc3b1d1f15df02483045022100ab1bea2cc5388428fd126c7801550208701e21564bd4bd00cfd4407cfafc1acd0220508ee587f080f3c80a5c0b2175b58edd84b755e659e2135b3152044d75ebc4b501210236dd1b7f27a296447d0eb3750e1bdb2d53af50b31a72a45511dc1ec3fe7a684a19391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('98574bc5f6e75769eb0c93d41453cc1dfbd15c14e63cc3c42f37cdbd08858762', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325341
+
+ self.assertFalse(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('3f0d188519237478258ad2bf881643618635d11c2bb95512e830fcf2eda3c522', tx_copy.txid())
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('3f0d188519237478258ad2bf881643618635d11c2bb95512e830fcf2eda3c522', tx.txid())
+ self.assertEqual('27b78ec072a403b0545258e7a1a8d494e4b6fd48bf77f4251a12160c92207cbc', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_xpub_p2wpkh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/84'/1'/0'
+ keystore.from_xprv('vprv9K9hbuA23Bidgj1KRSHUZMa59jJLeZBpXPVn4RP7sBLArNhZxJjw4AX7aQmVTErDt4YFC11ptMLjbwxgrsH8GLQ1cx77KggWeVPeDBjr9xM'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_standard_wallet(
+ keystore.from_xpub('vpub5Y941QgusZGvuD5nXTpUvVWohm8q41uftcRNronjRWs9jB2iVr4BbxqbRfAoQjWHgJtDCQEXChgfsPbEuBnidtkFztZSD3zDKTrtwXa2LCa'),
+ gap_limit=4
+ )
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000116e9c9dac2651672316aab3b9553257b6942c5f762c5d795776d9cfa504f183c000000000000fdffffff8085019852fada9da84b58dcf753d292dde314a19f5a5527f6588fa2566142130000000000fdffffffa4154a48db20ce538b28722a89c6b578bd5b5d60d6d7b52323976339e39405230000000000fdffffff0b5ef43f843a96364aebd708e25ea1bdcf2c7df7d0d995560b8b1be5f357b64f0100000000fdffffffd41dfe1199c76fdb3f20e9947ea31136d032d9da48c5e45d85c8f440e2351a510100000000fdffffff5bd015d17e4a1837b01c24ebb4a6b394e3da96a85442bd7dc6abddfbf16f20510000000000fdffffff13a3e7f80b1bd46e38f2abc9e2f335c18a4b0af1778133c7f1c3caae9504345c0200000000fdffffffdf4fc1ab21bca69d18544ddb10a913cd952dbc730ab3d236dd9471445ff405680100000000fdffffffe0424d78a30d5e60ac6b26e2274d7d6e7c6b78fe0b49bdc3ac4dd2147c9535750100000000fdffffff7ab6dd6b3c0d44b0fef0fdc9ab0ad6eee23eef799eee29c005d52bc4461998760000000000fdffffff48a77e5053a21acdf4f235ce00c82c9bc1704700f54d217f6a30704711b9737d0000000000fdffffff86918b39c1d9bb6f34d9b082182f73cedd15504331164dc2b186e95c568ccb870000000000fdffffff15a847356cbb44be67f345965bb3f2589e2fec1c9a0ada21fd28225dcc602e8f0100000000fdffffff9a2875297f81dfd3b77426d63f621db350c270cc28c634ad86b9969ee33ac6960000000000fdffffffd6eeb1d1833e00967083d1ab86fa5a2e44355bd613d9277135240fe6f60148a20100000000fdffffffd8a6e5a9b68a65ff88220ca33e36faf6f826ae8c5c8a13fe818a5e63828b68a40100000000fdffffff73aab8471f82092e45ed1b1afeffdb49ea1ec74ce4853f971812f6a72a7e85aa0000000000fdffffffacd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba0000000000fdffffff1eddd5e13bef1aba1ff151762b5860837daa9b39db1eae8ea8227c81a5a1c8ba0000000000fdffffff67a096ff7c343d39e96929798097f6d7a61156bbdb905fbe534ba36f273271d40100000000fdffffff109a671eb7daf6dcd07c0ceff99f2de65864ab36d64fb3a890bab951569adeee0100000000fdffffff4f1bdc64da8056d08f79db7f5348d1de55946e57aa7c8279499c703889b6e0fd0200000000fdffffff042f280000000000001600149c756aa33f4f89418b33872a973274b5445c727b80969800000000001600146c540c1c9f546004539f45318b8d9f4d7b4857ef80969800000000001976a91422a6daa4a7b695c8a2dd104d47c5dc73d655c96f88ac809698000000000017a914a6885437e0762013facbda93894202a0fe86e35f8702473044022075ef5f04d7a63347064938e15a0c74277a79e5c9d32a26e39e8a517a44d565cc022015246790fb5b29c9bf3eded1b95699b1635bcfc6d521886fddf1135ba1b988ec012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe02473044022061aa9b0d9649ffd7259bc54b35f678565dbbe11507d348dd8885522eaf1fa70c02202cc79de09e8e63e8d57fde6ef66c079ddac4d9828e1936a9db833d4c142615c3012103a8f58fc1f5625f18293403104874f2d38c9279f777e512570e4199c7d292b81b0247304402207744dc1ab0bf77c081b58540c4321d090c0a24a32742a361aa55ad86f0c7c24e02201a9b0dd78b63b495ab5a0b5b161c54cb085d70683c90e188bb4dc2e41e142f6601210361fb354f8259abfcbfbdda36b7cb4c3b05a3ca3d68dd391fd8376e920d93870d0247304402204803e423c321acc6c12cb0ebf196d2906842fdfed6de977cc78277052ee5f15002200634670c1dc25e6b1787a65d3e09c8e6bb0340238d90b9d98887e8fd53944e080121031104c60d027123bf8676bcaefaa66c001a0d3d379dc4a9492a567a9e1004452d02473044022050e4b5348d30011a22b6ae8b43921d29249d88ea71b1fbaa2d9c22dfdef58b7002201c5d5e143aa8835454f61b0742226ebf8cd466bcc2cdcb1f77b92e473d3b13190121030496b9d49aa8efece4f619876c60a77d2c0dc846390ecdc5d9acbfa1bb3128760247304402204d6a9b986e1a0e3473e8aef84b3eb7052442a76dfd7631e35377f141496a55490220131ab342853c01e31f111436f8461e28bc95883b871ca0e01b5f57146e79d7bb012103262ffbc88e25296056a3c65c880e3686297e07f360e6b80f1219d65b0900e84e02483045022100c8ffacf92efa1dddef7e858a241af7a80adcc2489bcc325195970733b1f35fac022076f40c26023a228041a9665c5290b9918d06f03b716e4d8f6d47e79121c7eb37012102d9ba7e02d7cd7dd24302f823b3114c99da21549c663f72440dc87e8ba412120902483045022100b55545d84e43d001bbc10a981f184e7d3b98a7ed6689863716cab053b3655a2f0220537eb76a695fbe86bf020b4b6f7ae93b506d778bbd0885f0a61067616a2c8bce0121034a57f2fa2c32c9246691f6a922fb1ebdf1468792bae7eff253a99fc9f2a5023902483045022100f1d4408463dbfe257f9f778d5e9c8cdb97c8b1d395dbd2e180bc08cad306492c022002a024e19e1a406eaa24467f033659de09ab58822987281e28bb6359288337bd012103e91daa18d924eea62011ce596e15b6d683975cf724ea5bf69a8e2022c26fc12f0247304402204f1e12b923872f396e5e1a3aa94b0b2e86b4ce448f4349a017631db26d7dff8a022069899a05de2ad2bbd8e0202c56ab1025a7db9a4998eea70744e3c367d2a7eb71012103b0eee86792dbef1d4a49bc4ea32d197c8c15d27e6e0c5c33e58e409e26d4a39a0247304402201787dacdb92e0df6ad90226649f0e8321287d0bd8fddc536a297dd19b5fc103e022001fe89300a76e5b46d0e3f7e39e0ee26cc83b71d59a2a5da1dd7b13350cd0c07012103afb1e43d7ec6b7999ef0f1093069e68fe1dfe5d73fc6cfb4f7a5022f7098758c02483045022100acc1212bba0fe4fcc6c3ae5cf8e25f221f140c8444d3c08dfc53a93630ac25da02203f12982847244bd9421ef340293f3a38d2ab5d028af60769e46fcc7d81312e7e012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024830450221009c04934102402949484b21899271c3991c007b783b8efc85a3c3d24641ac7c24022006fb1895ce969d08a2cb29413e1a85427c7e85426f7a185108ca44b5a0328cb301210360248db4c7d7f76fe231998d2967104fee04df8d8da34f10101cc5523e82648c02483045022100b11fe61b393fa5dbe18ab98f65c249345b429b13f69ee2d1b1335725b24a0e73022010960cdc5565cbc81885c8ed95142435d3c202dfa5a3dc5f50f3914c106335ce0121029c878610c34c21381cda12f6f36ab88bf60f5f496c1b82c357b8ac448713e7b50247304402200ca080db069c15bbf98e1d4dff68d0aea51227ff5d17a8cf67ceae464c22bbb0022051e7331c0918cbb71bb2cef29ca62411454508a16180b0fb5df94248890840df0121028f0be0cde43ff047edbda42c91c37152449d69789eb812bb2e148e4f22472c0f0247304402201fefe258938a2c481d5a745ef3aa8d9f8124bbe7f1f8c693e2ddce4ddc9a927c02204049e0060889ede8fda975edf896c03782d71ba53feb51b04f5ae5897d7431dc012103946730b480f52a43218a9edce240e8b234790e21df5e96482703d81c3c19d3f1024730440220126a6a56dbe69af78d156626fc9cf41d6aac0c07b8b5f0f8491f68db5e89cb5002207ee6ed6f2f41da256f3c1e79679a3de6cf34cc08b940b82be14aefe7da031a6b012102801bc7170efb82c490e243204d86970f15966aa3bce6a06bef5c09a83a5bfffe024730440220363204a1586d7f13c148295122cbf9ec7939685e3cadab81d6d9e921436d21b7022044626b8c2bd4aa7c167d74bc4e9eb9d0744e29ce0ad906d78e10d6d854f23d170121037fb9c51716739bb4c146857fab5a783372f72a65987d61f3b58c74360f4328dd0247304402207925a4c2a3a6b76e10558717ee28fcb8c6fde161b9dc6382239af9f372ace99902204a58e31ce0b4a4804a42d2224331289311ded2748062c92c8aca769e81417a4c012102e18a8c235b48e41ef98265a8e07fa005d2602b96d585a61ad67168d74e7391cb02483045022100bbfe060479174a8d846b5a897526003eb2220ba307a5fee6e1e8de3e4e8b38fd02206723857301d447f67ac98a5a5c2b80ef6820e98fae213db1720f93d91161803b01210386728e2ac3ecee15f58d0505ee26f86a68f08c702941ffaf2fb7213e5026aea10247304402203a2613ae68f697eb02b5b7d18e3c4236966dac2b3a760e3021197d76e9ad4239022046f9067d3df650fcabbdfd250308c64f90757dec86f0b08813c979a42d06a6ec012102a1d7ee1cb4dc502f899aaafae0a2eb6cbf80d9a1073ae60ddcaabc3b1d1f15df02483045022100ab1bea2cc5388428fd126c7801550208701e21564bd4bd00cfd4407cfafc1acd0220508ee587f080f3c80a5c0b2175b58edd84b755e659e2135b3152044d75ebc4b501210236dd1b7f27a296447d0eb3750e1bdb2d53af50b31a72a45511dc1ec3fe7a684a19391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('98574bc5f6e75769eb0c93d41453cc1dfbd15c14e63cc3c42f37cdbd08858762', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325341
+
+ self.assertFalse(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual('ee76c0c6da87f0eb5ab4d1ae05d3942512dcd3c4c42518f9d3619e74400cfc1f', tx_copy.txid())
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('ee76c0c6da87f0eb5ab4d1ae05d3942512dcd3c4c42518f9d3619e74400cfc1f', tx.txid())
+ self.assertEqual('729c2e40a2fccd6b731407c01ed304119c1ac329bdf9baae5b642d916c5f3272', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_wif_online_addr_p2pkh(self, mock_write): # compressed pubkey
+ wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
+ wallet_offline.import_private_key('p2pkh:cQDxbmQfwRV3vP1mdnVHq37nJekHLsuD3wdSQseBRA2ct4MFk5Pq', pw=None)
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('mg2jk6S5WGDhUPA8mLSxDLWpUoQnX1zzoG')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
+ self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
+ wallet_offline.import_private_key('p2wpkh-p2sh:cU9hVzhpvfn91u2zTVn8uqF2ymS7ucYH8V5TmsTDmuyMHgRk9WsJ', pw=None)
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('2NA2JbUVK7HGWUCK5RXSVNHrkgUYF8d9zV8')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
+ self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_wif_online_addr_p2wpkh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True)
+ wallet_offline.import_private_key('p2wpkh:cPuQzcNEgbeYZ5at9VdGkCwkPA9r34gvEVJjuoz384rTfYpahfe7', pw=None)
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('tb1qm2eh4787lwanrzr6pf0ekf5c7jnmghm2y9k529')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
+ self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_addr_p2pkh(self, mock_write): # compressed pubkey
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/44'/1'/0'
+ keystore.from_xprv('tprv8gfKwjuAaqtHgqxMh1tosAQ28XvBMkcY5NeFRA3pZMpz6MR4H4YZ3MJM4fvNPnRKeXR1Td2vQGgjorNXfo94WvT5CYDsPAqjHxSn436G1Eu'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('mg2jk6S5WGDhUPA8mLSxDLWpUoQnX1zzoG')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertFalse(tx.is_segwit())
+ self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
+ self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/49'/1'/0'
+ keystore.from_xprv('uprv8zHHrMQMQ26utWwNJ5MK2SXpB9hbmy7pbPaneii69xT8cZTyFpxQFxkknGWKP8dxBTZhzy7yP6cCnLrRCQjzJDk3G61SjZpxhFQuB2NR8a5'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('2NA2JbUVK7HGWUCK5RXSVNHrkgUYF8d9zV8')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
+ self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_write):
+ wallet_offline = WalletIntegrityHelper.create_standard_wallet(
+ # bip39: "qwe", der: m/84'/1'/0'
+ keystore.from_xprv('vprv9K9hbuA23Bidgj1KRSHUZMa59jJLeZBpXPVn4RP7sBLArNhZxJjw4AX7aQmVTErDt4YFC11ptMLjbwxgrsH8GLQ1cx77KggWeVPeDBjr9xM'),
+ gap_limit=4
+ )
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('tb1qm2eh4787lwanrzr6pf0ekf5c7jnmghm2y9k529')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('01000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('0a08ea26a49e2b80f253796d605b69e2d0403fac64bdf6f7db82ada4b7bb6b62', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325340
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx
+ tx = wallet_offline.sign_transaction(tx_copy, password=None)
+ self.assertTrue(tx.is_complete())
+ self.assertTrue(tx.is_segwit())
+ self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
+ self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_hd_multisig_online_addr_p2sh(self, mock_write):
+ # 2-of-3 legacy p2sh multisig
+ wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure', '', True),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4XJzYkhsCbDCcZRmDAKSD7bXi9mdCni7acVt45fxbTVZyU6jRGh29ULKTjoapkfFsSJvQHitcVKbQgzgkkYsAmaovcro7Mhf')
+ ],
+ '2of3', gap_limit=2
+ )
+ wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song', '', True),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
+ keystore.from_xpub('tpubD6NzVbkrYhZ4YARFMEZPckrqJkw59GZD1PXtQnw14ukvWDofR7Z1HMeSCxfYEZVvg4VdZ8zGok5VxHwdrLqew5cMdQntWc5mT7mh1CSgrnX')
+ ],
+ '2of3', gap_limit=2
+ )
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('2N4z38eTKcWTZnfugCCfRyXtXWMLnn8HDfw')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('010000000001016207d958dc46508d706e4cd7d3bc46c5c2b02160e2578e5fad2efafc3927050301000000171600147a4fc8cdc1c2cf7abbcd88ef6d880e59269797acfdffffff02809698000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e48870d0916020000000017a914703f83ef20f3a52d908475dcad00c5144164d5a2870247304402203b1a5cb48cadeee14fa6c7bbf2bc581ca63104762ec5c37c703df778884cc5b702203233fa53a2a0bfbd85617c636e415da72214e359282cce409019319d031766c50121021112c01a48cc7ea13cba70493c6bffebb3e805df10ff4611d2bf559d26e25c04bf391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('c59913a1fa9b1ef1f6928f0db490be67eeb9d7cb05aa565ee647e859642f3532', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325503
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx - first
+ tx = wallet_offline1.sign_transaction(tx_copy, password=None)
+ self.assertFalse(tx.is_complete())
+ tx = Transaction(tx.serialize())
+
+ # sign tx - second
+ tx = wallet_offline2.sign_transaction(tx, password=None)
+ self.assertTrue(tx.is_complete())
+ tx = Transaction(tx.serialize())
+
+ self.assertEqual('010000000132352f6459e847e65e56aa05cbd7b9ee67be90b40d8f92f6f11e9bfaa11399c500000000fdfe0000483045022100cfe41e783629a2ad0b1f17cd2dbd69db05763fa7a22691131fa321ba3140d7cb02203fbda2ccc6212315464cd814d4e909b4f80a2361e3af0f9deda06478f91a0f3901483045022100b84fd63e957f2409558f63962fc91ba58334efde8b88ff53ca71da3d0fe7219702206001c6caeb30e18a7525fc72de0003e12646bf815b12fb132c1aadd6ffa1989c014c69522102afb4af9a91264e1c6dce3ebe5312801723270ac0ba8134b7b49129328fcb0f2821030b482838721a38d94847699fed8818b5c5f56500ef72f13489e365b65e5749cf2103e5db7969ae2f2576e6a061bf3bb2db16571e77ffb41e0b27170734359235cbce53aefdffffff02a02526000000000017a9141567b2578f300fa618ef0033611fd67087aff6d187585d72000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887bf391400',
+ str(tx))
+ self.assertEqual('bb4c28af28b970522c56ff0482cd98c2b78a90bec578bcede8a9e5cbec6ef5e7', tx.txid())
+ self.assertEqual('bb4c28af28b970522c56ff0482cd98c2b78a90bec578bcede8a9e5cbec6ef5e7', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_hd_multisig_online_addr_p2wsh_p2sh(self, mock_write):
+ # 2-of-2 p2sh-embedded segwit multisig
+ wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ # bip39: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose, der: m/1234'/1'/0', p2wsh-p2sh multisig
+ keystore.from_xprv('Uprv9CvELvByqm8k2dpecJVjgLMX1z5DufEjY4fBC5YvdGF5WjGCa7GVJJ2fYni1tyuF7Hw83E6W2ZBjAhaFLZv2ri3rEsubkCd5avg4EHKoDBN'),
+ keystore.from_xpub('Upub5Qb8ik4Cnu8g97KLXKgVXHqY6tH8emQvqtBncjSKsyfTZuorPtTZgX7ovKKZHuuVGBVd1MTTBkWez1XXt2weN1sWBz6SfgRPQYEkNgz81QF')
+ ],
+ '2of2', gap_limit=2
+ )
+ wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ # bip39: square page wood spy oil story rebel give milk screen slide shuffle, der: m/1234'/1'/0', p2wsh-p2sh multisig
+ keystore.from_xprv('Uprv9BbnKEXJxXaNvdEsRJ9VA9toYrSeFJh5UfGBpM2iKe8Uh7UhrM9K8ioL53s8gvCoGfirHHaqpABDAE7VUNw8LNU1DMJKVoWyeNKu9XcDC19'),
+ keystore.from_xpub('Upub5RuakRisg8h3F7u7iL2k3UJFa1uiK7xauHamzTxYBbn4PXbM7eajr6M9Q2VCr6cVGhfhqWQqxnABvtSATuVM1xzxk4nA189jJwzaMn1QX7V')
+ ],
+ '2of2', gap_limit=2
+ )
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('2MsHQRm1pNi6VsmXYRxYMcCTdPu7Xa1RyFe')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('0100000000010118d494d28e5c3bf61566ca0313e22c3b561b888a317d689cc8b47b947adebd440000000017160014aec84704ea8508ddb94a3c6e53f0992d33a2a529fdffffff020f0925000000000017a91409f7aae0265787a02de22839d41e9c927768230287809698000000000017a91400698bd11c38f887f17c99846d9be96321fbf989870247304402206b906369f4075ebcfc149f7429dcfc34e11e1b7bbfc85d1185d5e9c324be0d3702203ce7fc12fd3131920fbcbb733250f05dbf7d03e18a4656232ee69d5c54dd46bd0121028a4b697a37f3f57f6e53f90db077fa9696095b277454fda839c211d640d48649c0391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('54356de9e156b85c8516fd4d51bdb68b5513f58b4a6147483978ae254627ee3e', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325504
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx - first
+ tx = wallet_offline1.sign_transaction(tx_copy, password=None)
+ self.assertFalse(tx.is_complete())
+ self.assertEqual('6a58a51591142429203b62b6ddf6b799a6926882efac229998c51bee6c3573eb', tx.txid())
+ tx = Transaction(tx.serialize())
+
+ # sign tx - second
+ tx = wallet_offline2.sign_transaction(tx, password=None)
+ self.assertTrue(tx.is_complete())
+ tx = Transaction(tx.serialize())
+
+ self.assertEqual('010000000001013eee274625ae78394847614a8bf513558bb6bd514dfd16855cb856e1e96d355401000000232200206ee8d4bb1277b7dbe1d4e49b880993aa993f417a9101cb23865c7c7258732704fdffffff02a02526000000000017a914a4189ef02c95cfe36f8e880c6cb54dff0837b22687585d72000000000017a91400698bd11c38f887f17c99846d9be96321fbf98987040047304402205a9dd9eb5676196893fb08f60079a2e9f567ee39614075d8c5d9fab0f11cbbc7022039640855188ebb7bccd9e3f00b397a888766d42d00d006f1ca7457c15449285f014730440220234f6648c5741eb195f0f4cd645298a10ce02f6ef557d05df93331e21c4f58cb022058ce2af0de1c238c4a8dd3b3c7a9a0da6e381ddad7593cddfc0480f9fe5baadf0147522102975c00f6af579f9a1d283f1e5a43032deadbab2308aef30fb307c0cfe54777462102d3f47041b424a84898e315cc8ef58190f6aec79c178c12de0790890ba7166e9c52aec0391400',
+ str(tx))
+ self.assertEqual('6a58a51591142429203b62b6ddf6b799a6926882efac229998c51bee6c3573eb', tx.txid())
+ self.assertEqual('96d0bca1001778c54e4c3a07929fab5562c5b5a23fd1ca3aa3870cc5df2bf97d', tx.wtxid())
+
+ @needs_test_with_all_ecc_implementations
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_sending_offline_hd_multisig_online_addr_p2wsh(self, mock_write):
+ # 2-of-3 p2wsh multisig
+ wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', '', True),
+ keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
+ keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra')
+ ],
+ '2of3', gap_limit=2
+ )
+ wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
+ [
+ keystore.from_seed('snow nest raise royal more walk demise rotate smooth spirit canyon gun', '', True),
+ keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra'),
+ keystore.from_xpub('Vpub5gSKXzxK7FeKQedu2q1z9oJWxqvX72AArW3HSWpEhc8othDH8xMDu28gr7gf17sp492BuJod8Tn7anjvJrKpETwqnQqX7CS8fcYyUtedEMk')
+ ],
+ '2of3', gap_limit=2
+ )
+ # ^ third seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
+ wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
+ wallet_online.import_address('tb1q83p6eqxkuvq4eumcha46crpzg4nj84s9p0hnynkxg8nhvfzqcc7q4erju6')
+
+ # bootstrap wallet_online
+ funding_tx = Transaction('0100000000010132352f6459e847e65e56aa05cbd7b9ee67be90b40d8f92f6f11e9bfaa11399c501000000171600142e5d579693b2a7679622935df94d9f3c84909b24fdffffff0280969800000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c83717d010000000017a91441b772909ad301b41b76f4a3c5058888a7fe6f9a8702483045022100de54689f74b8efcce7fdc91e40761084686003bcd56c886ee97e75a7e803526102204dea51ae5e7d01bd56a8c336c64841f7fe02a8b101fa892e13f2d079bb14e6bf012102024e2f73d632c49f4b821ccd3b6da66b155427b1e5b1c4688cefd5a4b4bfa404c1391400')
+ funding_txid = funding_tx.txid()
+ self.assertEqual('643a7ab9083d0227dd9df314ce56b18d279e6018ff975079dfaab82cd7a66fa3', funding_txid)
+ wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
+
+ # create unsigned tx
+ outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)]
+ tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
+ tx.set_rbf(True)
+ tx.locktime = 1325505
+
+ self.assertFalse(tx.is_complete())
+ self.assertEqual(1, len(tx.inputs()))
+ tx_copy = Transaction(tx.serialize())
+ self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
+
+ self.assertEqual(tx.txid(), tx_copy.txid())
+
+ # sign tx - first
+ tx = wallet_offline1.sign_transaction(tx_copy, password=None)
+ self.assertFalse(tx.is_complete())
+ self.assertEqual('32e946761b4e718c1fa8d044db9e72d5831f6395eb284faf2fb5c4af0743e501', tx.txid())
+ tx = Transaction(tx.serialize())
+
+ # sign tx - second
+ tx = wallet_offline2.sign_transaction(tx, password=None)
+ self.assertTrue(tx.is_complete())
+ tx = Transaction(tx.serialize())
+
+ self.assertEqual('01000000000101a36fa6d72cb8aadf795097ff18609e278db156ce14f39ddd27023d08b97a3a640000000000fdffffff02a02526000000000017a91447ee5a659f6ffb53f7e3afc1681b6415f3c00fa187585d7200000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c04004730440220629d89626585f563202e6b38ceddc26ccd00737e0b7ee4239b9266ef9174ea2f02200b74828399a2e35ed46c9b484af4817438d5fea890606ebb201b821944db1fdc0147304402205d1a59c84c419992069e9764a7992abca6a812cc5dfd4f0d6515d4283e660ce802202597a38899f31545aaf305629bd488f36bf54e4a05fe983932cafbb3906efb8f016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153aec1391400',
+ str(tx))
+ self.assertEqual('32e946761b4e718c1fa8d044db9e72d5831f6395eb284faf2fb5c4af0743e501', tx.txid())
+ self.assertEqual('4376fa5f1f6cb37b1f3956175d3bd4ef6882169294802b250a3c672f3ff431c1', tx.wtxid())
+
+
+class TestWalletHistory_SimpleRandomOrder(TestCaseForTestnet):
+ transactions = {
+ "0f4972c84974b908a58dda2614b68cf037e6c03e8291898c719766f213217b67": "01000000029d1bdbe67f0bd0d7bd700463f5c29302057c7b52d47de9e2ca5069761e139da2000000008b483045022100a146a2078a318c1266e42265a369a8eef8993750cb3faa8dd80754d8d541d5d202207a6ab8864986919fd1a7fd5854f1e18a8a0431df924d7a878ec3dc283e3d75340141045f7ba332df2a7b4f5d13f246e307c9174cfa9b8b05f3b83410a3c23ef8958d610be285963d67c7bc1feb082f168fa9877c25999963ff8b56b242a852b23e25edfeffffff9d1bdbe67f0bd0d7bd700463f5c29302057c7b52d47de9e2ca5069761e139da2010000008a47304402201c7fa37b74a915668b0244c01f14a9756bbbec1031fb69390bcba236148ab37e02206151581f9aa0e6758b503064c1e661a726d75c6be3364a5a121a8c12cf618f64014104dc28da82e141416aaf771eb78128d00a55fdcbd13622afcbb7a3b911e58baa6a99841bfb7b99bcb7e1d47904fda5d13fdf9675cdbbe73e44efcc08165f49bac6feffffff02b0183101000000001976a914ca14915184a2662b5d1505ce7142c8ca066c70e288ac005a6202000000001976a9145eb4eeaefcf9a709f8671444933243fbd05366a388ac54c51200",
+ "2791cdc98570cc2b6d9d5b197dc2d002221b074101e3becb19fab4b79150446d": "010000000132201ff125888a326635a2fc6e971cd774c4d0c1a757d742d0f6b5b020f7203a050000006a47304402201d20bb5629a35b84ff9dd54788b98e265623022894f12152ac0e6158042550fe02204e98969e1f7043261912dd0660d3da64e15acf5435577fc02a00eccfe76b323f012103a336ad86546ab66b6184238fe63bb2955314be118b32fa45dd6bd9c4c5875167fdffffff0254959800000000001976a9148d2db0eb25b691829a47503006370070bc67400588ac80969800000000001976a914f96669095e6df76cfdf5c7e49a1909f002e123d088ace8ca1200",
+ "2d216451b20b6501e927d85244bcc1c7c70598332717df91bb571359c358affd": "010000000001036cdf8d2226c57d7cc8485636d8e823c14790d5f24e6cf38ba9323babc7f6db2901000000171600143fc0dbdc2f939c322aed5a9c3544468ec17f5c3efdffffff507dce91b2a8731636e058ccf252f02b5599489b624e003435a29b9862ccc38c0200000017160014c50ff91aa2a790b99aa98af039ae1b156e053375fdffffff6254162cf8ace3ddfb3ec242b8eade155fa91412c5bde7f55decfac5793743c1010000008b483045022100de9599dcd7764ca8d4fcbe39230602e130db296c310d4abb7f7ae4d139c4d46402200fbfd8e6dc94d90afa05b0c0eab3b84feb465754db3f984fbf059447282771c30141045eecefd39fabba7b0098c3d9e85794e652bdbf094f3f85a3de97a249b98b9948857ea1e8209ee4f196a6bbcfbad103a38698ee58766321ba1cdee0cbfb60e7b2fdffffff01e85af70100000000160014e8d29f07cd5f813317bec4defbef337942d85d74024730440220218049aee7bbd34a7fa17f972a8d24a0469b0131d943ef3e30860401eaa2247402203495973f006e6ee6ae74a83228623029f238f37390ee4b587d95cdb1d1aaee9901210392ba263f3a2b260826943ff0df25e9ca4ef603b98b0a916242c947ae0626575f02473044022002603e5ceabb4406d11aedc0cccbf654dd391ce68b6b2228a40e51cf8129310d0220533743120d93be8b6c1453973935b911b0a2322e74708d23e8b5f90e74b0f192012103221b4ee0f508ba595fc1b9c2252ed9d03e99c73b97344dae93263c68834f034800ed161300",
+ "31494e7e9f42f4bd736769b07cc602e2a1019617b2c72a03ec945b667aada78f": "0100000000010454022b1b4d3b45e7fcac468de2d6df890a9f41050c05d80e68d4b083f728e76a000000008b483045022100ea8fe74db2aba23ad36ac66aaa481bad2b4d1b3c331869c1d60a28ce8cfad43c02206fa817281b33fbf74a6dd7352bdc5aa1d6d7966118a4ad5b7e153f37205f1ae80141045f7ba332df2a7b4f5d13f246e307c9174cfa9b8b05f3b83410a3c23ef8958d610be285963d67c7bc1feb082f168fa9877c25999963ff8b56b242a852b23e25edfdffffff54022b1b4d3b45e7fcac468de2d6df890a9f41050c05d80e68d4b083f728e76a01000000171600146dfe07e12af3db7c715bf1c455f8517e19c361e7fdffffff54022b1b4d3b45e7fcac468de2d6df890a9f41050c05d80e68d4b083f728e76a020000006a47304402200b1fb89e9a772a8519294acd61a53a29473ce76077165447f49a686f1718db5902207466e2e8290f84114dc9d6c56419cb79a138f03d7af8756de02c810f19e4e03301210222bfebe09c2638cfa5aa8223fb422fe636ba9675c5e2f53c27a5d10514f49051fdffffff54022b1b4d3b45e7fcac468de2d6df890a9f41050c05d80e68d4b083f728e76a0300000000fdffffff018793140d000000001600144b3e27ddf4fc5f367421ee193da5332ef351b700000247304402207ba52959938a3853bcfd942d8a7e6a181349069cde3ea73dbde43fa9669b8d5302207a686b92073863203305cb5d5550d88bdab0d21b9e9761ba4a106ea3970e08d901210265c1e014112ed19c9f754143fb6a2ff89f8630d62b33eb5ae708c9ea576e61b50002473044022029e868a905aa3ecae6eafcbd5959aefff0e5f39c1fc7a131a174828806e74e5202202f0aaa7c3cb3d9a9d526e5428ce37c0f0af0d774aa30b09ded8bc2230e7ffaf2012102fe0104455dc52b1689bba130664e452642180eb865217acfc6997260b7d946ae22c71200",
+ "336eee749da7d1c537fd5679157fae63005bfd4bb8cf47ae73600999cbc9beaa": "0100000000010232201ff125888a326635a2fc6e971cd774c4d0c1a757d742d0f6b5b020f7203a020000006a4730440220198c0ba2b2aefa78d8cca01401d408ecdebea5ac05affce36f079f6e5c8405ca02200eabb1b9a01ff62180cf061dfacedba6b2e07355841b9308de2d37d83489c7b80121031c663e5534fe2a6de816aded6bb9afca09b9e540695c23301f772acb29c64a05fdfffffffb28ff16811d3027a2405be68154be8fdaff77284dbce7a2314c4107c2c941600000000000fdffffff015e104f01000000001976a9146dfd56a0b5d0c9450d590ad21598ecfeaa438bd788ac000247304402207d6dc521e3a4577685535f098e5bac4601aa03658b924f30bf7afef1850e437e022045b76771d8b6ca1939352d6b759fca31029e5b2edffa44dc747fe49770e746cd012102c7f36d4ceed353b90594ebaf3907972b6d73289bdf4707e120de31ec4e1eb11679f31200",
+ "3a6ed17d34c49dfdf413398e113cf5f71710d59e9f4050bbc601d513a77eb308": "010000000168091e76227e99b098ef8d6d5f7c1bb2a154dd49103b93d7b8d7408d49f07be0000000008a47304402202f683a63af571f405825066bd971945a35e7142a75c9a5255d364b25b7115d5602206c59a7214ae729a519757e45fdc87061d357813217848cf94df74125221267ac014104aecb9d427e10f0c370c32210fe75b6e72ccc4f415076cf1a6318fbed5537388862c914b29269751ab3a04962df06d96f5f4f54e393a0afcbfa44b590385ae61afdffffff0240420f00000000001976a9145f917fd451ca6448978ebb2734d2798274daf00b88aca8063d00000000001976a914e1232622a96a04f5e5a24ca0792bb9c28b089d6e88ace9ca1200",
+ "475c149be20c8a73596fad6cb8861a5af46d4fcf8e26a9dbf6cedff7ff80b70d": "01000000013a7e6f19a963adc7437d2f3eb0936f1fc9ef4ba7e083e19802eb1111525a59c2000000008b483045022100958d3931051306489d48fe69b32561e0a16e82a2447c07be9d1069317084b5e502202f70c2d9be8248276d334d07f08f934ffeea83977ad241f9c2de954a2d577f94014104d950039cec15ad10ad4fb658873bc746148bc861323959e0c84bf10f8633104aa90b64ce9f80916ab0a4238e025dcddf885b9a2dd6e901fe043a433731db8ab4fdffffff02a086010000000000160014bbfab2cc3267cea2df1b68c392cb3f0294978ca922940d00000000001976a914760f657c67273a06cad5b1d757a95f4ed79f5a4b88ac4c8d1300",
+ "56a65810186f82132cea35357819499468e4e376fca685c023700c75dc3bd216": "01000000000101614b142aeeb827d35d2b77a5b11f16655b6776110ddd9f34424ff49d85706cf90200000000fdffffff02784a4c00000000001600148464f47f35cbcda2e4e5968c5a3a862c43df65a1404b4c00000000001976a914c9efecf0ecba8b42dce0ae2b28e3ea0573d351c988ac0247304402207d8e559ed1f56cb2d02c4cb6c95b95c470f4b3cb3ce97696c3a58e39e55cd9b2022005c9c6f66a7154032a0bb2edc1af1f6c8f488bec52b6581a3a780312fb55681b0121024f83b87ac3440e9b30cec707b7e1461ecc411c2f45520b45a644655528b0a68ae9ca1200",
+ "6ae728f783b0d4680ed8050c05419f0a89dfd6e28d46acfce7453b4d1b2b0254": "0100000000010496941b9f18710b39bacde890e39a7fa401e6bf49985857cb7adfb8a45147ef1e000000001716001441aec99157d762708339d7faf7a63a8c479ed84cfdffffff96941b9f18710b39bacde890e39a7fa401e6bf49985857cb7adfb8a45147ef1e0100000000fdffffff1a5d1e4ca513983635b0df49fd4f515c66dd26d7bff045cfbd4773aa5d93197f000000006a4730440220652145460092ef42452437b942cb3f563bf15ad90d572d0b31d9f28449b7a8dd022052aae24f58b8f76bd2c9cf165cc98623f22870ccdbef1661b6dbe01c0ef9010f01210375b63dd8e93634bbf162d88b25d6110b5f5a9638f6fe080c85f8b21c2199a1fdfdffffff1a5d1e4ca513983635b0df49fd4f515c66dd26d7bff045cfbd4773aa5d93197f010000008a47304402207517c52b241e6638a84b05385e0b3df806478c2e444f671ca34921f6232ee2e70220624af63d357b83e3abe7cdf03d680705df0049ec02f02918ee371170e3b4a73d014104de408e142c00615294813233cdfe9e7774615ae25d18ba4a1e3b70420bb6666d711464518457f8b947034076038c6f0cfc8940d85d3de0386e0ad88614885c7cfdffffff0480969800000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac809698000000000017a914f2a76207d7b54bd34282281205923841341d9e1f87002d3101000000001976a914b8d4651937cd7db5bcf5fc98e6d2d8cfa131e85088ac743db20a00000000160014c7d0df09e03173170aed0247243874c6872748ed02483045022100b932cda0aeb029922e126568a48c05d79317747dcd77e61dce44e190e140822002202d13f84338bb272c531c4086277ac11e166c59612f4aefa6e20f78455bdc09970121028e6808a8ac1e9ede621aaabfcad6f86662dbe0ace0236f078eb23c24bc88bd5e02483045022100d74a253262e3898626c12361ba9bb5866f9303b42eec0a55ced0578829e2e61e022059c08e61d90cd63c84de61c796c9d1bc1e2f8217892a7c07b383af357ddd7a730121028641e89822127336fc12ff99b1089eb1a124847639a0e98d17ff03a135ad578b000020c71200",
+ "72419d187c61cfc67a011095566b374dc2c01f5397e36eafe68e40fc44474112": "0100000002677b2113f26697718c8991823ec0e637f08cb61426da8da508b97449c872490f000000008b4830450221009c50c0f56f34781dfa7b3d540ac724436c67ffdc2e5b2d5a395c9ebf72116ef802205a94a490ea14e4824f36f1658a384aeaecadd54839600141eb20375a49d476d1014104c291245c2ee3babb2a35c39389df56540867f93794215f743b9aa97f5ba114c4cdee8d49d877966728b76bc649bb349efd73adef1d77452a9aac26f8c51ae1ddfdffffff677b2113f26697718c8991823ec0e637f08cb61426da8da508b97449c872490f010000008b483045022100ae0b286493491732e7d3f91ab4ac4cebf8fe8a3397e979cb689e62d350fdcf2802206cf7adf8b29159dd797905351da23a5f6dab9b9dbf5028611e86ccef9ff9012e014104c62c4c4201d5c6597e5999f297427139003fdb82e97c2112e84452d1cfdef31f92dd95e00e4d31a6f5f9af0dadede7f6f4284b84144e912ff15531f36358bda7fdffffff019f7093030000000022002027ce908c4ee5f5b76b4722775f23e20c5474f459619b94040258290395b88afb6ec51200",
+ "76bcf540b27e75488d95913d0950624511900ae291a37247c22d996bb7cde0b4": "0100000001f4ba9948cdc4face8315c7f0819c76643e813093ffe9fbcf83d798523c7965db000000006a473044022061df431a168483d144d4cffe1c5e860c0a431c19fc56f313a899feb5296a677c02200208474cc1d11ad89b9bebec5ec00b1e0af0adaba0e8b7f28eed4aaf8d409afb0121039742bf6ab70f12f6353e9455da6ed88f028257950450139209b6030e89927997fdffffff01d4f84b00000000001976a9140b93db89b6bf67b5c2db3370b73d806f458b3d0488ac0a171300",
+ "7f19935daa7347bdcf45f0bfd726dd665c514ffd49dfb035369813a54c1e5d1a": "01000000000102681b6a8dd3a406ee10e4e4aece3c2e69f6680c02f53157be6374c5c98322823a00000000232200209adfa712053a06cc944237148bcefbc48b16eb1dbdc43d1377809bcef1bea9affdffffff681b6a8dd3a406ee10e4e4aece3c2e69f6680c02f53157be6374c5c98322823a0100000023220020f40ed2e3fbffd150e5b74f162c3ce5dae0dfeba008a7f0f8271cf1cf58bfb442fdffffff02801d2c04000000001976a9140cc01e19090785d629cdcc98316f328df554de4f88ac6d455d05000000001976a914b9e828990a8731af4527bcb6d0cddf8d5ffe90ce88ac040047304402206eb65bd302eefae24eea05781e8317503e68584067d35af028a377f0751bb55b0220226453d00db341a4373f1bcac2391f886d3a6e4c30dd15133d1438018d2aad24014730440220343e578591fab0236d28fb361582002180d82cb1ba79eec9139a7a9519fca4260220723784bd708b4a8ed17bb4b83a5fd2e667895078e80eec55119015beb3592fd2016952210222eca5665ed166d090a5241d9a1eb27a92f85f125aaf8df510b2b5f701f3f534210227bca514c22353a7ae15c61506522872afecf10df75e599aabe4d562d0834fce2103601d7d49bada5a57a4832eafe4d1f1096d7b0b051de4a29cd5fc8ad62865e0a553ae0400483045022100b15ea9daacd809eb4d783a1449b7eb33e2965d4229e1a698db10869299dddc670220128871ffd27037a3e9dac6748ce30c14b145dd7f9d56cc9dcde482461fb6882601483045022100cb659e1de65f8b87f64d1b9e62929a5d565bbd13f73a1e6e9dd5f4efa024b6560220667b13ce2e1a3af2afdcedbe83e2120a6e8341198a79efb855b8bc5f93b4729f0169522102d038600af253cf5019f9d5637ca86763eca6827ed7b2b7f8cc6326dffab5eb68210315cdb32b7267e9b366fb93efe29d29705da3db966e8c8feae0c8eb51a7cf48e82103f0335f730b9414acddad5b3ee405da53961796efd8c003e76e5cd306fcc8600c53ae1fc71200",
+ "9de08bcafc602a3d2270c46cbad1be0ef2e96930bec3944739089f960652e7cb": "010000000001013409c10fd732d9e4b3a9a1c4beb511fa5eb32bc51fd169102a21aa8519618f800000000000fdffffff0640420f00000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac40420f00000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac40420f00000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac80841e00000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac64064a000000000016001469825d422ca80f2a5438add92d741c7df45211f280969800000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac02483045022100b4369b18bccb74d72b6a38bd6db59122a9e8af3356890a5ecd84bdb8c7ffe317022076a5aa2b817be7b3637d179106fccebb91acbc34011343c8e8177acc2da4882e0121033c8112bbf60855f4c3ae489954500c4b8f3408665d8e1f63cf3216a76125c69865281300",
+ "a29d131e766950cae2e97dd4527b7c050293c2f5630470bdd7d00b7fe6db1b9d": "010000000400899af3606e93106a5d0f470e4e2e480dfc2fd56a7257a1f0f4d16fd5961a0f000000006a47304402205b32a834956da303f6d124e1626c7c48a30b8624e33f87a2ae04503c87946691022068aa7f936591fb4b3272046634cf526e4f8a018771c38aff2432a021eea243b70121034bb61618c932b948b9593d1b506092286d9eb70ea7814becef06c3dfcc277d67fdffffff4bc2dcc375abfc7f97d8e8c482f4c7b8bc275384f5271678a32c35d955170753000000006b483045022100de775a580c6cb47061d5a00c6739033f468420c5719f9851f32c6992610abd3902204e6b296e812bb84a60c18c966f6166718922780e6344f243917d7840398eb3db0121025d7317c6910ad2ad3d29a748c7796ddf01e4a8bc5e3bf2a98032f0a20223e4aafdffffff4bc2dcc375abfc7f97d8e8c482f4c7b8bc275384f5271678a32c35d955170753010000006a4730440220615a26f38bf6eb7043794c08fb81f273896b25783346332bec4de8dfaf7ed4d202201c2bc4515fc9b07ded5479d5be452c61ce785099f5e33715e9abd4dbec410e11012103caa46fcb1a6f2505bf66c17901320cc2378057c99e35f0630c41693e97ebb7cffdffffff4bc2dcc375abfc7f97d8e8c482f4c7b8bc275384f5271678a32c35d955170753030000006b483045022100c8fba762dc50041ee3d5c7259c01763ed913063019eefec66678fb8603624faa02200727783ccbdbda8537a6201c63e30c0b2eb9afd0e26cb568d885e6151ef2a8540121027254a862a288cfd98853161f575c49ec0b38f79c3ef0bf1fb89986a3c36a8906fdffffff0240787d01000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac3bfc1502000000001976a914c30f2af6a79296b6531bf34dba14c8419be8fb7d88ac52c51200",
+ "c1433779c5faec5df5e7bdc51214a95f15deeab842c23efbdde3acf82c165462": "0100000003aabec9cb99096073ae47cfb84bfd5b0063ae7f157956fd37c5d1a79d74ee6e33000000008b4830450221008136fc880d5e24fdd9d2a43f5085f374fef013b814f625d44a8075104981d92a0220744526ec8fc7887c586968f22403f0180d54c9b7ff8db9b553a3c4497982e8250141047b8b4c91c5a93a1f2f171c619ca41770427aa07d6de5130c3ba23204b05510b3bd58b7a1b35b9c4409104cfe05e1677fc8b51c03eac98b206e5d6851b31d2368fdffffff16d23bdc750c7023c085a6fc76e3e468944919783535ea2c13826f181058a656010000008a47304402204148410f2d796b1bb976b83904167d28b65dcd7c21b3876022b4fa70abc86280022039ea474245c3dc8cd7e5a572a155df7a6a54496e50c73d9fed28e76a1cf998c00141044702781daed201e35aa07e74d7bda7069e487757a71e3334dc238144ad78819de4120d262e8488068e16c13eea6092e3ab2f729c13ef9a8c42136d6365820f7dfdffffff68091e76227e99b098ef8d6d5f7c1bb2a154dd49103b93d7b8d7408d49f07be0010000008b4830450221008228af51b61a4ee09f58b4a97f204a639c9c9d9787f79b2fc64ea54402c8547902201ed81fca828391d83df5fbd01a3fa5dd87168c455ed7451ba8ccb5bf06942c3b0141046fcdfab26ac08c827e68328dbbf417bbe7577a2baaa5acc29d3e33b3cc0c6366df34455a9f1754cb0952c48461f71ca296b379a574e33bcdbb5ed26bad31220bfdffffff0210791c00000000001976a914a4b991e7c72996c424fe0215f70be6aa7fcae22c88ac80c3c901000000001976a914b0f6e64ea993466f84050becc101062bb502b4e488ac7af31200",
+ "c2595a521111eb0298e183e0a74befc91f6f93b03e2f7d43c7ad63a9196f7e3a": "01000000018557003cb450f53922f63740f0f77db892ef27e15b2614b56309bfcee96a0ad3010000006a473044022041923c905ae4b5ed9a21aa94c60b7dbcb8176d58d1eb1506d9fb1e293b65ce01022015d6e9d2e696925c6ad46ce97cc23dec455defa6309b839abf979effc83b8b160121029332bf6bed07dcca4be8a5a9d60648526e205d60c75a21291bffcdefccafdac3fdffffff01c01c0f00000000001976a914a2185918aa1006f96ed47897b8fb620f28a1b09988ac01171300",
+ "e07bf0498d40d7b8d7933b1049dd54a1b21b7c5f6d8def98b0997e22761e0968": "01000000016d445091b7b4fa19cbbee30141071b2202d0c27d195b9d6d2bcc7085c9cd9127010000008b483045022100daf671b52393af79487667eddc92ebcc657e8ae743c387b25d1c1a2e19c7a4e7022015ef2a52ea7e94695de8898821f9da539815775516f18329896e5fc52a3563b30141041704a3daafaace77c8e6e54cf35ed27d0bf9bb8bcd54d1b955735ff63ec54fe82a80862d455c12e739108b345d585014bf6aa0cbd403817c89efa18b3c06d6b5fdffffff02144a4c00000000001976a9148942ac692ace81019176c4fb0ac408b18b49237f88ac404b4c00000000001976a914dd36d773acb68ac1041bc31b8a40ee504b164b2e88ace9ca1200",
+ "e453e7346693b507561691b5ea73f8eba60bfc8998056226df55b2fac88ba306": "010000000125af87b0c2ebb9539d644e97e6159ccb8e1aa80fe986d01f60d2f3f37f207ae8010000008b483045022100baed0747099f7b28a5624005d50adf1069120356ac68c471a56c511a5bf6972b022046fbf8ec6950a307c3c18ca32ad2955c559b0d9bbd9ec25b64f4806f78cadf770141041ea9afa5231dc4d65a2667789ebf6806829b6cf88bfe443228f95263730b7b70fb8b00b2b33777e168bcc7ad8e0afa5c7828842794ce3814c901e24193700f6cfdffffff02a0860100000000001976a914ade907333744c953140355ff60d341cedf7609fd88ac68830a00000000001976a9145d48feae4c97677e4ca7dcd73b0d9fd1399c962b88acc9cc1300",
+ "e87a207ff3f3d2601fd086e90fa81a8ecb9c15e6974e649d53b9ebc2b087af25": "01000000010db780fff7dfcef6dba9268ecf4f6df45a1a86b86cad6f59738a0ce29b145c47010000008a47304402202887ec6ec200e4e2b4178112633011cbdbc999e66d398b1ff3998e23f7c5541802204964bd07c0f18c48b7b9c00fbe34c7bc035efc479e21a4fa196027743f06095f0141044f1714ed25332bb2f74be169784577d0838aa66f2374f5d8cbbf216063626822d536411d13cbfcef1ff3cc1d58499578bc4a3c4a0be2e5184b2dd7963ef67713fdffffff02a0860100000000001600145bbdf3ba178f517d4812d286a40c436a9088076e6a0b0c00000000001976a9143fc16bef782f6856ff6638b1b99e4d3f863581d388acfbcb1300"
+ }
+ txid_list = sorted(list(transactions))
+
+ @classmethod
+ def create_old_wallet(cls):
+ ks = keystore.from_old_mpk('e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3')
+ # seed words: powerful random nobody notice nothing important anyway look away hidden message over
+ w = WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=20)
+ # some txns are beyond gap limit:
+ w.create_new_address(for_change=True)
+ return w
+
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_restoring_old_wallet_txorder1(self, mock_write):
+ w = self.create_old_wallet()
+ for i in [2, 12, 7, 9, 11, 10, 16, 6, 17, 1, 13, 15, 5, 8, 4, 0, 14, 18, 3]:
+ tx = Transaction(self.transactions[self.txid_list[i]])
+ w.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual(27633300, sum(w.get_balance()))
+
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_restoring_old_wallet_txorder2(self, mock_write):
+ w = self.create_old_wallet()
+ for i in [9, 18, 2, 0, 13, 3, 1, 11, 4, 17, 7, 14, 12, 15, 10, 8, 5, 6, 16]:
+ tx = Transaction(self.transactions[self.txid_list[i]])
+ w.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual(27633300, sum(w.get_balance()))
+
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_restoring_old_wallet_txorder3(self, mock_write):
+ w = self.create_old_wallet()
+ for i in [5, 8, 17, 0, 9, 10, 12, 3, 15, 18, 2, 11, 14, 7, 16, 1, 4, 6, 13]:
+ tx = Transaction(self.transactions[self.txid_list[i]])
+ w.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
+ self.assertEqual(27633300, sum(w.get_balance()))
+
+
+class TestWalletHistory_EvilGapLimit(TestCaseForTestnet):
+ transactions = {
+ # txn A:
+ "511a35e240f4c8855de4c548dad932d03611a37e94e9203fdb6fc79911fe1dd4": "010000000001018aacc3c8f98964232ebb74e379d8ff4e800991eecfcf64bd1793954f5e50a8790100000000fdffffff0340420f0000000000160014dbf321e905d544b54b86a2f3ed95b0ac66a3ddb0ff0514000000000016001474f1c130d3db22894efb3b7612b2c924628d0d7e80841e000000000016001488492707677190c073b6555fb08d37e91bbb75d802483045022100cf2904e09ea9d2670367eccc184d92fcb8a9b9c79a12e4efe81df161077945db02203530276a3401d944cf7a292e0660f36ee1df4a1c92c131d2c0d31d267d52524901210215f523a412a5262612e1a5ef9842dc864b0d73dc61fb4c6bfd480a867bebb1632e181400",
+ # txn B:
+ "fde0b68938709c4979827caa576e9455ded148537fdb798fd05680da64dc1b4f": "01000000000101a317998ac6cc717de17213804e1459900fe257b9f4a3b9b9edd29806728277530100000000fdffffff03c0c62d00000000001600149543301687b1ca2c67718d55fbe10413c73ddec200093d00000000001600141bc12094a4475dcfbf24f9920dafddf9104ca95b3e4a4c0000000000160014b226a59f2609aa7da4026fe2c231b5ae7be12ac302483045022100f1082386d2ce81612a3957e2801803938f6c0066d76cfbd853918d4119f396df022077d05a2b482b89707a8a600013cb08448cf211218a462f2a23c2c0d80a8a0ca7012103f4aac7e189de53d95e0cb2e45d3c0b2be18e93420734934c61a6a5ad88dd541033181400",
+ # txn C:
+ "268fce617aaaa4847835c2212b984d7b7741fdab65de22813288341819bc5656": "010000000001014f1bdc64da8056d08f79db7f5348d1de55946e57aa7c8279499c703889b6e0fd0100000000fdffffff0260e316000000000016001445e9879cf7cd5b4a15df7ddcaf5c6dca0e1508bacc242600000000001600141bc12094a4475dcfbf24f9920dafddf9104ca95b02483045022100ae3618912f341fefee11b67e0047c47c88c4fa031561c3fafe993259dd14d846022056fa0a5b5d8a65942fa68bcc2f848fd71fa455ba42bc2d421b67eb49ba62aa4e01210394d8f4f06c2ea9c569eb050c897737a7315e7f2104d9b536b49968cc89a1f11033181400",
+ }
+
+ @classmethod
+ def create_wallet(cls):
+ ks = keystore.from_xpub('vpub5Vhmk4dEJKanDTTw6immKXa3thw45u3gbd1rPYjREB6viP13sVTWcH6kvbR2YeLtGjradr6SFLVt9PxWDBSrvw1Dc1nmd3oko3m24CQbfaJ')
+ # seed words: nephew work weather maze pyramid employ check permit garment scene kiwi smooth
+ w = WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=20)
+ return w
+
+ @mock.patch.object(storage.WalletStorage, '_write')
+ def test_restoring_wallet_txorder1(self, mock_write):
+ w = self.create_wallet()
+ w.storage.put('stored_height', 1316917 + 100)
+ for txid in self.transactions:
+ tx = Transaction(self.transactions[txid])
+ w.transactions[tx.txid()] = tx
+ # txn A is an external incoming txn paying to addr (3) and (15)
+ # txn B is an external incoming txn paying to addr (4) and (25)
+ # txn C is an internal transfer txn from addr (25) -- to -- (1) and (25)
+ w.receive_history_callback('tb1qgh5c088he4d559wl0hw27hrdeg8p2z96pefn4q', # HD index 1
+ [('268fce617aaaa4847835c2212b984d7b7741fdab65de22813288341819bc5656', 1316917)],
+ {})
+ w.synchronize()
+ w.receive_history_callback('tb1qm0ejr6g964zt2jux5te7m9ds43n28hdsdz9ull', # HD index 3
+ [('511a35e240f4c8855de4c548dad932d03611a37e94e9203fdb6fc79911fe1dd4', 1316912)],
+ {})
+ w.synchronize()
+ w.receive_history_callback('tb1qj4pnq958k89zcem3342lhcgyz0rnmhkzl6x0cl', # HD index 4
+ [('fde0b68938709c4979827caa576e9455ded148537fdb798fd05680da64dc1b4f', 1316917)],
+ {})
+ w.synchronize()
+ w.receive_history_callback('tb1q3pyjwpm8wxgvquak240mprfhaydmkawcsl25je', # HD index 15
+ [('511a35e240f4c8855de4c548dad932d03611a37e94e9203fdb6fc79911fe1dd4', 1316912)],
+ {})
+ w.synchronize()
+ w.receive_history_callback('tb1qr0qjp99ygawul0eylxfqmt7alygye22mj33vej', # HD index 25
+ [('fde0b68938709c4979827caa576e9455ded148537fdb798fd05680da64dc1b4f', 1316917),
+ ('268fce617aaaa4847835c2212b984d7b7741fdab65de22813288341819bc5656', 1316917)],
+ {})
+ w.synchronize()
+ self.assertEqual(9999788, sum(w.get_balance()))
diff --git a/electrum/tests/test_x509.py b/electrum/tests/test_x509.py
new file mode 100644
index 000000000..75d11a8c2
--- /dev/null
+++ b/electrum/tests/test_x509.py
@@ -0,0 +1,6 @@
+import unittest
+from electrum.x509 import X509
+class TestX509(unittest.TestCase):
+ def test_generalizedtime(self):
+ full = X509(b'0\x82\x05F0\x82\x03.\x02\t\x00\xfeV\xd6\xb5?\xb1j\xe40\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000d1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x0c\nCalifornia1!0\x1f\x06\x03U\x04\n\x0c\x18Internet Widgits Pty Ltd1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14testnet.qtornado.com0 \x17\r180206010225Z\x18\x0f21180113010225Z0d1\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08\x0c\nCalifornia1!0\x1f\x06\x03U\x04\n\x0c\x18Internet Widgits Pty Ltd1\x1d0\x1b\x06\x03U\x04\x03\x0c\x14testnet.qtornado.com0\x82\x02"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x02\x0f\x000\x82\x02\n\x02\x82\x02\x01\x00\xc2B\xe0\xa8\xd9$M\xbc)Wx\x0cv\x00\xc0\xfa2Ew:\xce\xa7\xcb\xc8\r?\xea\xc5R(\xc7\xc3Y\xe7zq=\xcd\x8d\xe3\x86\x9ecSI\xc7\x84\xf2~\x91\xd4\x19\xc2;\x97\xe81e\xf2\xeb\xf1\xadw\xa3p\x88A*-\r\xb6Yt\x98R\xe8\x8a\xf9\xb5>"F\xac\x19%\xc8~\x1d\xac\x93A\xffk\xce\xdb\xfc9\x05\xa0\xad\xf9V\x0f0\xa2b\xd0@\xe4\xf1\xb1\xe8\xb1\x10[&\xa1\xff\x13\xcfQ\xb7\x805\xef\xe7tL\xe5|\x08W\x8c\xd72\x9d\'\xeb\x92)3N\x01M\x06\xa9\xdc\xe4\'\x13\x90x\xd8\x830\x97\xa8\xcc2d \xfa\x91\x04\xd0\x1b\xe7\xaa t\x87\xba]\xb5w\x05(\xba\x07\xc2X$~?L\xc5\x03\xb2\xdeQ\xf3\xf3\xdab\xd9\x92\xd9\x86^:\x93\xc9\x86~\xd1\x94\xd4\x80\x9c\xff0\xc6m\xf4\xf0\xd6\x18\x96l\x1d\x0c\xe8\x15 \x8c\x89\xcb\xa4*\xd9\xefg\x844\x81\xb3\xce\xa1\x8a|\xf9h\xc3\xe1!\xfeZ`\xb71\x97Kj\x0b"\xd3\x98T\r\xd9\xbb\xc6\x0b\x81\x81k\x0e\xd01\x16\x91\xe4A\x8c\x1a\xe9W\xd4=<\xd4m_\xd4m\xa4H\x14\xc0\xae\x12\xab\x808\xf1\xf9_\xbb\xfb\xd0U\x0e\\\xd3.?\xa36\xe1hstU"\x17P\xcb>\x83\x9c\xaa\x9b\xb7\xe5\xb4\xb5W\xdc\xc1\xee\x91K\x12\xc2\xe1U\xaf\xf7I`\x83\x91\x0c\xc0\xcb\x15\x13!V\xa9\xc1\xca\x1b\x80\xff\xd8\x1f\xd8_+\x83\xcd\xcb%\xd6\xb7\xdc\x8a2\xa8Q\x1f\xbb.\xdf\x05\xb7hD\xab\xea\xe9\xfb.\xdd\x93\xd1\xf0\xb8r\xb9t.\xab\xf6]\xac\xc9U9\x87\x9e\xe36 \x87\xe7eo\x98\xac\xf4\x87\x8e\xf4\xa86\xd3\xcapy\xee\xa0]\xdbA\xb9\x00\xe9_R\xc8\xf7\xca\x13\xc6\xb1Z|c\xe8v\xa24\xac?k\xf1\xc4\x97\x18\x07\xbaU\xc9\xf5? \x95\x8f\x11\xa7\xc9\x8eY\x9c\xdfnx?\x88\xba\x90\xef\x94WU\xb5\xcf\x0b"\xe8\xfe\xa6.\x0cr-\xaf3\x8a\xe6v\xf9\xb91\x87\x91\xc6\xb1\xe9\xb9UP\xf5\x14\xb7\x99\x80\xc0\xc5}\x9a~\x7f\x06\x1e\xb8\x05\xd5\xa2LXO\\73i\x82\xcd\xc6#\xb7\xa4q\xd7\xd4y\xb1d\xaf\xa8\t\x9e1K\xd94\xaf7\x08\x8c);\xd2\xed\x91\xc6\xed\x83\x90\r\xef\x85\xf0\xfeJi\x02;\xf0\x0b\x03\xe7\xc1\x84\xd45\xaeP\xc2Lp\x1akb\xcaP\xe9\xfc\xc1\xc8VPQu\x85\x92l\x12\xb99{\x91\xd0\xa6d\n\xde\xf85\x93e\xfa\\\xf9cKx8\x84"s\xb8\xe52~\x97\x05\xc3\xf6\x1c\xca\x0b\xda\x8b\x90\xfeu5,\x94,\x99\xf9\x9a\xf3T\x8dAZ\xc7\xe9\x95-\x98\xf2\xbaL\x89\xc0?\xba1\xb5\\t|RY_\xc6\xabr\xe8')
+ full.check_date()
diff --git a/electrum/transaction.py b/electrum/transaction.py
new file mode 100644
index 000000000..446a8b774
--- /dev/null
+++ b/electrum/transaction.py
@@ -0,0 +1,1242 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+
+# Note: The deserialization code originally comes from ABE.
+
+from typing import Sequence, Union, NamedTuple, Tuple, Optional, Iterable
+
+from .util import print_error, profiler
+
+from . import ecc
+from . import bitcoin
+from .bitcoin import *
+import struct
+import traceback
+import sys
+
+#
+# Workalike python implementation of Bitcoin's CDataStream class.
+#
+from .keystore import xpubkey_to_address, xpubkey_to_pubkey
+
+NO_SIGNATURE = 'ff'
+PARTIAL_TXN_HEADER_MAGIC = b'EPTF\xff'
+
+
+class SerializationError(Exception):
+ """ Thrown when there's a problem deserializing or serializing """
+
+
+class UnknownTxinType(Exception):
+ pass
+
+
+class NotRecognizedRedeemScript(Exception):
+ pass
+
+
+class MalformedBitcoinScript(Exception):
+ pass
+
+
+TxOutput = NamedTuple("TxOutput", [('type', int), ('address', str), ('value', Union[int, str])])
+# ^ value is str when the output is set to max: '!'
+
+
+TxOutputHwInfo = NamedTuple("TxOutputHwInfo", [('address_index', Tuple),
+ ('sorted_xpubs', Iterable[str]),
+ ('num_sig', Optional[int]),
+ ('script_type', str)])
+
+
+class BCDataStream(object):
+ def __init__(self):
+ self.input = None
+ self.read_cursor = 0
+
+ def clear(self):
+ self.input = None
+ self.read_cursor = 0
+
+ def write(self, _bytes): # Initialize with string of _bytes
+ if self.input is None:
+ self.input = bytearray(_bytes)
+ else:
+ self.input += bytearray(_bytes)
+
+ def read_string(self, encoding='ascii'):
+ # Strings are encoded depending on length:
+ # 0 to 252 : 1-byte-length followed by bytes (if any)
+ # 253 to 65,535 : byte'253' 2-byte-length followed by bytes
+ # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
+ # ... and the Bitcoin client is coded to understand:
+ # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
+ # ... but I don't think it actually handles any strings that big.
+ if self.input is None:
+ raise SerializationError("call write(bytes) before trying to deserialize")
+
+ length = self.read_compact_size()
+
+ return self.read_bytes(length).decode(encoding)
+
+ def write_string(self, string, encoding='ascii'):
+ string = to_bytes(string, encoding)
+ # Length-encoded as with read-string
+ self.write_compact_size(len(string))
+ self.write(string)
+
+ def read_bytes(self, length):
+ try:
+ result = self.input[self.read_cursor:self.read_cursor+length]
+ self.read_cursor += length
+ return result
+ except IndexError:
+ raise SerializationError("attempt to read past end of buffer")
+
+ def can_read_more(self) -> bool:
+ if not self.input:
+ return False
+ return self.read_cursor < len(self.input)
+
+ def read_boolean(self): return self.read_bytes(1)[0] != chr(0)
+ def read_int16(self): return self._read_num('0:
+ continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
+ if to_match[i] != decoded[i][0]:
+ return False
+ return True
+
+
+def parse_sig(x_sig):
+ return [None if x == NO_SIGNATURE else x for x in x_sig]
+
+def safe_parse_pubkey(x):
+ try:
+ return xpubkey_to_pubkey(x)
+ except:
+ return x
+
+def parse_scriptSig(d, _bytes):
+ try:
+ decoded = [ x for x in script_GetOp(_bytes) ]
+ except Exception as e:
+ # coinbase transactions raise an exception
+ print_error("parse_scriptSig: cannot find address in input script (coinbase?)",
+ bh2u(_bytes))
+ return
+
+ match = [ opcodes.OP_PUSHDATA4 ]
+ if match_decoded(decoded, match):
+ item = decoded[0][1]
+ if item[0] == 0:
+ # segwit embedded into p2sh
+ # witness version 0
+ d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
+ if len(item) == 22:
+ d['type'] = 'p2wpkh-p2sh'
+ elif len(item) == 34:
+ d['type'] = 'p2wsh-p2sh'
+ else:
+ print_error("unrecognized txin type", bh2u(item))
+ elif opcodes.OP_1 <= item[0] <= opcodes.OP_16:
+ # segwit embedded into p2sh
+ # witness version 1-16
+ pass
+ else:
+ # assert item[0] == 0x30
+ # pay-to-pubkey
+ d['type'] = 'p2pk'
+ d['address'] = "(pubkey)"
+ d['signatures'] = [bh2u(item)]
+ d['num_sig'] = 1
+ d['x_pubkeys'] = ["(pubkey)"]
+ d['pubkeys'] = ["(pubkey)"]
+ return
+
+ # p2pkh TxIn transactions push a signature
+ # (71-73 bytes) and then their public key
+ # (33 or 65 bytes) onto the stack:
+ match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ]
+ if match_decoded(decoded, match):
+ sig = bh2u(decoded[0][1])
+ x_pubkey = bh2u(decoded[1][1])
+ try:
+ signatures = parse_sig([sig])
+ pubkey, address = xpubkey_to_address(x_pubkey)
+ except:
+ print_error("parse_scriptSig: cannot find address in input script (p2pkh?)",
+ bh2u(_bytes))
+ return
+ d['type'] = 'p2pkh'
+ d['signatures'] = signatures
+ d['x_pubkeys'] = [x_pubkey]
+ d['num_sig'] = 1
+ d['pubkeys'] = [pubkey]
+ d['address'] = address
+ return
+
+ # p2sh transaction, m of n
+ match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)
+ if match_decoded(decoded, match):
+ x_sig = [bh2u(x[1]) for x in decoded[1:-1]]
+ redeem_script_unsanitized = decoded[-1][1] # for partial multisig txn, this has x_pubkeys
+ try:
+ m, n, x_pubkeys, pubkeys, redeem_script = parse_redeemScript_multisig(redeem_script_unsanitized)
+ except NotRecognizedRedeemScript:
+ print_error("parse_scriptSig: cannot find address in input script (p2sh?)",
+ bh2u(_bytes))
+ # we could still guess:
+ # d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1]))
+ return
+ # write result in d
+ d['type'] = 'p2sh'
+ d['num_sig'] = m
+ d['signatures'] = parse_sig(x_sig)
+ d['x_pubkeys'] = x_pubkeys
+ d['pubkeys'] = pubkeys
+ d['redeem_script'] = redeem_script
+ d['address'] = hash160_to_p2sh(hash_160(bfh(redeem_script)))
+ return
+
+ # custom partial format for imported addresses
+ match = [ opcodes.OP_INVALIDOPCODE, opcodes.OP_0, opcodes.OP_PUSHDATA4 ]
+ if match_decoded(decoded, match):
+ x_pubkey = bh2u(decoded[2][1])
+ pubkey, address = xpubkey_to_address(x_pubkey)
+ d['type'] = 'address'
+ d['address'] = address
+ d['num_sig'] = 1
+ d['x_pubkeys'] = [x_pubkey]
+ d['pubkeys'] = None # get_sorted_pubkeys will populate this
+ d['signatures'] = [None]
+ return
+
+ print_error("parse_scriptSig: cannot find address in input script (unknown)",
+ bh2u(_bytes))
+
+
+def parse_redeemScript_multisig(redeem_script: bytes):
+ try:
+ dec2 = [ x for x in script_GetOp(redeem_script) ]
+ except MalformedBitcoinScript:
+ raise NotRecognizedRedeemScript()
+ try:
+ m = dec2[0][0] - opcodes.OP_1 + 1
+ n = dec2[-2][0] - opcodes.OP_1 + 1
+ except IndexError:
+ raise NotRecognizedRedeemScript()
+ op_m = opcodes.OP_1 + m - 1
+ op_n = opcodes.OP_1 + n - 1
+ match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
+ if not match_decoded(dec2, match_multisig):
+ raise NotRecognizedRedeemScript()
+ x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
+ pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
+ redeem_script2 = bfh(multisig_script(x_pubkeys, m))
+ if redeem_script2 != redeem_script:
+ raise NotRecognizedRedeemScript()
+ redeem_script_sanitized = multisig_script(pubkeys, m)
+ return m, n, x_pubkeys, pubkeys, redeem_script_sanitized
+
+
+def get_address_from_output_script(_bytes, *, net=None):
+ try:
+ decoded = [x for x in script_GetOp(_bytes)]
+ except MalformedBitcoinScript:
+ decoded = None
+
+ # The Genesis Block, self-payments, and pay-by-IP-address payments look like:
+ # 65 BYTES:... CHECKSIG
+ match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ]
+ if match_decoded(decoded, match):
+ return TYPE_PUBKEY, bh2u(decoded[0][1])
+
+ # Pay-by-Bitcoin-address TxOuts look like:
+ # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
+ match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
+ if match_decoded(decoded, match):
+ return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net)
+
+ # p2sh
+ match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ]
+ if match_decoded(decoded, match):
+ return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net)
+
+ # segwit address
+ possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1))
+ for witver, opcode in enumerate(possible_witness_versions):
+ match = [ opcode, opcodes.OP_PUSHDATA4 ]
+ if match_decoded(decoded, match):
+ return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver, net=net)
+
+ return TYPE_SCRIPT, bh2u(_bytes)
+
+
+def parse_input(vds, full_parse: bool):
+ d = {}
+ prevout_hash = hash_encode(vds.read_bytes(32))
+ prevout_n = vds.read_uint32()
+ scriptSig = vds.read_bytes(vds.read_compact_size())
+ sequence = vds.read_uint32()
+ d['prevout_hash'] = prevout_hash
+ d['prevout_n'] = prevout_n
+ d['scriptSig'] = bh2u(scriptSig)
+ d['sequence'] = sequence
+ d['type'] = 'unknown' if prevout_hash != '00'*32 else 'coinbase'
+ d['address'] = None
+ d['num_sig'] = 0
+ if not full_parse:
+ return d
+ d['x_pubkeys'] = []
+ d['pubkeys'] = []
+ d['signatures'] = {}
+ if d['type'] != 'coinbase' and scriptSig:
+ try:
+ parse_scriptSig(d, scriptSig)
+ except BaseException:
+ traceback.print_exc(file=sys.stderr)
+ print_error('failed to parse scriptSig', bh2u(scriptSig))
+ return d
+
+
+def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
+ """Constructs a witness from the given stack items."""
+ witness = var_int(len(items))
+ for item in items:
+ if type(item) is int:
+ item = bitcoin.script_num_to_hex(item)
+ elif type(item) is bytes:
+ item = bh2u(item)
+ witness += bitcoin.witness_push(item)
+ return witness
+
+
+def parse_witness(vds, txin, full_parse: bool):
+ n = vds.read_compact_size()
+ if n == 0:
+ txin['witness'] = '00'
+ return
+ if n == 0xffffffff:
+ txin['value'] = vds.read_uint64()
+ txin['witness_version'] = vds.read_uint16()
+ n = vds.read_compact_size()
+ # now 'n' is the number of items in the witness
+ w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))
+ txin['witness'] = construct_witness(w)
+ if not full_parse:
+ return
+
+ try:
+ if txin.get('witness_version', 0) != 0:
+ raise UnknownTxinType()
+ if txin['type'] == 'coinbase':
+ pass
+ elif txin['type'] == 'address':
+ pass
+ elif txin['type'] == 'p2wsh-p2sh' or n > 2:
+ witness_script_unsanitized = w[-1] # for partial multisig txn, this has x_pubkeys
+ try:
+ m, n, x_pubkeys, pubkeys, witness_script = parse_redeemScript_multisig(bfh(witness_script_unsanitized))
+ except NotRecognizedRedeemScript:
+ raise UnknownTxinType()
+ txin['signatures'] = parse_sig(w[1:-1])
+ txin['num_sig'] = m
+ txin['x_pubkeys'] = x_pubkeys
+ txin['pubkeys'] = pubkeys
+ txin['witness_script'] = witness_script
+ if not txin.get('scriptSig'): # native segwit script
+ txin['type'] = 'p2wsh'
+ txin['address'] = bitcoin.script_to_p2wsh(witness_script)
+ elif txin['type'] == 'p2wpkh-p2sh' or n == 2:
+ txin['num_sig'] = 1
+ txin['x_pubkeys'] = [w[1]]
+ txin['pubkeys'] = [safe_parse_pubkey(w[1])]
+ txin['signatures'] = parse_sig([w[0]])
+ if not txin.get('scriptSig'): # native segwit script
+ txin['type'] = 'p2wpkh'
+ txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0]))
+ else:
+ raise UnknownTxinType()
+ except UnknownTxinType:
+ txin['type'] = 'unknown'
+ except BaseException:
+ txin['type'] = 'unknown'
+ traceback.print_exc(file=sys.stderr)
+ print_error('failed to parse witness', txin.get('witness'))
+
+
+def parse_output(vds, i):
+ d = {}
+ d['value'] = vds.read_int64()
+ if d['value'] > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN:
+ raise SerializationError('invalid output amount (too large)')
+ if d['value'] < 0:
+ raise SerializationError('invalid output amount (negative)')
+ scriptPubKey = vds.read_bytes(vds.read_compact_size())
+ d['type'], d['address'] = get_address_from_output_script(scriptPubKey)
+ d['scriptPubKey'] = bh2u(scriptPubKey)
+ d['prevout_n'] = i
+ return d
+
+
+def deserialize(raw: str, force_full_parse=False) -> dict:
+ raw_bytes = bfh(raw)
+ d = {}
+ if raw_bytes[:5] == PARTIAL_TXN_HEADER_MAGIC:
+ d['partial'] = is_partial = True
+ partial_format_version = raw_bytes[5]
+ if partial_format_version != 0:
+ raise SerializationError('unknown tx partial serialization format version: {}'
+ .format(partial_format_version))
+ raw_bytes = raw_bytes[6:]
+ else:
+ d['partial'] = is_partial = False
+ full_parse = force_full_parse or is_partial
+ vds = BCDataStream()
+ vds.write(raw_bytes)
+ d['version'] = vds.read_int32()
+ n_vin = vds.read_compact_size()
+ is_segwit = (n_vin == 0)
+ if is_segwit:
+ marker = vds.read_bytes(1)
+ if marker != b'\x01':
+ raise ValueError('invalid txn marker byte: {}'.format(marker))
+ n_vin = vds.read_compact_size()
+ d['segwit_ser'] = is_segwit
+ d['inputs'] = [parse_input(vds, full_parse=full_parse) for i in range(n_vin)]
+ n_vout = vds.read_compact_size()
+ d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
+ if is_segwit:
+ for i in range(n_vin):
+ txin = d['inputs'][i]
+ parse_witness(vds, txin, full_parse=full_parse)
+ d['lockTime'] = vds.read_uint32()
+ if vds.can_read_more():
+ raise SerializationError('extra junk at the end')
+ return d
+
+
+# pay & redeem scripts
+
+
+
+def multisig_script(public_keys: Sequence[str], m: int) -> str:
+ n = len(public_keys)
+ assert n <= 15
+ assert m <= n
+ op_m = format(opcodes.OP_1 + m - 1, 'x')
+ op_n = format(opcodes.OP_1 + n - 1, 'x')
+ keylist = [op_push(len(k)//2) + k for k in public_keys]
+ return op_m + ''.join(keylist) + op_n + 'ae'
+
+
+
+
+class Transaction:
+
+ def __str__(self):
+ if self.raw is None:
+ self.raw = self.serialize()
+ return self.raw
+
+ def __init__(self, raw):
+ if raw is None:
+ self.raw = None
+ elif isinstance(raw, str):
+ self.raw = raw.strip() if raw else None
+ elif isinstance(raw, dict):
+ self.raw = raw['hex']
+ else:
+ raise Exception("cannot initialize transaction", raw)
+ self._inputs = None
+ self._outputs = None
+ self.locktime = 0
+ self.version = 1
+ # by default we assume this is a partial txn;
+ # this value will get properly set when deserializing
+ self.is_partial_originally = True
+ self._segwit_ser = None # None means "don't know"
+
+ def update(self, raw):
+ self.raw = raw
+ self._inputs = None
+ self.deserialize()
+
+ def inputs(self):
+ if self._inputs is None:
+ self.deserialize()
+ return self._inputs
+
+ def outputs(self):
+ if self._outputs is None:
+ self.deserialize()
+ return self._outputs
+
+ @classmethod
+ def get_sorted_pubkeys(self, txin):
+ # sort pubkeys and x_pubkeys, using the order of pubkeys
+ if txin['type'] == 'coinbase':
+ return [], []
+ x_pubkeys = txin['x_pubkeys']
+ pubkeys = txin.get('pubkeys')
+ if pubkeys is None:
+ pubkeys = [xpubkey_to_pubkey(x) for x in x_pubkeys]
+ pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys)))
+ txin['pubkeys'] = pubkeys = list(pubkeys)
+ txin['x_pubkeys'] = x_pubkeys = list(x_pubkeys)
+ return pubkeys, x_pubkeys
+
+ def update_signatures(self, signatures: Sequence[str]):
+ """Add new signatures to a transaction
+
+ `signatures` is expected to be a list of sigs with signatures[i]
+ intended for self._inputs[i].
+ This is used by the Trezor, KeepKey an Safe-T plugins.
+ """
+ if self.is_complete():
+ return
+ if len(self.inputs()) != len(signatures):
+ raise Exception('expected {} signatures; got {}'.format(len(self.inputs()), len(signatures)))
+ for i, txin in enumerate(self.inputs()):
+ pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
+ sig = signatures[i]
+ if sig in txin.get('signatures'):
+ continue
+ pre_hash = Hash(bfh(self.serialize_preimage(i)))
+ sig_string = ecc.sig_string_from_der_sig(bfh(sig[:-2]))
+ for recid in range(4):
+ try:
+ public_key = ecc.ECPubkey.from_sig_string(sig_string, recid, pre_hash)
+ except ecc.InvalidECPointException:
+ # the point might not be on the curve for some recid values
+ continue
+ pubkey_hex = public_key.get_public_key_hex(compressed=True)
+ if pubkey_hex in pubkeys:
+ try:
+ public_key.verify_message_hash(sig_string, pre_hash)
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ continue
+ j = pubkeys.index(pubkey_hex)
+ print_error("adding sig", i, j, pubkey_hex, sig)
+ self.add_signature_to_txin(i, j, sig)
+ #self._inputs[i]['x_pubkeys'][j] = pubkey
+ break
+ # redo raw
+ self.raw = self.serialize()
+
+ def add_signature_to_txin(self, i, signingPos, sig):
+ txin = self._inputs[i]
+ txin['signatures'][signingPos] = sig
+ txin['scriptSig'] = None # force re-serialization
+ txin['witness'] = None # force re-serialization
+ self.raw = None
+
+ def deserialize(self, force_full_parse=False):
+ if self.raw is None:
+ return
+ #self.raw = self.serialize()
+ if self._inputs is not None:
+ return
+ d = deserialize(self.raw, force_full_parse)
+ self._inputs = d['inputs']
+ self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
+ self.locktime = d['lockTime']
+ self.version = d['version']
+ self.is_partial_originally = d['partial']
+ self._segwit_ser = d['segwit_ser']
+ return d
+
+ @classmethod
+ def from_io(klass, inputs, outputs, locktime=0):
+ self = klass(None)
+ self._inputs = inputs
+ self._outputs = outputs
+ self.locktime = locktime
+ return self
+
+ @classmethod
+ def pay_script(self, output_type, addr):
+ if output_type == TYPE_SCRIPT:
+ return addr
+ elif output_type == TYPE_ADDRESS:
+ return bitcoin.address_to_script(addr)
+ elif output_type == TYPE_PUBKEY:
+ return bitcoin.public_key_to_p2pk_script(addr)
+ else:
+ raise TypeError('Unknown output type')
+
+ @classmethod
+ def estimate_pubkey_size_from_x_pubkey(cls, x_pubkey):
+ try:
+ if x_pubkey[0:2] in ['02', '03']: # compressed pubkey
+ return 0x21
+ elif x_pubkey[0:2] == '04': # uncompressed pubkey
+ return 0x41
+ elif x_pubkey[0:2] == 'ff': # bip32 extended pubkey
+ return 0x21
+ elif x_pubkey[0:2] == 'fe': # old electrum extended pubkey
+ return 0x41
+ except Exception as e:
+ pass
+ return 0x21 # just guess it is compressed
+
+ @classmethod
+ def estimate_pubkey_size_for_txin(cls, txin):
+ pubkeys = txin.get('pubkeys', [])
+ x_pubkeys = txin.get('x_pubkeys', [])
+ if pubkeys and len(pubkeys) > 0:
+ return cls.estimate_pubkey_size_from_x_pubkey(pubkeys[0])
+ elif x_pubkeys and len(x_pubkeys) > 0:
+ return cls.estimate_pubkey_size_from_x_pubkey(x_pubkeys[0])
+ else:
+ return 0x21 # just guess it is compressed
+
+ @classmethod
+ def get_siglist(self, txin, estimate_size=False):
+ # if we have enough signatures, we use the actual pubkeys
+ # otherwise, use extended pubkeys (with bip32 derivation)
+ if txin['type'] == 'coinbase':
+ return [], []
+ num_sig = txin.get('num_sig', 1)
+ if estimate_size:
+ pubkey_size = self.estimate_pubkey_size_for_txin(txin)
+ pk_list = ["00" * pubkey_size] * len(txin.get('x_pubkeys', [None]))
+ # we assume that signature will be 0x48 bytes long
+ sig_list = [ "00" * 0x48 ] * num_sig
+ else:
+ pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
+ x_signatures = txin['signatures']
+ signatures = list(filter(None, x_signatures))
+ is_complete = len(signatures) == num_sig
+ if is_complete:
+ pk_list = pubkeys
+ sig_list = signatures
+ else:
+ pk_list = x_pubkeys
+ sig_list = [sig if sig else NO_SIGNATURE for sig in x_signatures]
+ return pk_list, sig_list
+
+ @classmethod
+ def serialize_witness(self, txin, estimate_size=False):
+ _type = txin['type']
+ if not self.is_segwit_input(txin) and not self.is_input_value_needed(txin):
+ return '00'
+ if _type == 'coinbase':
+ return txin['witness']
+
+ witness = txin.get('witness', None)
+ if witness is None or estimate_size:
+ if _type == 'address' and estimate_size:
+ _type = self.guess_txintype_from_address(txin['address'])
+ pubkeys, sig_list = self.get_siglist(txin, estimate_size)
+ if _type in ['p2wpkh', 'p2wpkh-p2sh']:
+ witness = construct_witness([sig_list[0], pubkeys[0]])
+ elif _type in ['p2wsh', 'p2wsh-p2sh']:
+ witness_script = multisig_script(pubkeys, txin['num_sig'])
+ witness = construct_witness([0] + sig_list + [witness_script])
+ else:
+ witness = txin.get('witness', '00')
+
+ if self.is_txin_complete(txin) or estimate_size:
+ partial_format_witness_prefix = ''
+ else:
+ input_value = int_to_hex(txin['value'], 8)
+ witness_version = int_to_hex(txin.get('witness_version', 0), 2)
+ partial_format_witness_prefix = var_int(0xffffffff) + input_value + witness_version
+ return partial_format_witness_prefix + witness
+
+ @classmethod
+ def is_segwit_input(cls, txin, guess_for_address=False):
+ _type = txin['type']
+ if _type == 'address' and guess_for_address:
+ _type = cls.guess_txintype_from_address(txin['address'])
+ has_nonzero_witness = txin.get('witness', '00') not in ('00', None)
+ return cls.is_segwit_inputtype(_type) or has_nonzero_witness
+
+ @classmethod
+ def is_segwit_inputtype(cls, txin_type):
+ return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
+
+ @classmethod
+ def is_input_value_needed(cls, txin):
+ return cls.is_segwit_input(txin) or txin['type'] == 'address'
+
+ @classmethod
+ def guess_txintype_from_address(cls, addr):
+ # It's not possible to tell the script type in general
+ # just from an address.
+ # - "1" addresses are of course p2pkh
+ # - "3" addresses are p2sh but we don't know the redeem script..
+ # - "bc1" addresses (if they are 42-long) are p2wpkh
+ # - "bc1" addresses that are 62-long are p2wsh but we don't know the script..
+ # If we don't know the script, we _guess_ it is pubkeyhash.
+ # As this method is used e.g. for tx size estimation,
+ # the estimation will not be precise.
+ witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr)
+ if witprog is not None:
+ return 'p2wpkh'
+ addrtype, hash_160 = b58_address_to_hash160(addr)
+ if addrtype == constants.net.ADDRTYPE_P2PKH:
+ return 'p2pkh'
+ elif addrtype == constants.net.ADDRTYPE_P2SH:
+ return 'p2wpkh-p2sh'
+
+ @classmethod
+ def input_script(self, txin, estimate_size=False):
+ _type = txin['type']
+ if _type == 'coinbase':
+ return txin['scriptSig']
+
+ # If there is already a saved scriptSig, just return that.
+ # This allows manual creation of txins of any custom type.
+ # However, if the txin is not complete, we might have some garbage
+ # saved from our partial txn ser format, so we re-serialize then.
+ script_sig = txin.get('scriptSig', None)
+ if script_sig is not None and self.is_txin_complete(txin):
+ return script_sig
+
+ pubkeys, sig_list = self.get_siglist(txin, estimate_size)
+ script = ''.join(push_script(x) for x in sig_list)
+ if _type == 'address' and estimate_size:
+ _type = self.guess_txintype_from_address(txin['address'])
+ if _type == 'p2pk':
+ pass
+ elif _type == 'p2sh':
+ # put op_0 before script
+ script = '00' + script
+ redeem_script = multisig_script(pubkeys, txin['num_sig'])
+ script += push_script(redeem_script)
+ elif _type == 'p2pkh':
+ script += push_script(pubkeys[0])
+ elif _type in ['p2wpkh', 'p2wsh']:
+ return ''
+ elif _type == 'p2wpkh-p2sh':
+ pubkey = safe_parse_pubkey(pubkeys[0])
+ scriptSig = bitcoin.p2wpkh_nested_script(pubkey)
+ return push_script(scriptSig)
+ elif _type == 'p2wsh-p2sh':
+ if estimate_size:
+ witness_script = ''
+ else:
+ witness_script = self.get_preimage_script(txin)
+ scriptSig = bitcoin.p2wsh_nested_script(witness_script)
+ return push_script(scriptSig)
+ elif _type == 'address':
+ return 'ff00' + push_script(pubkeys[0]) # fd extended pubkey
+ elif _type == 'unknown':
+ return txin['scriptSig']
+ return script
+
+ @classmethod
+ def is_txin_complete(cls, txin):
+ if txin['type'] == 'coinbase':
+ return True
+ num_sig = txin.get('num_sig', 1)
+ if num_sig == 0:
+ return True
+ x_signatures = txin['signatures']
+ signatures = list(filter(None, x_signatures))
+ return len(signatures) == num_sig
+
+ @classmethod
+ def get_preimage_script(self, txin):
+ preimage_script = txin.get('preimage_script', None)
+ if preimage_script is not None:
+ return preimage_script
+
+ pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
+ if txin['type'] == 'p2pkh':
+ return bitcoin.address_to_script(txin['address'])
+ elif txin['type'] in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
+ return multisig_script(pubkeys, txin['num_sig'])
+ elif txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:
+ pubkey = pubkeys[0]
+ pkh = bh2u(bitcoin.hash_160(bfh(pubkey)))
+ return '76a9' + push_script(pkh) + '88ac'
+ elif txin['type'] == 'p2pk':
+ pubkey = pubkeys[0]
+ return bitcoin.public_key_to_p2pk_script(pubkey)
+ else:
+ raise TypeError('Unknown txin type', txin['type'])
+
+ @classmethod
+ def serialize_outpoint(self, txin):
+ return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex(txin['prevout_n'], 4)
+
+ @classmethod
+ def get_outpoint_from_txin(cls, txin):
+ if txin['type'] == 'coinbase':
+ return None
+ prevout_hash = txin['prevout_hash']
+ prevout_n = txin['prevout_n']
+ return prevout_hash + ':%d' % prevout_n
+
+ @classmethod
+ def serialize_input(self, txin, script):
+ # Prev hash and index
+ s = self.serialize_outpoint(txin)
+ # Script length, script, sequence
+ s += var_int(len(script)//2)
+ s += script
+ s += int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
+ return s
+
+ def set_rbf(self, rbf):
+ nSequence = 0xffffffff - (2 if rbf else 1)
+ for txin in self.inputs():
+ txin['sequence'] = nSequence
+
+ def BIP_LI01_sort(self):
+ # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki
+ self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))
+ self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
+
+ def serialize_output(self, output):
+ output_type, addr, amount = output
+ s = int_to_hex(amount, 8)
+ script = self.pay_script(output_type, addr)
+ s += var_int(len(script)//2)
+ s += script
+ return s
+
+ def serialize_preimage(self, i):
+ nVersion = int_to_hex(self.version, 4)
+ nHashType = int_to_hex(1, 4)
+ nLocktime = int_to_hex(self.locktime, 4)
+ inputs = self.inputs()
+ outputs = self.outputs()
+ txin = inputs[i]
+ # TODO: py3 hex
+ if self.is_segwit_input(txin):
+ hashPrevouts = bh2u(Hash(bfh(''.join(self.serialize_outpoint(txin) for txin in inputs))))
+ hashSequence = bh2u(Hash(bfh(''.join(int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) for txin in inputs))))
+ hashOutputs = bh2u(Hash(bfh(''.join(self.serialize_output(o) for o in outputs))))
+ outpoint = self.serialize_outpoint(txin)
+ preimage_script = self.get_preimage_script(txin)
+ scriptCode = var_int(len(preimage_script) // 2) + preimage_script
+ amount = int_to_hex(txin['value'], 8)
+ nSequence = int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
+ preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
+ else:
+ 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))
+ txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
+ preimage = nVersion + txins + txouts + nLocktime + nHashType
+ return preimage
+
+ def is_segwit(self, guess_for_address=False):
+ if not self.is_partial_originally:
+ return self._segwit_ser
+ return any(self.is_segwit_input(x, guess_for_address=guess_for_address) for x in self.inputs())
+
+ def serialize(self, estimate_size=False, witness=True):
+ network_ser = self.serialize_to_network(estimate_size, witness)
+ if estimate_size:
+ return network_ser
+ if self.is_partial_originally and not self.is_complete():
+ partial_format_version = '00'
+ return bh2u(PARTIAL_TXN_HEADER_MAGIC) + partial_format_version + network_ser
+ else:
+ return network_ser
+
+ def serialize_to_network(self, estimate_size=False, witness=True):
+ nVersion = int_to_hex(self.version, 4)
+ nLocktime = int_to_hex(self.locktime, 4)
+ inputs = self.inputs()
+ outputs = self.outputs()
+ txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)
+ txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
+ use_segwit_ser_for_estimate_size = estimate_size and self.is_segwit(guess_for_address=True)
+ use_segwit_ser_for_actual_use = not estimate_size and \
+ (self.is_segwit() or any(txin['type'] == 'address' for txin in inputs))
+ use_segwit_ser = use_segwit_ser_for_estimate_size or use_segwit_ser_for_actual_use
+ if witness and use_segwit_ser:
+ marker = '00'
+ flag = '01'
+ witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
+ return nVersion + marker + flag + txins + txouts + witness + nLocktime
+ else:
+ return nVersion + txins + txouts + nLocktime
+
+ def txid(self):
+ self.deserialize()
+ all_segwit = all(self.is_segwit_input(x) for x in self.inputs())
+ if not all_segwit and not self.is_complete():
+ return None
+ ser = self.serialize_to_network(witness=False)
+ return bh2u(Hash(bfh(ser))[::-1])
+
+ def wtxid(self):
+ self.deserialize()
+ if not self.is_complete():
+ return None
+ ser = self.serialize_to_network(witness=True)
+ return bh2u(Hash(bfh(ser))[::-1])
+
+ def add_inputs(self, inputs):
+ self._inputs.extend(inputs)
+ self.raw = None
+
+ def add_outputs(self, outputs):
+ self._outputs.extend(outputs)
+ self.raw = None
+
+ def input_value(self):
+ return sum(x['value'] for x in self.inputs())
+
+ def output_value(self):
+ return sum(val for tp, addr, val in self.outputs())
+
+ def get_fee(self):
+ return self.input_value() - self.output_value()
+
+ def is_final(self):
+ return not any([x.get('sequence', 0xffffffff - 1) < 0xffffffff - 1 for x in self.inputs()])
+
+ @profiler
+ def estimated_size(self):
+ """Return an estimated virtual tx size in vbytes.
+ BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up.
+ This definition is only for humans, and has little meaning otherwise.
+ If we wanted sub-byte precision, fee calculation should use transaction
+ weights, but for simplicity we approximate that with (virtual_size)x4
+ """
+ weight = self.estimated_weight()
+ return self.virtual_size_from_weight(weight)
+
+ @classmethod
+ def estimated_input_weight(cls, txin, is_segwit_tx):
+ '''Return an estimate of serialized input weight in weight units.'''
+ script = cls.input_script(txin, True)
+ input_size = len(cls.serialize_input(txin, script)) // 2
+
+ if cls.is_segwit_input(txin, guess_for_address=True):
+ witness_size = len(cls.serialize_witness(txin, True)) // 2
+ else:
+ witness_size = 1 if is_segwit_tx else 0
+
+ return 4 * input_size + witness_size
+
+ @classmethod
+ def estimated_output_size(cls, address):
+ """Return an estimate of serialized output size in bytes."""
+ script = bitcoin.address_to_script(address)
+ # 8 byte value + 1 byte script len + script
+ return 9 + len(script) // 2
+
+ @classmethod
+ def virtual_size_from_weight(cls, weight):
+ return weight // 4 + (weight % 4 > 0)
+
+ def estimated_total_size(self):
+ """Return an estimated total transaction size in bytes."""
+ return len(self.serialize(True)) // 2 if not self.is_complete() or self.raw is None else len(self.raw) // 2 # ASCII hex string
+
+ def estimated_witness_size(self):
+ """Return an estimate of witness size in bytes."""
+ estimate = not self.is_complete()
+ if not self.is_segwit(guess_for_address=estimate):
+ return 0
+ inputs = self.inputs()
+ witness = ''.join(self.serialize_witness(x, estimate) for x in inputs)
+ witness_size = len(witness) // 2 + 2 # include marker and flag
+ return witness_size
+
+ def estimated_base_size(self):
+ """Return an estimated base transaction size in bytes."""
+ return self.estimated_total_size() - self.estimated_witness_size()
+
+ def estimated_weight(self):
+ """Return an estimate of transaction weight."""
+ total_tx_size = self.estimated_total_size()
+ base_tx_size = self.estimated_base_size()
+ return 3 * base_tx_size + total_tx_size
+
+ def signature_count(self):
+ r = 0
+ s = 0
+ for txin in self.inputs():
+ if txin['type'] == 'coinbase':
+ continue
+ signatures = list(filter(None, txin.get('signatures',[])))
+ s += len(signatures)
+ r += txin.get('num_sig',-1)
+ return s, r
+
+ def is_complete(self):
+ if not self.is_partial_originally:
+ return True
+ s, r = self.signature_count()
+ return r == s
+
+ def sign(self, keypairs) -> None:
+ # keypairs: (x_)pubkey -> secret_bytes
+ for i, txin in enumerate(self.inputs()):
+ pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
+ for j, (pubkey, x_pubkey) in enumerate(zip(pubkeys, x_pubkeys)):
+ if self.is_txin_complete(txin):
+ break
+ if pubkey in keypairs:
+ _pubkey = pubkey
+ elif x_pubkey in keypairs:
+ _pubkey = x_pubkey
+ else:
+ continue
+ print_error("adding signature for", _pubkey)
+ sec, compressed = keypairs.get(_pubkey)
+ sig = self.sign_txin(i, sec)
+ self.add_signature_to_txin(i, j, sig)
+
+ print_error("is_complete", self.is_complete())
+ self.raw = self.serialize()
+
+ def sign_txin(self, txin_index, privkey_bytes) -> str:
+ pre_hash = Hash(bfh(self.serialize_preimage(txin_index)))
+ privkey = ecc.ECPrivkey(privkey_bytes)
+ sig = privkey.sign_transaction(pre_hash)
+ sig = bh2u(sig) + '01'
+ return sig
+
+ def get_outputs(self):
+ """convert pubkeys to addresses"""
+ outputs = []
+ for o in self.outputs():
+ if o.type == TYPE_ADDRESS:
+ addr = o.address
+ elif o.type == TYPE_PUBKEY:
+ # TODO do we really want this conversion? it's not really that address after all
+ addr = bitcoin.public_key_to_p2pkh(bfh(o.address))
+ else:
+ addr = 'SCRIPT ' + o.address
+ outputs.append((addr, o.value)) # consider using yield (addr, v)
+ return outputs
+
+ def get_output_addresses(self):
+ return [addr for addr, val in self.get_outputs()]
+
+
+ def has_address(self, addr):
+ return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs()))
+
+ def as_dict(self):
+ if self.raw is None:
+ self.raw = self.serialize()
+ self.deserialize()
+ out = {
+ 'hex': self.raw,
+ 'complete': self.is_complete(),
+ 'final': self.is_final(),
+ }
+ return out
+
+
+def tx_from_str(txt):
+ "json or raw hexadecimal"
+ import json
+ txt = txt.strip()
+ if not txt:
+ raise ValueError("empty string")
+ try:
+ bfh(txt)
+ is_hex = True
+ except:
+ is_hex = False
+ if is_hex:
+ return txt
+ tx_dict = json.loads(str(txt))
+ assert "hex" in tx_dict.keys()
+ return tx_dict["hex"]
diff --git a/electrum/util.py b/electrum/util.py
new file mode 100644
index 000000000..d5d45dd4f
--- /dev/null
+++ b/electrum/util.py
@@ -0,0 +1,912 @@
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import binascii
+import os, sys, re, json
+from collections import defaultdict
+from typing import NamedTuple
+from datetime import datetime
+import decimal
+from decimal import Decimal
+import traceback
+import urllib
+import threading
+import hmac
+import stat
+import inspect
+from locale import localeconv
+
+from .i18n import _
+
+
+import urllib.request, urllib.parse, urllib.error
+import queue
+
+def inv_dict(d):
+ return {v: k for k, v in d.items()}
+
+
+base_units = {'BTX':8, 'mBTX':5, 'bits':2, 'sat':0}
+base_units_inverse = inv_dict(base_units)
+base_units_list = ['BTX', 'mBTX', 'bits', 'sat'] # list(dict) does not guarantee order
+
+
+def decimal_point_to_base_unit_name(dp: int) -> str:
+ # e.g. 8 -> "BTX"
+ try:
+ return base_units_inverse[dp]
+ except KeyError:
+ raise Exception('Unknown base unit')
+
+
+def base_unit_name_to_decimal_point(unit_name: str) -> int:
+ # e.g. "BTX" -> 8
+ try:
+ return base_units[unit_name]
+ except KeyError:
+ raise Exception('Unknown base unit')
+
+
+def normalize_version(v):
+ return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
+
+class NotEnoughFunds(Exception): pass
+
+
+class NoDynamicFeeEstimates(Exception):
+ def __str__(self):
+ return _('Dynamic fee estimates not available')
+
+
+class InvalidPassword(Exception):
+ def __str__(self):
+ return _("Incorrect password")
+
+
+class FileImportFailed(Exception):
+ def __init__(self, message=''):
+ self.message = str(message)
+
+ def __str__(self):
+ return _("Failed to import from file.") + "\n" + self.message
+
+
+class FileExportFailed(Exception):
+ def __init__(self, message=''):
+ self.message = str(message)
+
+ def __str__(self):
+ return _("Failed to export to file.") + "\n" + self.message
+
+
+class TimeoutException(Exception):
+ def __init__(self, message=''):
+ self.message = str(message)
+
+ def __str__(self):
+ if not self.message:
+ return _("Operation timed out.")
+ return self.message
+
+
+class WalletFileException(Exception): pass
+
+
+class BitcoinException(Exception): pass
+
+
+# Throw this exception to unwind the stack like when an error occurs.
+# However unlike other exceptions the user won't be informed.
+class UserCancelled(Exception):
+ '''An exception that is suppressed from the user'''
+ pass
+
+class Satoshis(object):
+ __slots__ = ('value',)
+
+ def __new__(cls, value):
+ self = super(Satoshis, cls).__new__(cls)
+ self.value = value
+ return self
+
+ def __repr__(self):
+ return 'Satoshis(%d)'%self.value
+
+ def __str__(self):
+ return format_satoshis(self.value) + " BTX"
+
+class Fiat(object):
+ __slots__ = ('value', 'ccy')
+
+ def __new__(cls, value, ccy):
+ self = super(Fiat, cls).__new__(cls)
+ self.ccy = ccy
+ self.value = value
+ return self
+
+ def __repr__(self):
+ return 'Fiat(%s)'% self.__str__()
+
+ def __str__(self):
+ if self.value.is_nan():
+ return _('No Data')
+ else:
+ return "{:.2f}".format(self.value) + ' ' + self.ccy
+
+class MyEncoder(json.JSONEncoder):
+ def default(self, obj):
+ from .transaction import Transaction
+ if isinstance(obj, Transaction):
+ return obj.as_dict()
+ if isinstance(obj, Satoshis):
+ return str(obj)
+ if isinstance(obj, Fiat):
+ return str(obj)
+ if isinstance(obj, Decimal):
+ return str(obj)
+ if isinstance(obj, datetime):
+ return obj.isoformat(' ')[:-3]
+ if isinstance(obj, set):
+ return list(obj)
+ return super(MyEncoder, self).default(obj)
+
+class PrintError(object):
+ '''A handy base class'''
+ verbosity_filter = ''
+
+ def diagnostic_name(self):
+ return self.__class__.__name__
+
+ def print_error(self, *msg):
+ if self.verbosity_filter in verbosity or verbosity == '*':
+ print_error("[%s]" % self.diagnostic_name(), *msg)
+
+ def print_stderr(self, *msg):
+ print_stderr("[%s]" % self.diagnostic_name(), *msg)
+
+ def print_msg(self, *msg):
+ print_msg("[%s]" % self.diagnostic_name(), *msg)
+
+class ThreadJob(PrintError):
+ """A job that is run periodically from a thread's main loop. run() is
+ called from that thread's context.
+ """
+
+ def run(self):
+ """Called periodically from the thread"""
+ pass
+
+class DebugMem(ThreadJob):
+ '''A handy class for debugging GC memory leaks'''
+ def __init__(self, classes, interval=30):
+ self.next_time = 0
+ self.classes = classes
+ self.interval = interval
+
+ def mem_stats(self):
+ import gc
+ self.print_error("Start memscan")
+ gc.collect()
+ objmap = defaultdict(list)
+ for obj in gc.get_objects():
+ for class_ in self.classes:
+ if isinstance(obj, class_):
+ objmap[class_].append(obj)
+ for class_, objs in objmap.items():
+ self.print_error("%s: %d" % (class_.__name__, len(objs)))
+ self.print_error("Finish memscan")
+
+ def run(self):
+ if time.time() > self.next_time:
+ self.mem_stats()
+ self.next_time = time.time() + self.interval
+
+class DaemonThread(threading.Thread, PrintError):
+ """ daemon thread that terminates cleanly """
+ verbosity_filter = 'd'
+
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self.parent_thread = threading.currentThread()
+ self.running = False
+ self.running_lock = threading.Lock()
+ self.job_lock = threading.Lock()
+ self.jobs = []
+
+ def add_jobs(self, jobs):
+ with self.job_lock:
+ self.jobs.extend(jobs)
+
+ def run_jobs(self):
+ # Don't let a throwing job disrupt the thread, future runs of
+ # itself, or other jobs. This is useful protection against
+ # malformed or malicious server responses
+ with self.job_lock:
+ for job in self.jobs:
+ try:
+ job.run()
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+
+ def remove_jobs(self, jobs):
+ with self.job_lock:
+ for job in jobs:
+ self.jobs.remove(job)
+
+ def start(self):
+ with self.running_lock:
+ self.running = True
+ return threading.Thread.start(self)
+
+ def is_running(self):
+ with self.running_lock:
+ return self.running and self.parent_thread.is_alive()
+
+ def stop(self):
+ with self.running_lock:
+ self.running = False
+
+ def on_stop(self):
+ if 'ANDROID_DATA' in os.environ:
+ import jnius
+ jnius.detach()
+ self.print_error("jnius detach")
+ self.print_error("stopped")
+
+
+verbosity = '*'
+def set_verbosity(b):
+ global verbosity
+ verbosity = b
+
+
+def print_error(*args):
+ if not verbosity: return
+ print_stderr(*args)
+
+def print_stderr(*args):
+ args = [str(item) for item in args]
+ sys.stderr.write(" ".join(args) + "\n")
+ sys.stderr.flush()
+
+def print_msg(*args):
+ # Stringify args
+ args = [str(item) for item in args]
+ sys.stdout.write(" ".join(args) + "\n")
+ sys.stdout.flush()
+
+def json_encode(obj):
+ try:
+ s = json.dumps(obj, sort_keys = True, indent = 4, cls=MyEncoder)
+ except TypeError:
+ s = repr(obj)
+ return s
+
+def json_decode(x):
+ try:
+ return json.loads(x, parse_float=Decimal)
+ except:
+ return x
+
+
+# taken from Django Source Code
+def constant_time_compare(val1, val2):
+ """Return True if the two strings are equal, False otherwise."""
+ return hmac.compare_digest(to_bytes(val1, 'utf8'), to_bytes(val2, 'utf8'))
+
+
+# decorator that prints execution time
+def profiler(func):
+ def get_func_name(args):
+ arg_names_from_sig = inspect.getfullargspec(func).args
+ # prepend class name if there is one (and if we can find it)
+ if len(arg_names_from_sig) > 0 and len(args) > 0 \
+ and arg_names_from_sig[0] in ('self', 'cls', 'klass'):
+ classname = args[0].__class__.__name__
+ else:
+ classname = ''
+ name = '{}.{}'.format(classname, func.__name__) if classname else func.__name__
+ return name
+ def do_profile(args, kw_args):
+ name = get_func_name(args)
+ t0 = time.time()
+ o = func(*args, **kw_args)
+ t = time.time() - t0
+ print_error("[profiler]", name, "%.4f"%t)
+ return o
+ return lambda *args, **kw_args: do_profile(args, kw_args)
+
+
+def android_ext_dir():
+ import jnius
+ env = jnius.autoclass('android.os.Environment')
+ return env.getExternalStorageDirectory().getPath()
+
+def android_data_dir():
+ import jnius
+ PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity')
+ return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
+
+def android_headers_dir():
+ d = android_ext_dir() + '/org.electrum-btx.electrum-btx'
+ if not os.path.exists(d):
+ try:
+ os.mkdir(d)
+ except FileExistsError:
+ pass # in case of race
+ return d
+
+def android_check_data_dir():
+ """ if needed, move old directory to sandbox """
+ ext_dir = android_ext_dir()
+ data_dir = android_data_dir()
+ old_electrum_dir = ext_dir + '/electrum-btx'
+ if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir):
+ import shutil
+ new_headers_path = android_headers_dir() + '/blockchain_headers'
+ old_headers_path = old_electrum_dir + '/blockchain_headers'
+ if not os.path.exists(new_headers_path) and os.path.exists(old_headers_path):
+ print_error("Moving headers file to", new_headers_path)
+ shutil.move(old_headers_path, new_headers_path)
+ print_error("Moving data to", data_dir)
+ shutil.move(old_electrum_dir, data_dir)
+ return data_dir
+
+
+def get_headers_dir(config):
+ return android_headers_dir() if 'ANDROID_DATA' in os.environ else config.path
+
+
+def assert_datadir_available(config_path):
+ path = config_path
+ if os.path.exists(path):
+ return
+ else:
+ raise FileNotFoundError(
+ 'Electrum datadir does not exist. Was it deleted while running?' + '\n' +
+ 'Should be at {}'.format(path))
+
+
+def assert_file_in_datadir_available(path, config_path):
+ if os.path.exists(path):
+ return
+ else:
+ assert_datadir_available(config_path)
+ raise FileNotFoundError(
+ 'Cannot find file but datadir is there.' + '\n' +
+ 'Should be at {}'.format(path))
+
+
+def assert_bytes(*args):
+ """
+ porting helper, assert args type
+ """
+ try:
+ for x in args:
+ assert isinstance(x, (bytes, bytearray))
+ except:
+ print('assert bytes failed', list(map(type, args)))
+ raise
+
+
+def assert_str(*args):
+ """
+ porting helper, assert args type
+ """
+ for x in args:
+ assert isinstance(x, str)
+
+
+
+def to_string(x, enc):
+ if isinstance(x, (bytes, bytearray)):
+ return x.decode(enc)
+ if isinstance(x, str):
+ return x
+ else:
+ raise TypeError("Not a string or bytes like object")
+
+def to_bytes(something, encoding='utf8'):
+ """
+ cast string to bytes() like object, but for python2 support it's bytearray copy
+ """
+ if isinstance(something, bytes):
+ return something
+ if isinstance(something, str):
+ return something.encode(encoding)
+ elif isinstance(something, bytearray):
+ return bytes(something)
+ else:
+ raise TypeError("Not a string or bytes like object")
+
+
+bfh = bytes.fromhex
+hfu = binascii.hexlify
+
+
+def bh2u(x):
+ """
+ str with hex representation of a bytes-like object
+
+ >>> x = bytes((1, 2, 10))
+ >>> bh2u(x)
+ '01020A'
+
+ :param x: bytes
+ :rtype: str
+ """
+ return hfu(x).decode('ascii')
+
+
+def user_dir():
+ if 'ANDROID_DATA' in os.environ:
+ return android_check_data_dir()
+ elif os.name == 'posix':
+ return os.path.join(os.environ["HOME"], ".electrum-btx")
+ elif "APPDATA" in os.environ:
+ return os.path.join(os.environ["APPDATA"], "Electrum-BTX")
+ elif "LOCALAPPDATA" in os.environ:
+ return os.path.join(os.environ["LOCALAPPDATA"], "Electrum-BTX")
+ else:
+ #raise Exception("No home directory found in environment variables.")
+ return
+
+def is_valid_email(s):
+ regexp = r"[^@]+@[^@]+\.[^@]+"
+ return re.match(regexp, s) is not None
+
+
+def format_satoshis_plain(x, decimal_point = 8):
+ """Display a satoshi amount scaled. Always uses a '.' as a decimal
+ point and has no thousands separator"""
+ scale_factor = pow(10, decimal_point)
+ return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
+
+
+DECIMAL_POINT = localeconv()['decimal_point']
+
+
+def format_satoshis(x, num_zeros=0, decimal_point=8, precision=None, is_diff=False, whitespaces=False):
+ if x is None:
+ return 'unknown'
+ if precision is None:
+ precision = decimal_point
+ decimal_format = ".0" + str(precision) if precision > 0 else ""
+ if is_diff:
+ decimal_format = '+' + decimal_format
+ result = ("{:" + decimal_format + "f}").format(x / pow (10, decimal_point)).rstrip('0')
+ integer_part, fract_part = result.split(".")
+ dp = DECIMAL_POINT
+ if len(fract_part) < num_zeros:
+ fract_part += "0" * (num_zeros - len(fract_part))
+ result = integer_part + dp + fract_part
+ if whitespaces:
+ result += " " * (decimal_point - len(fract_part))
+ result = " " * (15 - len(result)) + result
+ return result
+
+
+FEERATE_PRECISION = 1 # num fractional decimal places for sat/byte fee rates
+_feerate_quanta = Decimal(10) ** (-FEERATE_PRECISION)
+
+
+def format_fee_satoshis(fee, num_zeros=0):
+ return format_satoshis(fee, num_zeros, 0, precision=FEERATE_PRECISION)
+
+
+def quantize_feerate(fee):
+ """Strip sat/byte fee rate of excess precision."""
+ if fee is None:
+ return None
+ return Decimal(fee).quantize(_feerate_quanta, rounding=decimal.ROUND_HALF_DOWN)
+
+
+def timestamp_to_datetime(timestamp):
+ if timestamp is None:
+ return None
+ return datetime.fromtimestamp(timestamp)
+
+def format_time(timestamp):
+ date = timestamp_to_datetime(timestamp)
+ return date.isoformat(' ')[:-3] if date else _("Unknown")
+
+
+# Takes a timestamp and returns a string with the approximation of the age
+def age(from_date, since_date = None, target_tz=None, include_seconds=False):
+ if from_date is None:
+ return "Unknown"
+
+ from_date = datetime.fromtimestamp(from_date)
+ if since_date is None:
+ since_date = datetime.now(target_tz)
+
+ td = time_difference(from_date - since_date, include_seconds)
+ return td + " ago" if from_date < since_date else "in " + td
+
+
+def time_difference(distance_in_time, include_seconds):
+ #distance_in_time = since_date - from_date
+ distance_in_seconds = int(round(abs(distance_in_time.days * 86400 + distance_in_time.seconds)))
+ distance_in_minutes = int(round(distance_in_seconds/60))
+
+ if distance_in_minutes <= 1:
+ if include_seconds:
+ for remainder in [5, 10, 20]:
+ if distance_in_seconds < remainder:
+ return "less than %s seconds" % remainder
+ if distance_in_seconds < 40:
+ return "half a minute"
+ elif distance_in_seconds < 60:
+ return "less than a minute"
+ else:
+ return "1 minute"
+ else:
+ if distance_in_minutes == 0:
+ return "less than a minute"
+ else:
+ return "1 minute"
+ elif distance_in_minutes < 45:
+ return "%s minutes" % distance_in_minutes
+ elif distance_in_minutes < 90:
+ return "about 1 hour"
+ elif distance_in_minutes < 1440:
+ return "about %d hours" % (round(distance_in_minutes / 60.0))
+ elif distance_in_minutes < 2880:
+ return "1 day"
+ elif distance_in_minutes < 43220:
+ return "%d days" % (round(distance_in_minutes / 1440))
+ elif distance_in_minutes < 86400:
+ return "about 1 month"
+ elif distance_in_minutes < 525600:
+ return "%d months" % (round(distance_in_minutes / 43200))
+ elif distance_in_minutes < 1051200:
+ return "about 1 year"
+ else:
+ return "over %d years" % (round(distance_in_minutes / 525600))
+
+mainnet_block_explorers = {
+ 'bitcore.cc': ('https://insight.bitcore.cc/',
+ {'tx': 'tx/', 'addr': 'address/'}),
+ 'cryptoID': ('https://chainz.cryptoid.info/',
+ {'tx': 'btx/tx.dws?', 'addr': 'address.dws?'}),
+ 'system default': ('https://insight.bitcore.cc/',
+ {'tx': 'tx/', 'addr': 'address/'}),
+}
+
+testnet_block_explorers = {
+ 'Blocktrail.com': ('https://www.blocktrail.com/tBTC/',
+ {'tx': 'tx/', 'addr': 'address/'}),
+ 'system default': ('blockchain://000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943/',
+ {'tx': 'tx/', 'addr': 'address/'}),
+}
+
+def block_explorer_info():
+ from . import constants
+ return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers
+
+def block_explorer(config):
+ return config.get('block_explorer', 'system default')
+
+def block_explorer_tuple(config):
+ return block_explorer_info().get(block_explorer(config))
+
+def block_explorer_URL(config, kind, item):
+ be_tuple = block_explorer_tuple(config)
+ if not be_tuple:
+ return
+ kind_str = be_tuple[1].get(kind)
+ if not kind_str:
+ return
+ url_parts = [be_tuple[0], kind_str, item]
+ return ''.join(url_parts)
+
+# URL decode
+#_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
+#urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
+
+def parse_URI(uri, on_pr=None):
+ from . import bitcoin
+ from .bitcoin import COIN
+
+ if ':' not in uri:
+ if not bitcoin.is_address(uri):
+ raise Exception("Not a bitcore address")
+ return {'address': uri}
+
+ u = urllib.parse.urlparse(uri)
+ if u.scheme != 'bitcore':
+ raise Exception("Not a bitcore URI")
+ address = u.path
+
+ # python for android fails to parse query
+ if address.find('?') > 0:
+ address, query = u.path.split('?')
+ pq = urllib.parse.parse_qs(query)
+ else:
+ pq = urllib.parse.parse_qs(u.query)
+
+ for k, v in pq.items():
+ if len(v)!=1:
+ raise Exception('Duplicate Key', k)
+
+ out = {k: v[0] for k, v in pq.items()}
+ if address:
+ if not bitcoin.is_address(address):
+ raise Exception("Invalid bitcore address:" + address)
+ out['address'] = address
+ if 'amount' in out:
+ am = out['amount']
+ m = re.match('([0-9\.]+)X([0-9])', am)
+ if m:
+ k = int(m.group(2)) - 8
+ amount = Decimal(m.group(1)) * pow( Decimal(10) , k)
+ else:
+ amount = Decimal(am) * COIN
+ out['amount'] = int(amount)
+ if 'message' in out:
+ out['message'] = out['message']
+ out['memo'] = out['message']
+ if 'time' in out:
+ out['time'] = int(out['time'])
+ if 'exp' in out:
+ out['exp'] = int(out['exp'])
+ if 'sig' in out:
+ out['sig'] = bh2u(bitcoin.base_decode(out['sig'], None, base=58))
+
+ r = out.get('r')
+ sig = out.get('sig')
+ name = out.get('name')
+ if on_pr and (r or (name and sig)):
+ def get_payment_request_thread():
+ from . import paymentrequest as pr
+ if name and sig:
+ s = pr.serialize_request(out).SerializeToString()
+ request = pr.PaymentRequest(s)
+ else:
+ request = pr.get_payment_request(r)
+ if on_pr:
+ on_pr(request)
+ t = threading.Thread(target=get_payment_request_thread)
+ t.setDaemon(True)
+ t.start()
+
+ return out
+
+
+def create_URI(addr, amount, message):
+ from . import bitcoin
+ if not bitcoin.is_address(addr):
+ return ""
+ query = []
+ if amount:
+ query.append('amount=%s'%format_satoshis_plain(amount))
+ if message:
+ query.append('message=%s'%urllib.parse.quote(message))
+ p = urllib.parse.ParseResult(scheme='bitcore', netloc='', path=addr, params='', query='&'.join(query), fragment='')
+ return urllib.parse.urlunparse(p)
+
+
+# Python bug (http://bugs.python.org/issue1927) causes raw_input
+# to be redirected improperly between stdin/stderr on Unix systems
+#TODO: py3
+def raw_input(prompt=None):
+ if prompt:
+ sys.stdout.write(prompt)
+ return builtin_raw_input()
+
+import builtins
+builtin_raw_input = builtins.input
+builtins.input = raw_input
+
+
+def parse_json(message):
+ # TODO: check \r\n pattern
+ n = message.find(b'\n')
+ if n==-1:
+ return None, message
+ try:
+ j = json.loads(message[0:n].decode('utf8'))
+ except:
+ j = None
+ return j, message[n+1:]
+
+
+class timeout(Exception):
+ pass
+
+import socket
+import json
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+
+import time
+
+
+class SocketPipe:
+ def __init__(self, socket):
+ self.socket = socket
+ self.message = b''
+ self.set_timeout(0.1)
+ self.recv_time = time.time()
+
+ def set_timeout(self, t):
+ self.socket.settimeout(t)
+
+ def idle_time(self):
+ return time.time() - self.recv_time
+
+ def get(self):
+ while True:
+ response, self.message = parse_json(self.message)
+ if response is not None:
+ return response
+ try:
+ data = self.socket.recv(1024)
+ except socket.timeout:
+ raise timeout
+ except ssl.SSLError:
+ raise timeout
+ except socket.error as err:
+ if err.errno == 60:
+ raise timeout
+ elif err.errno in [11, 35, 10035]:
+ print_error("socket errno %d (resource temporarily unavailable)"% err.errno)
+ time.sleep(0.2)
+ raise timeout
+ else:
+ print_error("pipe: socket error", err)
+ data = b''
+ except:
+ traceback.print_exc(file=sys.stderr)
+ data = b''
+
+ if not data: # Connection closed remotely
+ return None
+ self.message += data
+ self.recv_time = time.time()
+
+ def send(self, request):
+ out = json.dumps(request) + '\n'
+ out = out.encode('utf8')
+ self._send(out)
+
+ def send_all(self, requests):
+ out = b''.join(map(lambda x: (json.dumps(x) + '\n').encode('utf8'), requests))
+ self._send(out)
+
+ def _send(self, out):
+ while out:
+ try:
+ sent = self.socket.send(out)
+ out = out[sent:]
+ except ssl.SSLError as e:
+ print_error("SSLError:", e)
+ time.sleep(0.1)
+ continue
+
+
+class QueuePipe:
+
+ def __init__(self, send_queue=None, get_queue=None):
+ self.send_queue = send_queue if send_queue else queue.Queue()
+ self.get_queue = get_queue if get_queue else queue.Queue()
+ self.set_timeout(0.1)
+
+ def get(self):
+ try:
+ return self.get_queue.get(timeout=self.timeout)
+ except queue.Empty:
+ raise timeout
+
+ def get_all(self):
+ responses = []
+ while True:
+ try:
+ r = self.get_queue.get_nowait()
+ responses.append(r)
+ except queue.Empty:
+ break
+ return responses
+
+ def set_timeout(self, t):
+ self.timeout = t
+
+ def send(self, request):
+ self.send_queue.put(request)
+
+ def send_all(self, requests):
+ for request in requests:
+ self.send(request)
+
+
+
+
+def setup_thread_excepthook():
+ """
+ Workaround for `sys.excepthook` thread bug from:
+ http://bugs.python.org/issue1230540
+
+ Call once from the main thread before creating any threads.
+ """
+
+ init_original = threading.Thread.__init__
+
+ def init(self, *args, **kwargs):
+
+ init_original(self, *args, **kwargs)
+ run_original = self.run
+
+ def run_with_except_hook(*args2, **kwargs2):
+ try:
+ run_original(*args2, **kwargs2)
+ except Exception:
+ sys.excepthook(*sys.exc_info())
+
+ self.run = run_with_except_hook
+
+ threading.Thread.__init__ = init
+
+
+def versiontuple(v):
+ return tuple(map(int, (v.split("."))))
+
+
+def import_meta(path, validater, load_meta):
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ d = validater(json.loads(f.read()))
+ load_meta(d)
+ #backwards compatibility for JSONDecodeError
+ except ValueError:
+ traceback.print_exc(file=sys.stderr)
+ raise FileImportFailed(_("Invalid JSON code."))
+ except BaseException as e:
+ traceback.print_exc(file=sys.stdout)
+ raise FileImportFailed(e)
+
+
+def export_meta(meta, fileName):
+ try:
+ with open(fileName, 'w+', encoding='utf-8') as f:
+ json.dump(meta, f, indent=4, sort_keys=True)
+ except (IOError, os.error) as e:
+ traceback.print_exc(file=sys.stderr)
+ raise FileExportFailed(e)
+
+
+def make_dir(path, allow_symlink=True):
+ """Make directory if it does not yet exist."""
+ if not os.path.exists(path):
+ if not allow_symlink and os.path.islink(path):
+ raise Exception('Dangling link: ' + path)
+ os.mkdir(path)
+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+
+
+TxMinedStatus = NamedTuple("TxMinedStatus", [("height", int),
+ ("conf", int),
+ ("timestamp", int),
+ ("header_hash", str)])
+VerifiedTxInfo = NamedTuple("VerifiedTxInfo", [("height", int),
+ ("timestamp", int),
+ ("txpos", int),
+ ("header_hash", str)])
diff --git a/electrum/verifier.py b/electrum/verifier.py
new file mode 100644
index 000000000..4a0d82ec4
--- /dev/null
+++ b/electrum/verifier.py
@@ -0,0 +1,175 @@
+# Electrum - Lightweight Bitcoin Client
+# Copyright (c) 2012 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from typing import Sequence, Optional
+
+from .util import ThreadJob, bh2u, VerifiedTxInfo
+from .bitcoin import Hash, hash_decode, hash_encode
+from .transaction import Transaction
+from .blockchain import hash_header
+
+
+class MerkleVerificationFailure(Exception): pass
+class MissingBlockHeader(MerkleVerificationFailure): pass
+class MerkleRootMismatch(MerkleVerificationFailure): pass
+class InnerNodeOfSpvProofIsValidTx(MerkleVerificationFailure): pass
+
+
+class SPV(ThreadJob):
+ """ Simple Payment Verification """
+
+ def __init__(self, network, wallet):
+ self.wallet = wallet
+ self.network = network
+ self.blockchain = network.blockchain()
+ self.merkle_roots = {} # txid -> merkle root (once it has been verified)
+ self.requested_merkle = set() # txid set of pending requests
+
+ def run(self):
+ interface = self.network.interface
+ if not interface:
+ return
+
+ blockchain = interface.blockchain
+ if not blockchain:
+ return
+
+ local_height = self.network.get_local_height()
+ unverified = self.wallet.get_unverified_txs()
+ for tx_hash, tx_height in unverified.items():
+ # do not request merkle branch before headers are available
+ if tx_height <= 0 or tx_height > local_height:
+ continue
+
+ header = blockchain.read_header(tx_height)
+ if header is None:
+ index = tx_height // 2016
+ if index < len(blockchain.checkpoints):
+ self.network.request_chunk(interface, index)
+ elif (tx_hash not in self.requested_merkle
+ and tx_hash not in self.merkle_roots):
+ self.network.get_merkle_for_transaction(
+ tx_hash,
+ tx_height,
+ self.verify_merkle)
+ self.print_error('requested merkle', tx_hash)
+ self.requested_merkle.add(tx_hash)
+
+ if self.network.blockchain() != self.blockchain:
+ self.blockchain = self.network.blockchain()
+ self.undo_verifications()
+
+ def verify_merkle(self, response):
+ if self.wallet.verifier is None:
+ return # we have been killed, this was just an orphan callback
+ if response.get('error'):
+ self.print_error('received an error:', response)
+ return
+ params = response['params']
+ merkle = response['result']
+ # Verify the hash of the server-provided merkle branch to a
+ # transaction matches the merkle root of its block
+ tx_hash = params[0]
+ tx_height = merkle.get('block_height')
+ pos = merkle.get('pos')
+ merkle_branch = merkle.get('merkle')
+ header = self.network.blockchain().read_header(tx_height)
+ try:
+ verify_tx_is_in_block(tx_hash, merkle_branch, pos, header, tx_height)
+ except MerkleVerificationFailure as e:
+ self.print_error(str(e))
+ # FIXME: we should make a fresh connection to a server
+ # to recover from this, as this TX will now never verify
+ return
+ # we passed all the tests
+ self.merkle_roots[tx_hash] = header.get('merkle_root')
+ try:
+ # note: we could pop in the beginning, but then we would request
+ # this proof again in case of verification failure from the same server
+ self.requested_merkle.remove(tx_hash)
+ except KeyError: pass
+ self.print_error("verified %s" % tx_hash)
+ header_hash = hash_header(header)
+ vtx_info = VerifiedTxInfo(tx_height, header.get('timestamp'), pos, header_hash)
+ self.wallet.add_verified_tx(tx_hash, vtx_info)
+ if self.is_up_to_date() and self.wallet.is_up_to_date():
+ self.wallet.save_verified_tx(write=True)
+
+ @classmethod
+ def hash_merkle_root(cls, merkle_branch: Sequence[str], tx_hash: str, leaf_pos_in_tree: int):
+ """Return calculated merkle root."""
+ try:
+ h = hash_decode(tx_hash)
+ merkle_branch_bytes = [hash_decode(item) for item in merkle_branch]
+ int(leaf_pos_in_tree) # raise if invalid
+ except Exception as e:
+ raise MerkleVerificationFailure(e)
+
+ for i, item in enumerate(merkle_branch_bytes):
+ h = Hash(item + h) if ((leaf_pos_in_tree >> i) & 1) else Hash(h + item)
+ cls._raise_if_valid_tx(bh2u(h))
+ return hash_encode(h)
+
+ @classmethod
+ def _raise_if_valid_tx(cls, raw_tx: str):
+ # If an inner node of the merkle proof is also a valid tx, chances are, this is an attack.
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-June/016105.html
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20180609/9f4f5b1f/attachment-0001.pdf
+ # https://bitcoin.stackexchange.com/questions/76121/how-is-the-leaf-node-weakness-in-merkle-trees-exploitable/76122#76122
+ tx = Transaction(raw_tx)
+ try:
+ tx.deserialize()
+ except:
+ pass
+ else:
+ raise InnerNodeOfSpvProofIsValidTx()
+
+ def undo_verifications(self):
+ height = self.blockchain.get_forkpoint()
+ tx_hashes = self.wallet.undo_verifications(self.blockchain, height)
+ for tx_hash in tx_hashes:
+ self.print_error("redoing", tx_hash)
+ self.remove_spv_proof_for_tx(tx_hash)
+
+ def remove_spv_proof_for_tx(self, tx_hash):
+ self.merkle_roots.pop(tx_hash, None)
+ try:
+ self.requested_merkle.remove(tx_hash)
+ except KeyError:
+ pass
+
+ def is_up_to_date(self):
+ return not self.requested_merkle
+
+
+def verify_tx_is_in_block(tx_hash: str, merkle_branch: Sequence[str],
+ leaf_pos_in_tree: int, block_header: Optional[dict],
+ block_height: int) -> None:
+ """Raise MerkleVerificationFailure if verification fails."""
+ if not block_header:
+ raise MissingBlockHeader("merkle verification failed for {} (missing header {})"
+ .format(tx_hash, block_height))
+ calc_merkle_root = SPV.hash_merkle_root(merkle_branch, tx_hash, leaf_pos_in_tree)
+ if block_header.get('merkle_root') != calc_merkle_root:
+ raise MerkleRootMismatch("merkle verification failed for {} ({} != {})".format(
+ tx_hash, block_header.get('merkle_root'), calc_merkle_root))
diff --git a/electrum/version.py b/electrum/version.py
new file mode 100644
index 000000000..5030fa5f5
--- /dev/null
+++ b/electrum/version.py
@@ -0,0 +1,18 @@
+ELECTRUM_VERSION = '3.2.3' # version of the client package
+APK_VERSION = '3.2.3.0' # read by buildozer.spec
+
+PROTOCOL_VERSION = '1.2' # protocol version requested
+
+# The hash of the mnemonic seed must begin with this
+SEED_PREFIX = '01' # Standard wallet
+SEED_PREFIX_2FA = '101' # Two-factor authentication
+SEED_PREFIX_SW = '100' # Segwit wallet
+
+
+def seed_prefix(seed_type):
+ if seed_type == 'standard':
+ return SEED_PREFIX
+ elif seed_type == 'segwit':
+ return SEED_PREFIX_SW
+ elif seed_type == '2fa':
+ return SEED_PREFIX_2FA
diff --git a/electrum/wallet.py b/electrum/wallet.py
new file mode 100644
index 000000000..9559f3955
--- /dev/null
+++ b/electrum/wallet.py
@@ -0,0 +1,1676 @@
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Wallet classes:
+# - Imported_Wallet: imported address, no keystore
+# - Standard_Wallet: one keystore, P2PKH
+# - Multisig_Wallet: several keystores, P2SH
+
+
+import os
+import sys
+import random
+import time
+import json
+import copy
+import errno
+import traceback
+from functools import partial
+from numbers import Number
+from decimal import Decimal
+
+from .i18n import _
+from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
+ format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
+ TimeoutException, WalletFileException, BitcoinException,
+ InvalidPassword, format_time)
+
+from .bitcoin import *
+from .version import *
+from .keystore import load_keystore, Hardware_KeyStore
+from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW
+
+from . import transaction, bitcoin, coinchooser, paymentrequest, contacts
+from .transaction import Transaction, TxOutput, TxOutputHwInfo
+from .plugin import run_hook
+from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
+ TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED)
+
+from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
+from .paymentrequest import InvoiceStore
+from .contacts import Contacts
+
+TX_STATUS = [
+ _('Unconfirmed'),
+ _('Unconfirmed parent'),
+ _('Not Verified'),
+ _('Local'),
+]
+
+
+
+def relayfee(network):
+ from .simple_config import FEERATE_DEFAULT_RELAY
+ MAX_RELAY_FEE = 1000000
+ f = network.relay_fee if network and network.relay_fee else FEERATE_DEFAULT_RELAY
+ return min(f, MAX_RELAY_FEE)
+
+def dust_threshold(network):
+ # Change <= dust threshold is added to the tx fee
+ return 182 * 3 * relayfee(network) / 1000
+
+
+def append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax):
+ if txin_type != 'p2pk':
+ address = bitcoin.pubkey_to_address(txin_type, pubkey)
+ scripthash = bitcoin.address_to_scripthash(address)
+ else:
+ script = bitcoin.public_key_to_p2pk_script(pubkey)
+ scripthash = bitcoin.script_to_scripthash(script)
+ address = '(pubkey)'
+
+ u = network.listunspent_for_scripthash(scripthash)
+ for item in u:
+ if len(inputs) >= imax:
+ break
+ item['address'] = address
+ item['type'] = txin_type
+ item['prevout_hash'] = item['tx_hash']
+ item['prevout_n'] = int(item['tx_pos'])
+ item['pubkeys'] = [pubkey]
+ item['x_pubkeys'] = [pubkey]
+ item['signatures'] = [None]
+ item['num_sig'] = 1
+ inputs.append(item)
+
+def sweep_preparations(privkeys, network, imax=100):
+
+ def find_utxos_for_privkey(txin_type, privkey, compressed):
+ pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
+ append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)
+ keypairs[pubkey] = privkey, compressed
+ inputs = []
+ keypairs = {}
+ for sec in privkeys:
+ txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
+ find_utxos_for_privkey(txin_type, privkey, compressed)
+ # do other lookups to increase support coverage
+ if is_minikey(sec):
+ # minikeys don't have a compressed byte
+ # we lookup both compressed and uncompressed pubkeys
+ find_utxos_for_privkey(txin_type, privkey, not compressed)
+ elif txin_type == 'p2pkh':
+ # WIF serialization does not distinguish p2pkh and p2pk
+ # we also search for pay-to-pubkey outputs
+ find_utxos_for_privkey('p2pk', privkey, compressed)
+ if not inputs:
+ raise Exception(_('No inputs found. (Note that inputs need to be confirmed)'))
+ # FIXME actually inputs need not be confirmed now, see https://github.com/kyuupichan/electrumx/issues/365
+ return inputs, keypairs
+
+
+def sweep(privkeys, network, config, recipient, fee=None, imax=100):
+ inputs, keypairs = sweep_preparations(privkeys, network, imax)
+ total = sum(i.get('value') for i in inputs)
+ if fee is None:
+ outputs = [TxOutput(TYPE_ADDRESS, recipient, total)]
+ tx = Transaction.from_io(inputs, outputs)
+ fee = config.estimate_fee(tx.estimated_size())
+ if total - fee < 0:
+ raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee))
+ if total - fee < dust_threshold(network):
+ raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
+
+ outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)]
+ locktime = network.get_local_height()
+
+ tx = Transaction.from_io(inputs, outputs, locktime=locktime)
+ tx.BIP_LI01_sort()
+ tx.set_rbf(True)
+ tx.sign(keypairs)
+ return tx
+
+
+
+class CannotBumpFee(Exception): pass
+
+
+
+
+class Abstract_Wallet(AddressSynchronizer):
+ """
+ Wallet classes are created to handle various address generation methods.
+ Completion states (watching-only, single account, no seed, etc) are handled inside classes.
+ """
+
+ max_change_outputs = 3
+ gap_limit_for_change = 6
+ verbosity_filter = 'w'
+
+ def __init__(self, storage):
+ AddressSynchronizer.__init__(self, storage)
+
+ self.electrum_version = ELECTRUM_VERSION
+ # saved fields
+ self.use_change = storage.get('use_change', True)
+ self.multiple_change = storage.get('multiple_change', False)
+ self.labels = storage.get('labels', {})
+ self.frozen_addresses = set(storage.get('frozen_addresses',[]))
+ self.fiat_value = storage.get('fiat_value', {})
+ self.receive_requests = storage.get('payment_requests', {})
+
+ self.calc_unused_change_addresses()
+
+ # save wallet type the first time
+ if self.storage.get('wallet_type') is None:
+ self.storage.put('wallet_type', self.wallet_type)
+
+ # invoices and contacts
+ self.invoices = InvoiceStore(self.storage)
+ self.contacts = Contacts(self.storage)
+
+ self.coin_price_cache = {}
+
+ def load_and_cleanup(self):
+ self.load_keystore()
+ self.load_addresses()
+ self.test_addresses_sanity()
+ super().load_and_cleanup()
+
+ def diagnostic_name(self):
+ return self.basename()
+
+ def __str__(self):
+ return self.basename()
+
+ def get_master_public_key(self):
+ return None
+
+ def basename(self):
+ return os.path.basename(self.storage.path)
+
+ def save_addresses(self):
+ self.storage.put('addresses', {'receiving':self.receiving_addresses, 'change':self.change_addresses})
+
+ def load_addresses(self):
+ d = self.storage.get('addresses', {})
+ if type(d) != dict: d={}
+ self.receiving_addresses = d.get('receiving', [])
+ self.change_addresses = d.get('change', [])
+
+ def test_addresses_sanity(self):
+ addrs = self.get_receiving_addresses()
+ if len(addrs) > 0:
+ if not bitcoin.is_address(addrs[0]):
+ raise WalletFileException('The addresses in this wallet are not bitcore addresses.')
+
+ def synchronize(self):
+ pass
+
+ def calc_unused_change_addresses(self):
+ with self.lock:
+ if hasattr(self, '_unused_change_addresses'):
+ addrs = self._unused_change_addresses
+ else:
+ addrs = self.get_change_addresses()
+ self._unused_change_addresses = [addr for addr in addrs if
+ self.get_address_history_len(addr) == 0]
+ return list(self._unused_change_addresses)
+
+ def is_deterministic(self):
+ return self.keystore.is_deterministic()
+
+ def set_label(self, name, text = None):
+ changed = False
+ old_text = self.labels.get(name)
+ if text:
+ text = text.replace("\n", " ")
+ if old_text != text:
+ self.labels[name] = text
+ changed = True
+ else:
+ if old_text:
+ self.labels.pop(name)
+ changed = True
+ if changed:
+ run_hook('set_label', self, name, text)
+ self.storage.put('labels', self.labels)
+ return changed
+
+ def set_fiat_value(self, txid, ccy, text):
+ if txid not in self.transactions:
+ return
+ if not text:
+ d = self.fiat_value.get(ccy, {})
+ if d and txid in d:
+ d.pop(txid)
+ else:
+ return
+ else:
+ try:
+ Decimal(text)
+ except:
+ return
+ if ccy not in self.fiat_value:
+ self.fiat_value[ccy] = {}
+ self.fiat_value[ccy][txid] = text
+ self.storage.put('fiat_value', self.fiat_value)
+
+ def get_fiat_value(self, txid, ccy):
+ fiat_value = self.fiat_value.get(ccy, {}).get(txid)
+ try:
+ return Decimal(fiat_value)
+ except:
+ return
+
+ def is_mine(self, address):
+ try:
+ self.get_address_index(address)
+ except KeyError:
+ return False
+ return True
+
+ def is_change(self, address):
+ if not self.is_mine(address):
+ return False
+ return self.get_address_index(address)[0]
+
+ def get_address_index(self, address):
+ raise NotImplementedError()
+
+ def get_redeem_script(self, address):
+ return None
+
+ def export_private_key(self, address, password):
+ if self.is_watching_only():
+ return []
+ index = self.get_address_index(address)
+ pk, compressed = self.keystore.get_private_key(index, password)
+ txin_type = self.get_txin_type(address)
+ redeem_script = self.get_redeem_script(address)
+ serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type)
+ return serialized_privkey, redeem_script
+
+ def get_public_keys(self, address):
+ return [self.get_public_key(address)]
+
+ def is_found(self):
+ return self.history.values() != [[]] * len(self.history)
+
+ def get_tx_info(self, tx):
+ is_relevant, is_mine, v, fee = self.get_wallet_delta(tx)
+ exp_n = None
+ can_broadcast = False
+ can_bump = False
+ label = ''
+ height = conf = timestamp = None
+ tx_hash = tx.txid()
+ if tx.is_complete():
+ if tx_hash in self.transactions.keys():
+ label = self.get_label(tx_hash)
+ tx_mined_status = self.get_tx_height(tx_hash)
+ height, conf = tx_mined_status.height, tx_mined_status.conf
+ if height > 0:
+ if conf:
+ status = _("{} confirmations").format(conf)
+ else:
+ status = _('Not verified')
+ elif height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED):
+ status = _('Unconfirmed')
+ if fee is None:
+ fee = self.tx_fees.get(tx_hash)
+ if fee and self.network and self.network.config.has_fee_mempool():
+ size = tx.estimated_size()
+ fee_per_byte = fee / size
+ exp_n = self.network.config.fee_to_depth(fee_per_byte)
+ can_bump = is_mine and not tx.is_final()
+ else:
+ status = _('Local')
+ can_broadcast = self.network is not None
+ else:
+ status = _("Signed")
+ can_broadcast = self.network is not None
+ else:
+ s, r = tx.signature_count()
+ status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)'%(s,r)
+
+ if is_relevant:
+ if is_mine:
+ if fee is not None:
+ amount = v + fee
+ else:
+ amount = v
+ else:
+ amount = v
+ else:
+ amount = None
+
+ return tx_hash, status, label, can_broadcast, can_bump, amount, fee, height, conf, timestamp, exp_n
+
+ def get_spendable_coins(self, domain, config):
+ confirmed_only = config.get('confirmed_only', False)
+ return self.get_utxos(domain, excluded=self.frozen_addresses, mature=True, confirmed_only=confirmed_only)
+
+ def dummy_address(self):
+ return self.get_receiving_addresses()[0]
+
+ def get_frozen_balance(self):
+ return self.get_balance(self.frozen_addresses)
+
+ def balance_at_timestamp(self, domain, target_timestamp):
+ h = self.get_history(domain)
+ balance = 0
+ for tx_hash, tx_mined_status, value, balance in h:
+ if tx_mined_status.timestamp > target_timestamp:
+ return balance - value
+ # return last balance
+ return balance
+
+ @profiler
+ def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None, fx=None, show_addresses=False):
+ from .util import timestamp_to_datetime, Satoshis, Fiat
+ out = []
+ income = 0
+ expenditures = 0
+ capital_gains = Decimal(0)
+ fiat_income = Decimal(0)
+ fiat_expenditures = Decimal(0)
+ h = self.get_history(domain)
+ now = time.time()
+ for tx_hash, tx_mined_status, value, balance in h:
+ timestamp = tx_mined_status.timestamp
+ if from_timestamp and (timestamp or now) < from_timestamp:
+ continue
+ if to_timestamp and (timestamp or now) >= to_timestamp:
+ continue
+ item = {
+ 'txid': tx_hash,
+ 'height': tx_mined_status.height,
+ 'confirmations': tx_mined_status.conf,
+ 'timestamp': timestamp,
+ 'value': Satoshis(value),
+ 'balance': Satoshis(balance),
+ 'date': timestamp_to_datetime(timestamp),
+ 'label': self.get_label(tx_hash),
+ }
+ if show_addresses:
+ tx = self.transactions.get(tx_hash)
+ item['inputs'] = list(map(lambda x: dict((k, x[k]) for k in ('prevout_hash', 'prevout_n')), tx.inputs()))
+ item['outputs'] = list(map(lambda x:{'address':x[0], 'value':Satoshis(x[1])}, tx.get_outputs()))
+ # value may be None if wallet is not fully synchronized
+ if value is None:
+ continue
+ # fixme: use in and out values
+ if value < 0:
+ expenditures += -value
+ else:
+ income += value
+ # fiat computations
+ if fx and fx.is_enabled():
+ fiat_value = self.get_fiat_value(tx_hash, fx.ccy)
+ fiat_default = fiat_value is None
+ fiat_value = fiat_value if fiat_value is not None else value / Decimal(COIN) * self.price_at_timestamp(tx_hash, fx.timestamp_rate) #
+ item['fiat_value'] = Fiat(fiat_value, fx.ccy)
+ item['fiat_default'] = fiat_default
+ if value < 0:
+ acquisition_price = - value / Decimal(COIN) * self.average_price(tx_hash, fx.timestamp_rate, fx.ccy)
+ liquidation_price = - fiat_value
+ item['acquisition_price'] = Fiat(acquisition_price, fx.ccy)
+ cg = liquidation_price - acquisition_price
+ item['capital_gain'] = Fiat(cg, fx.ccy)
+ capital_gains += cg
+ fiat_expenditures += -fiat_value
+ else:
+ fiat_income += fiat_value
+ out.append(item)
+ # add summary
+ if out:
+ b, v = out[0]['balance'].value, out[0]['value'].value
+ start_balance = None if b is None or v is None else b - v
+ end_balance = out[-1]['balance'].value
+ if from_timestamp is not None and to_timestamp is not None:
+ start_date = timestamp_to_datetime(from_timestamp)
+ end_date = timestamp_to_datetime(to_timestamp)
+ else:
+ start_date = None
+ end_date = None
+ summary = {
+ 'start_date': start_date,
+ 'end_date': end_date,
+ 'start_balance': Satoshis(start_balance),
+ 'end_balance': Satoshis(end_balance),
+ 'income': Satoshis(income),
+ 'expenditures': Satoshis(expenditures)
+ }
+ if fx and fx.is_enabled():
+ unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy)
+ summary['capital_gains'] = Fiat(capital_gains, fx.ccy)
+ summary['fiat_income'] = Fiat(fiat_income, fx.ccy)
+ summary['fiat_expenditures'] = Fiat(fiat_expenditures, fx.ccy)
+ summary['unrealized_gains'] = Fiat(unrealized, fx.ccy)
+ summary['start_fiat_balance'] = Fiat(fx.historical_value(start_balance, start_date), fx.ccy)
+ summary['end_fiat_balance'] = Fiat(fx.historical_value(end_balance, end_date), fx.ccy)
+ summary['start_fiat_value'] = Fiat(fx.historical_value(COIN, start_date), fx.ccy)
+ summary['end_fiat_value'] = Fiat(fx.historical_value(COIN, end_date), fx.ccy)
+ else:
+ summary = {}
+ return {
+ 'transactions': out,
+ 'summary': summary
+ }
+
+ def get_label(self, tx_hash):
+ label = self.labels.get(tx_hash, '')
+ if label is '':
+ label = self.get_default_label(tx_hash)
+ return label
+
+ def get_default_label(self, tx_hash):
+ if self.txi.get(tx_hash) == {}:
+ d = self.txo.get(tx_hash, {})
+ labels = []
+ for addr in d.keys():
+ label = self.labels.get(addr)
+ if label:
+ labels.append(label)
+ return ', '.join(labels)
+ return ''
+
+ def get_tx_status(self, tx_hash, tx_mined_status):
+ extra = []
+ height = tx_mined_status.height
+ conf = tx_mined_status.conf
+ timestamp = tx_mined_status.timestamp
+ if conf == 0:
+ tx = self.transactions.get(tx_hash)
+ if not tx:
+ return 2, 'unknown'
+ is_final = tx and tx.is_final()
+ if not is_final:
+ extra.append('rbf')
+ fee = self.get_wallet_delta(tx)[3]
+ if fee is None:
+ fee = self.tx_fees.get(tx_hash)
+ if fee is not None:
+ size = tx.estimated_size()
+ fee_per_byte = fee / size
+ extra.append(format_fee_satoshis(fee_per_byte) + ' sat/b')
+ if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
+ and self.network and self.network.config.has_fee_mempool():
+ exp_n = self.network.config.fee_to_depth(fee_per_byte)
+ if exp_n:
+ extra.append('%.2f MB'%(exp_n/1000000))
+ if height == TX_HEIGHT_LOCAL:
+ status = 3
+ elif height == TX_HEIGHT_UNCONF_PARENT:
+ status = 1
+ elif height == TX_HEIGHT_UNCONFIRMED:
+ status = 0
+ else:
+ status = 2
+ else:
+ status = 3 + min(conf, 6)
+ time_str = format_time(timestamp) if timestamp else _("unknown")
+ status_str = TX_STATUS[status] if status < 4 else time_str
+ if extra:
+ status_str += ' [%s]'%(', '.join(extra))
+ return status, status_str
+
+ def relayfee(self):
+ return relayfee(self.network)
+
+ def dust_threshold(self):
+ return dust_threshold(self.network)
+
+ def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None,
+ change_addr=None, is_sweep=False):
+ # check outputs
+ i_max = None
+ for i, o in enumerate(outputs):
+ if o.type == TYPE_ADDRESS:
+ if not is_address(o.address):
+ raise Exception("Invalid bitcore address: {}".format(o.address))
+ if o.value == '!':
+ if i_max is not None:
+ raise Exception("More than one output set to spend max")
+ i_max = i
+
+ # Avoid index-out-of-range with inputs[0] below
+ if not inputs:
+ raise NotEnoughFunds()
+
+ if fixed_fee is None and config.fee_per_kb() is None:
+ raise NoDynamicFeeEstimates()
+
+ for item in inputs:
+ self.add_input_info(item)
+
+ # change address
+ # if we leave it empty, coin_chooser will set it
+ change_addrs = []
+ if change_addr:
+ change_addrs = [change_addr]
+ elif self.use_change:
+ # Recalc and get unused change addresses
+ addrs = self.calc_unused_change_addresses()
+ # New change addresses are created only after a few
+ # confirmations.
+ if addrs:
+ # if there are any unused, select all
+ change_addrs = addrs
+ else:
+ # if there are none, take one randomly from the last few
+ addrs = self.get_change_addresses()[-self.gap_limit_for_change:]
+ change_addrs = [random.choice(addrs)] if addrs else []
+
+ # Fee estimator
+ if fixed_fee is None:
+ fee_estimator = config.estimate_fee
+ elif isinstance(fixed_fee, Number):
+ fee_estimator = lambda size: fixed_fee
+ elif callable(fixed_fee):
+ fee_estimator = fixed_fee
+ else:
+ raise Exception('Invalid argument fixed_fee: %s' % fixed_fee)
+
+ if i_max is None:
+ # Let the coin chooser select the coins to spend
+ max_change = self.max_change_outputs if self.multiple_change else 1
+ coin_chooser = coinchooser.get_coin_chooser(config)
+ tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change],
+ fee_estimator, self.dust_threshold())
+ else:
+ # FIXME?? this might spend inputs with negative effective value...
+ sendable = sum(map(lambda x:x['value'], inputs))
+ outputs[i_max] = outputs[i_max]._replace(value=0)
+ tx = Transaction.from_io(inputs, outputs[:])
+ fee = fee_estimator(tx.estimated_size())
+ amount = sendable - tx.output_value() - fee
+ if amount < 0:
+ raise NotEnoughFunds()
+ outputs[i_max] = outputs[i_max]._replace(value=amount)
+ tx = Transaction.from_io(inputs, outputs[:])
+
+ # Sort the inputs and outputs deterministically
+ tx.BIP_LI01_sort()
+ # Timelock tx to current height.
+ tx.locktime = self.get_local_height()
+ run_hook('make_unsigned_transaction', self, tx)
+ return tx
+
+ def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):
+ coins = self.get_spendable_coins(domain, config)
+ tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr)
+ self.sign_transaction(tx, password)
+ return tx
+
+ def is_frozen(self, addr):
+ return addr in self.frozen_addresses
+
+ def set_frozen_state(self, addrs, freeze):
+ '''Set frozen state of the addresses to FREEZE, True or False'''
+ if all(self.is_mine(addr) for addr in addrs):
+ if freeze:
+ self.frozen_addresses |= set(addrs)
+ else:
+ self.frozen_addresses -= set(addrs)
+ self.storage.put('frozen_addresses', list(self.frozen_addresses))
+ return True
+ return False
+
+ def wait_until_synchronized(self, callback=None):
+ def wait_for_wallet():
+ self.set_up_to_date(False)
+ while not self.is_up_to_date():
+ if callback:
+ msg = "{}\n{} {}".format(
+ _("Please wait..."),
+ _("Addresses generated:"),
+ len(self.get_addresses()))
+ callback(msg)
+ time.sleep(0.1)
+ def wait_for_network():
+ while not self.network.is_connected():
+ if callback:
+ msg = "{} \n".format(_("Connecting..."))
+ callback(msg)
+ time.sleep(0.1)
+ # wait until we are connected, because the user
+ # might have selected another server
+ if self.network:
+ self.print_error("waiting for network...")
+ wait_for_network()
+ self.print_error("waiting while wallet is syncing...")
+ wait_for_wallet()
+ else:
+ self.synchronize()
+
+ def can_export(self):
+ return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key')
+
+ def address_is_old(self, address, age_limit=2):
+ age = -1
+ h = self.history.get(address, [])
+ for tx_hash, tx_height in h:
+ if tx_height <= 0:
+ tx_age = 0
+ else:
+ tx_age = self.get_local_height() - tx_height + 1
+ if tx_age > age:
+ age = tx_age
+ return age > age_limit
+
+ def bump_fee(self, tx, delta):
+ if tx.is_final():
+ raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('transaction is final'))
+ tx = Transaction(tx.serialize())
+ tx.deserialize(force_full_parse=True) # need to parse inputs
+ inputs = copy.deepcopy(tx.inputs())
+ outputs = copy.deepcopy(tx.outputs())
+ for txin in inputs:
+ txin['signatures'] = [None] * len(txin['signatures'])
+ self.add_input_info(txin)
+ # use own outputs
+ s = list(filter(lambda x: self.is_mine(x[1]), outputs))
+ # ... unless there is none
+ if not s:
+ s = outputs
+ x_fee = run_hook('get_tx_extra_fee', self, tx)
+ if x_fee:
+ x_fee_address, x_fee_amount = x_fee
+ s = filter(lambda x: x[1]!=x_fee_address, s)
+
+ # prioritize low value outputs, to get rid of dust
+ s = sorted(s, key=lambda x: x[2])
+ for o in s:
+ i = outputs.index(o)
+ if o.value - delta >= self.dust_threshold():
+ outputs[i] = o._replace(value=o.value-delta)
+ delta = 0
+ break
+ else:
+ del outputs[i]
+ delta -= o.value
+ if delta > 0:
+ continue
+ if delta > 0:
+ raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs'))
+ locktime = self.get_local_height()
+ tx_new = Transaction.from_io(inputs, outputs, locktime=locktime)
+ tx_new.BIP_LI01_sort()
+ return tx_new
+
+ def cpfp(self, tx, fee):
+ txid = tx.txid()
+ for i, o in enumerate(tx.outputs()):
+ address, value = o.address, o.value
+ if o.type == TYPE_ADDRESS and self.is_mine(address):
+ break
+ else:
+ return
+ coins = self.get_addr_utxo(address)
+ item = coins.get(txid+':%d'%i)
+ if not item:
+ return
+ self.add_input_info(item)
+ inputs = [item]
+ outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)]
+ locktime = self.get_local_height()
+ # note: no need to call tx.BIP_LI01_sort() here - single input/output
+ return Transaction.from_io(inputs, outputs, locktime=locktime)
+
+ def add_input_sig_info(self, txin, address):
+ raise NotImplementedError() # implemented by subclasses
+
+ def add_input_info(self, txin):
+ address = txin['address']
+ if self.is_mine(address):
+ txin['type'] = self.get_txin_type(address)
+ # segwit needs value to sign
+ if txin.get('value') is None and Transaction.is_input_value_needed(txin):
+ received, spent = self.get_addr_io(address)
+ item = received.get(txin['prevout_hash']+':%d'%txin['prevout_n'])
+ tx_height, value, is_cb = item
+ txin['value'] = value
+ self.add_input_sig_info(txin, address)
+
+ def add_input_info_to_all_inputs(self, tx):
+ if tx.is_complete():
+ return
+ for txin in tx.inputs():
+ self.add_input_info(txin)
+
+ def can_sign(self, tx):
+ if tx.is_complete():
+ return False
+ # add info to inputs if we can; otherwise we might return a false negative:
+ self.add_input_info_to_all_inputs(tx) # though note that this is a side-effect
+ for k in self.get_keystores():
+ if k.can_sign(tx):
+ return True
+ return False
+
+ def get_input_tx(self, tx_hash, ignore_timeout=False):
+ # First look up an input transaction in the wallet where it
+ # will likely be. If co-signing a transaction it may not have
+ # all the input txs, in which case we ask the network.
+ tx = self.transactions.get(tx_hash, None)
+ if not tx and self.network:
+ try:
+ tx = Transaction(self.network.get_transaction(tx_hash))
+ except TimeoutException as e:
+ self.print_error('getting input txn from network timed out for {}'.format(tx_hash))
+ if not ignore_timeout:
+ raise e
+ return tx
+
+ def add_hw_info(self, tx):
+ # add previous tx for hw wallets
+ for txin in tx.inputs():
+ tx_hash = txin['prevout_hash']
+ # segwit inputs might not be needed for some hw wallets
+ ignore_timeout = Transaction.is_segwit_input(txin)
+ txin['prev_tx'] = self.get_input_tx(tx_hash, ignore_timeout)
+ # add output info for hw wallets
+ info = {}
+ xpubs = self.get_master_public_keys()
+ for txout in tx.outputs():
+ _type, addr, amount = txout
+ if self.is_mine(addr):
+ index = self.get_address_index(addr)
+ pubkeys = self.get_public_keys(addr)
+ # sort xpubs using the order of pubkeys
+ sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))
+ num_sig = self.m if isinstance(self, Multisig_Wallet) else None
+ info[addr] = TxOutputHwInfo(index, sorted_xpubs, num_sig, self.txin_type)
+ tx.output_info = info
+
+ def sign_transaction(self, tx, password):
+ if self.is_watching_only():
+ return
+ self.add_input_info_to_all_inputs(tx)
+ # hardware wallets require extra info
+ if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]):
+ self.add_hw_info(tx)
+ # sign. start with ready keystores.
+ for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True):
+ try:
+ if k.can_sign(tx):
+ k.sign_transaction(tx, password)
+ except UserCancelled:
+ continue
+ return tx
+
+ def get_unused_addresses(self):
+ # fixme: use slots from expired requests
+ domain = self.get_receiving_addresses()
+ return [addr for addr in domain if not self.history.get(addr)
+ and addr not in self.receive_requests.keys()]
+
+ def get_unused_address(self):
+ addrs = self.get_unused_addresses()
+ if addrs:
+ return addrs[0]
+
+ def get_receiving_address(self):
+ # always return an address
+ domain = self.get_receiving_addresses()
+ if not domain:
+ return
+ choice = domain[0]
+ for addr in domain:
+ if not self.history.get(addr):
+ if addr not in self.receive_requests.keys():
+ return addr
+ else:
+ choice = addr
+ return choice
+
+ def get_payment_status(self, address, amount):
+ local_height = self.get_local_height()
+ received, sent = self.get_addr_io(address)
+ l = []
+ for txo, x in received.items():
+ h, v, is_cb = x
+ txid, n = txo.split(':')
+ info = self.verified_tx.get(txid)
+ if info:
+ conf = local_height - info.height
+ else:
+ conf = 0
+ l.append((conf, v))
+ vsum = 0
+ for conf, v in reversed(sorted(l)):
+ vsum += v
+ if vsum >= amount:
+ return True, conf
+ return False, None
+
+ def get_payment_request(self, addr, config):
+ r = self.receive_requests.get(addr)
+ if not r:
+ return
+ out = copy.copy(r)
+ out['URI'] = 'bitcore:' + addr + '?amount=' + format_satoshis(out.get('amount'))
+ status, conf = self.get_request_status(addr)
+ out['status'] = status
+ if conf is not None:
+ out['confirmations'] = conf
+ # check if bip70 file exists
+ rdir = config.get('requests_dir')
+ if rdir:
+ key = out.get('id', addr)
+ path = os.path.join(rdir, 'req', key[0], key[1], key)
+ if os.path.exists(path):
+ baseurl = 'file://' + rdir
+ rewrite = config.get('url_rewrite')
+ if rewrite:
+ try:
+ baseurl = baseurl.replace(*rewrite)
+ except BaseException as e:
+ self.print_stderr('Invalid config setting for "url_rewrite". err:', e)
+ out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)
+ out['URI'] += '&r=' + out['request_url']
+ out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key
+ websocket_server_announce = config.get('websocket_server_announce')
+ if websocket_server_announce:
+ out['websocket_server'] = websocket_server_announce
+ else:
+ out['websocket_server'] = config.get('websocket_server', 'localhost')
+ websocket_port_announce = config.get('websocket_port_announce')
+ if websocket_port_announce:
+ out['websocket_port'] = websocket_port_announce
+ else:
+ out['websocket_port'] = config.get('websocket_port', 9999)
+ return out
+
+ def get_request_status(self, key):
+ r = self.receive_requests.get(key)
+ if r is None:
+ return PR_UNKNOWN
+ address = r['address']
+ amount = r.get('amount')
+ timestamp = r.get('time', 0)
+ if timestamp and type(timestamp) != int:
+ timestamp = 0
+ expiration = r.get('exp')
+ if expiration and type(expiration) != int:
+ expiration = 0
+ conf = None
+ if amount:
+ if self.is_up_to_date():
+ paid, conf = self.get_payment_status(address, amount)
+ status = PR_PAID if paid else PR_UNPAID
+ if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration:
+ status = PR_EXPIRED
+ else:
+ status = PR_UNKNOWN
+ else:
+ status = PR_UNKNOWN
+ return status, conf
+
+ def make_payment_request(self, addr, amount, message, expiration):
+ timestamp = int(time.time())
+ _id = bh2u(Hash(addr + "%d"%timestamp))[0:10]
+ r = {'time':timestamp, 'amount':amount, 'exp':expiration, 'address':addr, 'memo':message, 'id':_id}
+ return r
+
+ def sign_payment_request(self, key, alias, alias_addr, password):
+ req = self.receive_requests.get(key)
+ alias_privkey = self.export_private_key(alias_addr, password)[0]
+ pr = paymentrequest.make_unsigned_request(req)
+ paymentrequest.sign_request_with_alias(pr, alias, alias_privkey)
+ req['name'] = pr.pki_data
+ req['sig'] = bh2u(pr.signature)
+ self.receive_requests[key] = req
+ self.storage.put('payment_requests', self.receive_requests)
+
+ def add_payment_request(self, req, config):
+ addr = req['address']
+ if not bitcoin.is_address(addr):
+ raise Exception(_('Invalid Bitcoin address.'))
+ if not self.is_mine(addr):
+ raise Exception(_('Address not in wallet.'))
+
+ amount = req.get('amount')
+ message = req.get('memo')
+ self.receive_requests[addr] = req
+ self.storage.put('payment_requests', self.receive_requests)
+ self.set_label(addr, message) # should be a default label
+
+ rdir = config.get('requests_dir')
+ if rdir and amount is not None:
+ key = req.get('id', addr)
+ pr = paymentrequest.make_request(config, req)
+ path = os.path.join(rdir, 'req', key[0], key[1], key)
+ if not os.path.exists(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno != errno.EEXIST:
+ raise
+ with open(os.path.join(path, key), 'wb') as f:
+ f.write(pr.SerializeToString())
+ # reload
+ req = self.get_payment_request(addr, config)
+ with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f:
+ f.write(json.dumps(req))
+ return req
+
+ def remove_payment_request(self, addr, config):
+ if addr not in self.receive_requests:
+ return False
+ r = self.receive_requests.pop(addr)
+ rdir = config.get('requests_dir')
+ if rdir:
+ key = r.get('id', addr)
+ for s in ['.json', '']:
+ n = os.path.join(rdir, 'req', key[0], key[1], key, key + s)
+ if os.path.exists(n):
+ os.unlink(n)
+ self.storage.put('payment_requests', self.receive_requests)
+ return True
+
+ def get_sorted_requests(self, config):
+ def f(addr):
+ try:
+ return self.get_address_index(addr)
+ except:
+ return
+ keys = map(lambda x: (f(x), x), self.receive_requests.keys())
+ sorted_keys = sorted(filter(lambda x: x[0] is not None, keys))
+ return [self.get_payment_request(x[1], config) for x in sorted_keys]
+
+ def get_fingerprint(self):
+ raise NotImplementedError()
+
+ def can_import_privkey(self):
+ return False
+
+ def can_import_address(self):
+ return False
+
+ def can_delete_address(self):
+ return False
+
+ def has_password(self):
+ return self.has_keystore_encryption() or self.has_storage_encryption()
+
+ def can_have_keystore_encryption(self):
+ return self.keystore and self.keystore.may_have_password()
+
+ def get_available_storage_encryption_version(self):
+ """Returns the type of storage encryption offered to the user.
+
+ A wallet file (storage) is either encrypted with this version
+ or is stored in plaintext.
+ """
+ if isinstance(self.keystore, Hardware_KeyStore):
+ return STO_EV_XPUB_PW
+ else:
+ return STO_EV_USER_PW
+
+ def has_keystore_encryption(self):
+ """Returns whether encryption is enabled for the keystore.
+
+ If True, e.g. signing a transaction will require a password.
+ """
+ if self.can_have_keystore_encryption():
+ return self.storage.get('use_encryption', False)
+ return False
+
+ def has_storage_encryption(self):
+ """Returns whether encryption is enabled for the wallet file on disk."""
+ return self.storage.is_encrypted()
+
+ @classmethod
+ def may_have_password(cls):
+ return True
+
+ def check_password(self, password):
+ if self.has_keystore_encryption():
+ self.keystore.check_password(password)
+ self.storage.check_password(password)
+
+ def update_password(self, old_pw, new_pw, encrypt_storage=False):
+ if old_pw is None and self.has_password():
+ raise InvalidPassword()
+ self.check_password(old_pw)
+
+ if encrypt_storage:
+ enc_version = self.get_available_storage_encryption_version()
+ else:
+ enc_version = STO_EV_PLAINTEXT
+ self.storage.set_password(new_pw, enc_version)
+
+ # note: Encrypting storage with a hw device is currently only
+ # allowed for non-multisig wallets. Further,
+ # Hardware_KeyStore.may_have_password() == False.
+ # If these were not the case,
+ # extra care would need to be taken when encrypting keystores.
+ self._update_password_for_keystore(old_pw, new_pw)
+ encrypt_keystore = self.can_have_keystore_encryption()
+ self.storage.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
+
+ self.storage.write()
+
+ def sign_message(self, address, message, password):
+ index = self.get_address_index(address)
+ return self.keystore.sign_message(index, message, password)
+
+ def decrypt_message(self, pubkey, message, password):
+ addr = self.pubkeys_to_address(pubkey)
+ index = self.get_address_index(addr)
+ return self.keystore.decrypt_message(index, message, password)
+
+ def txin_value(self, txin):
+ txid = txin['prevout_hash']
+ prev_n = txin['prevout_n']
+ for address, d in self.txo.get(txid, {}).items():
+ for n, v, cb in d:
+ if n == prev_n:
+ return v
+ # may occur if wallet is not synchronized
+ return None
+
+ def price_at_timestamp(self, txid, price_func):
+ """Returns fiat price of bitcore at the time tx got confirmed."""
+ timestamp = self.get_tx_height(txid).timestamp
+ return price_func(timestamp if timestamp else time.time())
+
+ def unrealized_gains(self, domain, price_func, ccy):
+ coins = self.get_utxos(domain)
+ now = time.time()
+ p = price_func(now)
+ ap = sum(self.coin_price(coin['prevout_hash'], price_func, ccy, self.txin_value(coin)) for coin in coins)
+ lp = sum([coin['value'] for coin in coins]) * p / Decimal(COIN)
+ return lp - ap
+
+ def average_price(self, txid, price_func, ccy):
+ """ Average acquisition price of the inputs of a transaction """
+ input_value = 0
+ total_price = 0
+ for addr, d in self.txi.get(txid, {}).items():
+ for ser, v in d:
+ input_value += v
+ total_price += self.coin_price(ser.split(':')[0], price_func, ccy, v)
+ return total_price / (input_value/Decimal(COIN))
+
+ def coin_price(self, txid, price_func, ccy, txin_value):
+ """
+ Acquisition price of a coin.
+ This assumes that either all inputs are mine, or no input is mine.
+ """
+ if txin_value is None:
+ return Decimal('NaN')
+ cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value))
+ result = self.coin_price_cache.get(cache_key, None)
+ if result is not None:
+ return result
+ if self.txi.get(txid, {}) != {}:
+ result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN)
+ self.coin_price_cache[cache_key] = result
+ return result
+ else:
+ fiat_value = self.get_fiat_value(txid, ccy)
+ if fiat_value is not None:
+ return fiat_value
+ else:
+ p = self.price_at_timestamp(txid, price_func)
+ return p * txin_value/Decimal(COIN)
+
+ def is_billing_address(self, addr):
+ # overloaded for TrustedCoin wallets
+ return False
+
+
+class Simple_Wallet(Abstract_Wallet):
+ # wallet with a single keystore
+
+ def get_keystore(self):
+ return self.keystore
+
+ def get_keystores(self):
+ return [self.keystore]
+
+ def is_watching_only(self):
+ return self.keystore.is_watching_only()
+
+ def _update_password_for_keystore(self, old_pw, new_pw):
+ if self.keystore and self.keystore.may_have_password():
+ self.keystore.update_password(old_pw, new_pw)
+ self.save_keystore()
+
+ def save_keystore(self):
+ self.storage.put('keystore', self.keystore.dump())
+
+
+class Imported_Wallet(Simple_Wallet):
+ # wallet made of imported addresses
+
+ wallet_type = 'imported'
+ txin_type = 'address'
+
+ def __init__(self, storage):
+ Abstract_Wallet.__init__(self, storage)
+
+ def is_watching_only(self):
+ return self.keystore is None
+
+ def get_keystores(self):
+ return [self.keystore] if self.keystore else []
+
+ def can_import_privkey(self):
+ return bool(self.keystore)
+
+ def load_keystore(self):
+ self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None
+
+ def save_keystore(self):
+ self.storage.put('keystore', self.keystore.dump())
+
+ def load_addresses(self):
+ self.addresses = self.storage.get('addresses', {})
+ # fixme: a reference to addresses is needed
+ if self.keystore:
+ self.keystore.addresses = self.addresses
+
+ def save_addresses(self):
+ self.storage.put('addresses', self.addresses)
+
+ def can_import_address(self):
+ return self.is_watching_only()
+
+ def can_delete_address(self):
+ return True
+
+ def has_seed(self):
+ return False
+
+ def is_deterministic(self):
+ return False
+
+ def is_change(self, address):
+ return False
+
+ def get_master_public_keys(self):
+ return []
+
+ def is_beyond_limit(self, address):
+ return False
+
+ def get_fingerprint(self):
+ return ''
+
+ def get_addresses(self):
+ # note: overridden so that the history can be cleared
+ return sorted(self.addresses.keys())
+
+ def get_receiving_addresses(self):
+ return self.get_addresses()
+
+ def get_change_addresses(self):
+ return []
+
+ def import_address(self, address):
+ if not bitcoin.is_address(address):
+ return ''
+ if address in self.addresses:
+ return ''
+ self.addresses[address] = {}
+ self.add_address(address)
+ self.save_addresses()
+ self.save_transactions(write=True)
+ return address
+
+ def delete_address(self, address):
+ if address not in self.addresses:
+ return
+
+ transactions_to_remove = set() # only referred to by this address
+ transactions_new = set() # txs that are not only referred to by address
+ with self.lock:
+ for addr, details in self.history.items():
+ if addr == address:
+ for tx_hash, height in details:
+ transactions_to_remove.add(tx_hash)
+ else:
+ for tx_hash, height in details:
+ transactions_new.add(tx_hash)
+ transactions_to_remove -= transactions_new
+ self.history.pop(address, None)
+
+ for tx_hash in transactions_to_remove:
+ self.remove_transaction(tx_hash)
+ self.tx_fees.pop(tx_hash, None)
+ self.verified_tx.pop(tx_hash, None)
+ self.unverified_tx.pop(tx_hash, None)
+ self.transactions.pop(tx_hash, None)
+ self.save_verified_tx()
+ self.save_transactions()
+
+ self.set_label(address, None)
+ self.remove_payment_request(address, {})
+ self.set_frozen_state([address], False)
+
+ pubkey = self.get_public_key(address)
+ self.addresses.pop(address)
+ if pubkey:
+ # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key)
+ for txin_type in bitcoin.WIF_SCRIPT_TYPES.keys():
+ try:
+ addr2 = bitcoin.pubkey_to_address(txin_type, pubkey)
+ except NotImplementedError:
+ pass
+ else:
+ if addr2 in self.addresses:
+ break
+ else:
+ self.keystore.delete_imported_key(pubkey)
+ self.save_keystore()
+ self.save_addresses()
+
+ self.storage.write()
+
+ def get_address_index(self, address):
+ return self.get_public_key(address)
+
+ def get_public_key(self, address):
+ return self.addresses[address].get('pubkey')
+
+ def import_private_key(self, sec, pw, redeem_script=None):
+ try:
+ txin_type, pubkey = self.keystore.import_privkey(sec, pw)
+ except Exception:
+ neutered_privkey = str(sec)[:3] + '..' + str(sec)[-2:]
+ raise BitcoinException('Invalid private key: {}'.format(neutered_privkey))
+ if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
+ if redeem_script is not None:
+ raise BitcoinException('Cannot use redeem script with script type {}'.format(txin_type))
+ addr = bitcoin.pubkey_to_address(txin_type, pubkey)
+ elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
+ if redeem_script is None:
+ raise BitcoinException('Redeem script required for script type {}'.format(txin_type))
+ addr = bitcoin.redeem_script_to_address(txin_type, redeem_script)
+ else:
+ raise NotImplementedError(txin_type)
+ self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':redeem_script}
+ self.save_keystore()
+ self.add_address(addr)
+ self.save_addresses()
+ self.save_transactions(write=True)
+ return addr
+
+ def get_redeem_script(self, address):
+ d = self.addresses[address]
+ redeem_script = d['redeem_script']
+ return redeem_script
+
+ def get_txin_type(self, address):
+ return self.addresses[address].get('type', 'address')
+
+ def add_input_sig_info(self, txin, address):
+ if self.is_watching_only():
+ x_pubkey = 'fd' + address_to_script(address)
+ txin['x_pubkeys'] = [x_pubkey]
+ txin['signatures'] = [None]
+ return
+ if txin['type'] in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
+ pubkey = self.addresses[address]['pubkey']
+ txin['num_sig'] = 1
+ txin['x_pubkeys'] = [pubkey]
+ txin['signatures'] = [None]
+ else:
+ raise NotImplementedError('imported wallets for p2sh are not implemented')
+
+ def pubkeys_to_address(self, pubkey):
+ for addr, v in self.addresses.items():
+ if v.get('pubkey') == pubkey:
+ return addr
+
+class Deterministic_Wallet(Abstract_Wallet):
+
+ def __init__(self, storage):
+ Abstract_Wallet.__init__(self, storage)
+ self.gap_limit = storage.get('gap_limit', 20)
+
+ def has_seed(self):
+ return self.keystore.has_seed()
+
+ def get_addresses(self):
+ # note: overridden so that the history can be cleared.
+ # addresses are ordered based on derivation
+ out = []
+ out += self.get_receiving_addresses()
+ out += self.get_change_addresses()
+ return out
+
+ def get_receiving_addresses(self):
+ return self.receiving_addresses
+
+ def get_change_addresses(self):
+ return self.change_addresses
+
+ def get_seed(self, password):
+ return self.keystore.get_seed(password)
+
+ def add_seed(self, seed, pw):
+ self.keystore.add_seed(seed, pw)
+
+ def change_gap_limit(self, value):
+ '''This method is not called in the code, it is kept for console use'''
+ if value >= self.gap_limit:
+ self.gap_limit = value
+ self.storage.put('gap_limit', self.gap_limit)
+ return True
+ elif value >= self.min_acceptable_gap():
+ addresses = self.get_receiving_addresses()
+ k = self.num_unused_trailing_addresses(addresses)
+ n = len(addresses) - k + value
+ self.receiving_addresses = self.receiving_addresses[0:n]
+ self.gap_limit = value
+ self.storage.put('gap_limit', self.gap_limit)
+ self.save_addresses()
+ return True
+ else:
+ return False
+
+ def num_unused_trailing_addresses(self, addresses):
+ k = 0
+ for a in addresses[::-1]:
+ if self.history.get(a):break
+ k = k + 1
+ return k
+
+ def min_acceptable_gap(self):
+ # fixme: this assumes wallet is synchronized
+ n = 0
+ nmax = 0
+ addresses = self.get_receiving_addresses()
+ k = self.num_unused_trailing_addresses(addresses)
+ for a in addresses[0:-k]:
+ if self.history.get(a):
+ n = 0
+ else:
+ n += 1
+ if n > nmax: nmax = n
+ return nmax + 1
+
+ def load_addresses(self):
+ super().load_addresses()
+ self._addr_to_addr_index = {} # key: address, value: (is_change, index)
+ for i, addr in enumerate(self.receiving_addresses):
+ self._addr_to_addr_index[addr] = (False, i)
+ for i, addr in enumerate(self.change_addresses):
+ self._addr_to_addr_index[addr] = (True, i)
+
+ def create_new_address(self, for_change=False):
+ assert type(for_change) is bool
+ with self.lock:
+ addr_list = self.change_addresses if for_change else self.receiving_addresses
+ n = len(addr_list)
+ x = self.derive_pubkeys(for_change, n)
+ address = self.pubkeys_to_address(x)
+ addr_list.append(address)
+ self._addr_to_addr_index[address] = (for_change, n)
+ self.save_addresses()
+ self.add_address(address)
+ if for_change:
+ # note: if it's actually used, it will get filtered later
+ self._unused_change_addresses.append(address)
+ return address
+
+ def synchronize_sequence(self, for_change):
+ limit = self.gap_limit_for_change if for_change else self.gap_limit
+ while True:
+ addresses = self.get_change_addresses() if for_change else self.get_receiving_addresses()
+ if len(addresses) < limit:
+ self.create_new_address(for_change)
+ continue
+ if list(map(lambda a: self.address_is_old(a), addresses[-limit:] )) == limit*[False]:
+ break
+ else:
+ self.create_new_address(for_change)
+
+ def synchronize(self):
+ with self.lock:
+ self.synchronize_sequence(False)
+ self.synchronize_sequence(True)
+
+ def is_beyond_limit(self, address):
+ is_change, i = self.get_address_index(address)
+ addr_list = self.get_change_addresses() if is_change else self.get_receiving_addresses()
+ limit = self.gap_limit_for_change if is_change else self.gap_limit
+ if i < limit:
+ return False
+ prev_addresses = addr_list[max(0, i - limit):max(0, i)]
+ for addr in prev_addresses:
+ if self.history.get(addr):
+ return False
+ return True
+
+ def get_address_index(self, address):
+ return self._addr_to_addr_index[address]
+
+ def get_master_public_keys(self):
+ return [self.get_master_public_key()]
+
+ def get_fingerprint(self):
+ return self.get_master_public_key()
+
+ def get_txin_type(self, address):
+ return self.txin_type
+
+
+class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet):
+
+ """ Deterministic Wallet with a single pubkey per address """
+
+ def __init__(self, storage):
+ Deterministic_Wallet.__init__(self, storage)
+
+ def get_public_key(self, address):
+ sequence = self.get_address_index(address)
+ pubkey = self.get_pubkey(*sequence)
+ return pubkey
+
+ def load_keystore(self):
+ self.keystore = load_keystore(self.storage, 'keystore')
+ try:
+ xtype = bitcoin.xpub_type(self.keystore.xpub)
+ except:
+ xtype = 'standard'
+ self.txin_type = 'p2pkh' if xtype == 'standard' else xtype
+
+ def get_pubkey(self, c, i):
+ return self.derive_pubkeys(c, i)
+
+ def add_input_sig_info(self, txin, address):
+ derivation = self.get_address_index(address)
+ x_pubkey = self.keystore.get_xpubkey(*derivation)
+ txin['x_pubkeys'] = [x_pubkey]
+ txin['signatures'] = [None]
+ txin['num_sig'] = 1
+
+ def get_master_public_key(self):
+ return self.keystore.get_master_public_key()
+
+ def derive_pubkeys(self, c, i):
+ return self.keystore.derive_pubkey(c, i)
+
+
+
+
+
+
+class Standard_Wallet(Simple_Deterministic_Wallet):
+ wallet_type = 'standard'
+
+ def pubkeys_to_address(self, pubkey):
+ return bitcoin.pubkey_to_address(self.txin_type, pubkey)
+
+
+class Multisig_Wallet(Deterministic_Wallet):
+ # generic m of n
+ gap_limit = 20
+
+ def __init__(self, storage):
+ self.wallet_type = storage.get('wallet_type')
+ self.m, self.n = multisig_type(self.wallet_type)
+ Deterministic_Wallet.__init__(self, storage)
+
+ def get_pubkeys(self, c, i):
+ return self.derive_pubkeys(c, i)
+
+ def get_public_keys(self, address):
+ sequence = self.get_address_index(address)
+ return self.get_pubkeys(*sequence)
+
+ def pubkeys_to_address(self, pubkeys):
+ redeem_script = self.pubkeys_to_redeem_script(pubkeys)
+ return bitcoin.redeem_script_to_address(self.txin_type, redeem_script)
+
+ def pubkeys_to_redeem_script(self, pubkeys):
+ return transaction.multisig_script(sorted(pubkeys), self.m)
+
+ def get_redeem_script(self, address):
+ pubkeys = self.get_public_keys(address)
+ redeem_script = self.pubkeys_to_redeem_script(pubkeys)
+ return redeem_script
+
+ def derive_pubkeys(self, c, i):
+ return [k.derive_pubkey(c, i) for k in self.get_keystores()]
+
+ def load_keystore(self):
+ self.keystores = {}
+ for i in range(self.n):
+ name = 'x%d/'%(i+1)
+ self.keystores[name] = load_keystore(self.storage, name)
+ self.keystore = self.keystores['x1/']
+ xtype = bitcoin.xpub_type(self.keystore.xpub)
+ self.txin_type = 'p2sh' if xtype == 'standard' else xtype
+
+ def save_keystore(self):
+ for name, k in self.keystores.items():
+ self.storage.put(name, k.dump())
+
+ def get_keystore(self):
+ return self.keystores.get('x1/')
+
+ def get_keystores(self):
+ return [self.keystores[i] for i in sorted(self.keystores.keys())]
+
+ def can_have_keystore_encryption(self):
+ return any([k.may_have_password() for k in self.get_keystores()])
+
+ def _update_password_for_keystore(self, old_pw, new_pw):
+ for name, keystore in self.keystores.items():
+ if keystore.may_have_password():
+ keystore.update_password(old_pw, new_pw)
+ self.storage.put(name, keystore.dump())
+
+ def check_password(self, password):
+ for name, keystore in self.keystores.items():
+ if keystore.may_have_password():
+ keystore.check_password(password)
+ self.storage.check_password(password)
+
+ def get_available_storage_encryption_version(self):
+ # multisig wallets are not offered hw device encryption
+ return STO_EV_USER_PW
+
+ def has_seed(self):
+ return self.keystore.has_seed()
+
+ def is_watching_only(self):
+ return not any([not k.is_watching_only() for k in self.get_keystores()])
+
+ def get_master_public_key(self):
+ return self.keystore.get_master_public_key()
+
+ def get_master_public_keys(self):
+ return [k.get_master_public_key() for k in self.get_keystores()]
+
+ def get_fingerprint(self):
+ return ''.join(sorted(self.get_master_public_keys()))
+
+ def add_input_sig_info(self, txin, address):
+ # x_pubkeys are not sorted here because it would be too slow
+ # they are sorted in transaction.get_sorted_pubkeys
+ # pubkeys is set to None to signal that x_pubkeys are unsorted
+ derivation = self.get_address_index(address)
+ x_pubkeys_expected = [k.get_xpubkey(*derivation) for k in self.get_keystores()]
+ x_pubkeys_actual = txin.get('x_pubkeys')
+ # if 'x_pubkeys' is already set correctly (ignoring order, as above), leave it.
+ # otherwise we might delete signatures
+ if x_pubkeys_actual and set(x_pubkeys_actual) == set(x_pubkeys_expected):
+ return
+ txin['x_pubkeys'] = x_pubkeys_expected
+ txin['pubkeys'] = None
+ # we need n place holders
+ txin['signatures'] = [None] * self.n
+ txin['num_sig'] = self.m
+
+
+wallet_types = ['standard', 'multisig', 'imported']
+
+def register_wallet_type(category):
+ wallet_types.append(category)
+
+wallet_constructors = {
+ 'standard': Standard_Wallet,
+ 'old': Standard_Wallet,
+ 'xpub': Standard_Wallet,
+ 'imported': Imported_Wallet
+}
+
+def register_constructor(wallet_type, constructor):
+ wallet_constructors[wallet_type] = constructor
+
+# former WalletFactory
+class Wallet(object):
+ """The main wallet "entry point".
+ This class is actually a factory that will return a wallet of the correct
+ type when passed a WalletStorage instance."""
+
+ def __new__(self, storage):
+ wallet_type = storage.get('wallet_type')
+ WalletClass = Wallet.wallet_class(wallet_type)
+ wallet = WalletClass(storage)
+ # Convert hardware wallets restored with older versions of
+ # Electrum to BIP44 wallets. A hardware wallet does not have
+ # a seed and plugins do not need to handle having one.
+ rwc = getattr(wallet, 'restore_wallet_class', None)
+ if rwc and storage.get('seed', ''):
+ storage.print_error("converting wallet type to " + rwc.wallet_type)
+ storage.put('wallet_type', rwc.wallet_type)
+ wallet = rwc(storage)
+ return wallet
+
+ @staticmethod
+ def wallet_class(wallet_type):
+ if multisig_type(wallet_type):
+ return Multisig_Wallet
+ if wallet_type in wallet_constructors:
+ return wallet_constructors[wallet_type]
+ raise WalletFileException("Unknown wallet type: " + str(wallet_type))
diff --git a/electrum/websockets.py b/electrum/websockets.py
new file mode 100644
index 000000000..4637f83e1
--- /dev/null
+++ b/electrum/websockets.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import queue
+import threading, os, json
+from collections import defaultdict
+try:
+ from SimpleWebSocketServer import WebSocket, SimpleSSLWebSocketServer
+except ImportError:
+ import sys
+ sys.exit("install SimpleWebSocketServer")
+
+from . import util
+from . import bitcoin
+
+request_queue = queue.Queue()
+
+class ElectrumWebSocket(WebSocket):
+
+ def handleMessage(self):
+ assert self.data[0:3] == 'id:'
+ util.print_error("message received", self.data)
+ request_id = self.data[3:]
+ request_queue.put((self, request_id))
+
+ def handleConnected(self):
+ util.print_error("connected", self.address)
+
+ def handleClose(self):
+ util.print_error("closed", self.address)
+
+
+
+class WsClientThread(util.DaemonThread):
+
+ def __init__(self, config, network):
+ util.DaemonThread.__init__(self)
+ self.network = network
+ self.config = config
+ self.response_queue = queue.Queue()
+ self.subscriptions = defaultdict(list)
+
+ def make_request(self, request_id):
+ # read json file
+ rdir = self.config.get('requests_dir')
+ n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json')
+ with open(n, encoding='utf-8') as f:
+ s = f.read()
+ d = json.loads(s)
+ addr = d.get('address')
+ amount = d.get('amount')
+ return addr, amount
+
+ def reading_thread(self):
+ while self.is_running():
+ try:
+ ws, request_id = request_queue.get()
+ except queue.Empty:
+ continue
+ try:
+ addr, amount = self.make_request(request_id)
+ except:
+ continue
+ l = self.subscriptions.get(addr, [])
+ l.append((ws, amount))
+ self.subscriptions[addr] = l
+ self.network.subscribe_to_addresses([addr], self.response_queue.put)
+
+ def run(self):
+ threading.Thread(target=self.reading_thread).start()
+ while self.is_running():
+ try:
+ r = self.response_queue.get(timeout=0.1)
+ except queue.Empty:
+ continue
+ util.print_error('response', r)
+ method = r.get('method')
+ result = r.get('result')
+ if result is None:
+ continue
+ if method == 'blockchain.scripthash.subscribe':
+ addr = r.get('params')[0]
+ scripthash = bitcoin.address_to_scripthash(addr)
+ self.network.get_balance_for_scripthash(
+ scripthash, self.response_queue.put)
+ elif method == 'blockchain.scripthash.get_balance':
+ scripthash = r.get('params')[0]
+ addr = self.network.h2addr.get(scripthash, None)
+ if addr is None:
+ util.print_error(
+ "can't find address for scripthash: %s" % scripthash)
+ l = self.subscriptions.get(addr, [])
+ for ws, amount in l:
+ if not ws.closed:
+ if sum(result.values()) >=amount:
+ ws.sendMessage('paid')
+
+
+
+class WebSocketServer(threading.Thread):
+
+ def __init__(self, config, ns):
+ threading.Thread.__init__(self)
+ self.config = config
+ self.net_server = ns
+ self.daemon = True
+
+ def run(self):
+ t = WsClientThread(self.config, self.net_server)
+ t.start()
+
+ host = self.config.get('websocket_server')
+ port = self.config.get('websocket_port', 9999)
+ certfile = self.config.get('ssl_chain')
+ keyfile = self.config.get('ssl_privkey')
+ self.server = SimpleSSLWebSocketServer(host, port, ElectrumWebSocket, certfile, keyfile)
+ self.server.serveforever()
+
+
diff --git a/electrum/wordlist/chinese_simplified.txt b/electrum/wordlist/chinese_simplified.txt
new file mode 100644
index 000000000..b90f1ed85
--- /dev/null
+++ b/electrum/wordlist/chinese_simplified.txt
@@ -0,0 +1,2048 @@
+的
+一
+是
+在
+不
+了
+有
+和
+人
+这
+中
+大
+为
+上
+个
+国
+我
+以
+要
+他
+时
+来
+用
+们
+生
+到
+作
+地
+于
+出
+就
+分
+对
+成
+会
+可
+主
+发
+年
+动
+同
+工
+也
+能
+下
+过
+子
+说
+产
+种
+面
+而
+方
+后
+多
+定
+行
+学
+法
+所
+民
+得
+经
+十
+三
+之
+进
+着
+等
+部
+度
+家
+电
+力
+里
+如
+水
+化
+高
+自
+二
+理
+起
+小
+物
+现
+实
+加
+量
+都
+两
+体
+制
+机
+当
+使
+点
+从
+业
+本
+去
+把
+性
+好
+应
+开
+它
+合
+还
+因
+由
+其
+些
+然
+前
+外
+天
+政
+四
+日
+那
+社
+义
+事
+平
+形
+相
+全
+表
+间
+样
+与
+关
+各
+重
+新
+线
+内
+数
+正
+心
+反
+你
+明
+看
+原
+又
+么
+利
+比
+或
+但
+质
+气
+第
+向
+道
+命
+此
+变
+条
+只
+没
+结
+解
+问
+意
+建
+月
+公
+无
+系
+军
+很
+情
+者
+最
+立
+代
+想
+已
+通
+并
+提
+直
+题
+党
+程
+展
+五
+果
+料
+象
+员
+革
+位
+入
+常
+文
+总
+次
+品
+式
+活
+设
+及
+管
+特
+件
+长
+求
+老
+头
+基
+资
+边
+流
+路
+级
+少
+图
+山
+统
+接
+知
+较
+将
+组
+见
+计
+别
+她
+手
+角
+期
+根
+论
+运
+农
+指
+几
+九
+区
+强
+放
+决
+西
+被
+干
+做
+必
+战
+先
+回
+则
+任
+取
+据
+处
+队
+南
+给
+色
+光
+门
+即
+保
+治
+北
+造
+百
+规
+热
+领
+七
+海
+口
+东
+导
+器
+压
+志
+世
+金
+增
+争
+济
+阶
+油
+思
+术
+极
+交
+受
+联
+什
+认
+六
+共
+权
+收
+证
+改
+清
+美
+再
+采
+转
+更
+单
+风
+切
+打
+白
+教
+速
+花
+带
+安
+场
+身
+车
+例
+真
+务
+具
+万
+每
+目
+至
+达
+走
+积
+示
+议
+声
+报
+斗
+完
+类
+八
+离
+华
+名
+确
+才
+科
+张
+信
+马
+节
+话
+米
+整
+空
+元
+况
+今
+集
+温
+传
+土
+许
+步
+群
+广
+石
+记
+需
+段
+研
+界
+拉
+林
+律
+叫
+且
+究
+观
+越
+织
+装
+影
+算
+低
+持
+音
+众
+书
+布
+复
+容
+儿
+须
+际
+商
+非
+验
+连
+断
+深
+难
+近
+矿
+千
+周
+委
+素
+技
+备
+半
+办
+青
+省
+列
+习
+响
+约
+支
+般
+史
+感
+劳
+便
+团
+往
+酸
+历
+市
+克
+何
+除
+消
+构
+府
+称
+太
+准
+精
+值
+号
+率
+族
+维
+划
+选
+标
+写
+存
+候
+毛
+亲
+快
+效
+斯
+院
+查
+江
+型
+眼
+王
+按
+格
+养
+易
+置
+派
+层
+片
+始
+却
+专
+状
+育
+厂
+京
+识
+适
+属
+圆
+包
+火
+住
+调
+满
+县
+局
+照
+参
+红
+细
+引
+听
+该
+铁
+价
+严
+首
+底
+液
+官
+德
+随
+病
+苏
+失
+尔
+死
+讲
+配
+女
+黄
+推
+显
+谈
+罪
+神
+艺
+呢
+席
+含
+企
+望
+密
+批
+营
+项
+防
+举
+球
+英
+氧
+势
+告
+李
+台
+落
+木
+帮
+轮
+破
+亚
+师
+围
+注
+远
+字
+材
+排
+供
+河
+态
+封
+另
+施
+减
+树
+溶
+怎
+止
+案
+言
+士
+均
+武
+固
+叶
+鱼
+波
+视
+仅
+费
+紧
+爱
+左
+章
+早
+朝
+害
+续
+轻
+服
+试
+食
+充
+兵
+源
+判
+护
+司
+足
+某
+练
+差
+致
+板
+田
+降
+黑
+犯
+负
+击
+范
+继
+兴
+似
+余
+坚
+曲
+输
+修
+故
+城
+夫
+够
+送
+笔
+船
+占
+右
+财
+吃
+富
+春
+职
+觉
+汉
+画
+功
+巴
+跟
+虽
+杂
+飞
+检
+吸
+助
+升
+阳
+互
+初
+创
+抗
+考
+投
+坏
+策
+古
+径
+换
+未
+跑
+留
+钢
+曾
+端
+责
+站
+简
+述
+钱
+副
+尽
+帝
+射
+草
+冲
+承
+独
+令
+限
+阿
+宣
+环
+双
+请
+超
+微
+让
+控
+州
+良
+轴
+找
+否
+纪
+益
+依
+优
+顶
+础
+载
+倒
+房
+突
+坐
+粉
+敌
+略
+客
+袁
+冷
+胜
+绝
+析
+块
+剂
+测
+丝
+协
+诉
+念
+陈
+仍
+罗
+盐
+友
+洋
+错
+苦
+夜
+刑
+移
+频
+逐
+靠
+混
+母
+短
+皮
+终
+聚
+汽
+村
+云
+哪
+既
+距
+卫
+停
+烈
+央
+察
+烧
+迅
+境
+若
+印
+洲
+刻
+括
+激
+孔
+搞
+甚
+室
+待
+核
+校
+散
+侵
+吧
+甲
+游
+久
+菜
+味
+旧
+模
+湖
+货
+损
+预
+阻
+毫
+普
+稳
+乙
+妈
+植
+息
+扩
+银
+语
+挥
+酒
+守
+拿
+序
+纸
+医
+缺
+雨
+吗
+针
+刘
+啊
+急
+唱
+误
+训
+愿
+审
+附
+获
+茶
+鲜
+粮
+斤
+孩
+脱
+硫
+肥
+善
+龙
+演
+父
+渐
+血
+欢
+械
+掌
+歌
+沙
+刚
+攻
+谓
+盾
+讨
+晚
+粒
+乱
+燃
+矛
+乎
+杀
+药
+宁
+鲁
+贵
+钟
+煤
+读
+班
+伯
+香
+介
+迫
+句
+丰
+培
+握
+兰
+担
+弦
+蛋
+沉
+假
+穿
+执
+答
+乐
+谁
+顺
+烟
+缩
+征
+脸
+喜
+松
+脚
+困
+异
+免
+背
+星
+福
+买
+染
+井
+概
+慢
+怕
+磁
+倍
+祖
+皇
+促
+静
+补
+评
+翻
+肉
+践
+尼
+衣
+宽
+扬
+棉
+希
+伤
+操
+垂
+秋
+宜
+氢
+套
+督
+振
+架
+亮
+末
+宪
+庆
+编
+牛
+触
+映
+雷
+销
+诗
+座
+居
+抓
+裂
+胞
+呼
+娘
+景
+威
+绿
+晶
+厚
+盟
+衡
+鸡
+孙
+延
+危
+胶
+屋
+乡
+临
+陆
+顾
+掉
+呀
+灯
+岁
+措
+束
+耐
+剧
+玉
+赵
+跳
+哥
+季
+课
+凯
+胡
+额
+款
+绍
+卷
+齐
+伟
+蒸
+殖
+永
+宗
+苗
+川
+炉
+岩
+弱
+零
+杨
+奏
+沿
+露
+杆
+探
+滑
+镇
+饭
+浓
+航
+怀
+赶
+库
+夺
+伊
+灵
+税
+途
+灭
+赛
+归
+召
+鼓
+播
+盘
+裁
+险
+康
+唯
+录
+菌
+纯
+借
+糖
+盖
+横
+符
+私
+努
+堂
+域
+枪
+润
+幅
+哈
+竟
+熟
+虫
+泽
+脑
+壤
+碳
+欧
+遍
+侧
+寨
+敢
+彻
+虑
+斜
+薄
+庭
+纳
+弹
+饲
+伸
+折
+麦
+湿
+暗
+荷
+瓦
+塞
+床
+筑
+恶
+户
+访
+塔
+奇
+透
+梁
+刀
+旋
+迹
+卡
+氯
+遇
+份
+毒
+泥
+退
+洗
+摆
+灰
+彩
+卖
+耗
+夏
+择
+忙
+铜
+献
+硬
+予
+繁
+圈
+雪
+函
+亦
+抽
+篇
+阵
+阴
+丁
+尺
+追
+堆
+雄
+迎
+泛
+爸
+楼
+避
+谋
+吨
+野
+猪
+旗
+累
+偏
+典
+馆
+索
+秦
+脂
+潮
+爷
+豆
+忽
+托
+惊
+塑
+遗
+愈
+朱
+替
+纤
+粗
+倾
+尚
+痛
+楚
+谢
+奋
+购
+磨
+君
+池
+旁
+碎
+骨
+监
+捕
+弟
+暴
+割
+贯
+殊
+释
+词
+亡
+壁
+顿
+宝
+午
+尘
+闻
+揭
+炮
+残
+冬
+桥
+妇
+警
+综
+招
+吴
+付
+浮
+遭
+徐
+您
+摇
+谷
+赞
+箱
+隔
+订
+男
+吹
+园
+纷
+唐
+败
+宋
+玻
+巨
+耕
+坦
+荣
+闭
+湾
+键
+凡
+驻
+锅
+救
+恩
+剥
+凝
+碱
+齿
+截
+炼
+麻
+纺
+禁
+废
+盛
+版
+缓
+净
+睛
+昌
+婚
+涉
+筒
+嘴
+插
+岸
+朗
+庄
+街
+藏
+姑
+贸
+腐
+奴
+啦
+惯
+乘
+伙
+恢
+匀
+纱
+扎
+辩
+耳
+彪
+臣
+亿
+璃
+抵
+脉
+秀
+萨
+俄
+网
+舞
+店
+喷
+纵
+寸
+汗
+挂
+洪
+贺
+闪
+柬
+爆
+烯
+津
+稻
+墙
+软
+勇
+像
+滚
+厘
+蒙
+芳
+肯
+坡
+柱
+荡
+腿
+仪
+旅
+尾
+轧
+冰
+贡
+登
+黎
+削
+钻
+勒
+逃
+障
+氨
+郭
+峰
+币
+港
+伏
+轨
+亩
+毕
+擦
+莫
+刺
+浪
+秘
+援
+株
+健
+售
+股
+岛
+甘
+泡
+睡
+童
+铸
+汤
+阀
+休
+汇
+舍
+牧
+绕
+炸
+哲
+磷
+绩
+朋
+淡
+尖
+启
+陷
+柴
+呈
+徒
+颜
+泪
+稍
+忘
+泵
+蓝
+拖
+洞
+授
+镜
+辛
+壮
+锋
+贫
+虚
+弯
+摩
+泰
+幼
+廷
+尊
+窗
+纲
+弄
+隶
+疑
+氏
+宫
+姐
+震
+瑞
+怪
+尤
+琴
+循
+描
+膜
+违
+夹
+腰
+缘
+珠
+穷
+森
+枝
+竹
+沟
+催
+绳
+忆
+邦
+剩
+幸
+浆
+栏
+拥
+牙
+贮
+礼
+滤
+钠
+纹
+罢
+拍
+咱
+喊
+袖
+埃
+勤
+罚
+焦
+潜
+伍
+墨
+欲
+缝
+姓
+刊
+饱
+仿
+奖
+铝
+鬼
+丽
+跨
+默
+挖
+链
+扫
+喝
+袋
+炭
+污
+幕
+诸
+弧
+励
+梅
+奶
+洁
+灾
+舟
+鉴
+苯
+讼
+抱
+毁
+懂
+寒
+智
+埔
+寄
+届
+跃
+渡
+挑
+丹
+艰
+贝
+碰
+拔
+爹
+戴
+码
+梦
+芽
+熔
+赤
+渔
+哭
+敬
+颗
+奔
+铅
+仲
+虎
+稀
+妹
+乏
+珍
+申
+桌
+遵
+允
+隆
+螺
+仓
+魏
+锐
+晓
+氮
+兼
+隐
+碍
+赫
+拨
+忠
+肃
+缸
+牵
+抢
+博
+巧
+壳
+兄
+杜
+讯
+诚
+碧
+祥
+柯
+页
+巡
+矩
+悲
+灌
+龄
+伦
+票
+寻
+桂
+铺
+圣
+恐
+恰
+郑
+趣
+抬
+荒
+腾
+贴
+柔
+滴
+猛
+阔
+辆
+妻
+填
+撤
+储
+签
+闹
+扰
+紫
+砂
+递
+戏
+吊
+陶
+伐
+喂
+疗
+瓶
+婆
+抚
+臂
+摸
+忍
+虾
+蜡
+邻
+胸
+巩
+挤
+偶
+弃
+槽
+劲
+乳
+邓
+吉
+仁
+烂
+砖
+租
+乌
+舰
+伴
+瓜
+浅
+丙
+暂
+燥
+橡
+柳
+迷
+暖
+牌
+秧
+胆
+详
+簧
+踏
+瓷
+谱
+呆
+宾
+糊
+洛
+辉
+愤
+竞
+隙
+怒
+粘
+乃
+绪
+肩
+籍
+敏
+涂
+熙
+皆
+侦
+悬
+掘
+享
+纠
+醒
+狂
+锁
+淀
+恨
+牲
+霸
+爬
+赏
+逆
+玩
+陵
+祝
+秒
+浙
+貌
+役
+彼
+悉
+鸭
+趋
+凤
+晨
+畜
+辈
+秩
+卵
+署
+梯
+炎
+滩
+棋
+驱
+筛
+峡
+冒
+啥
+寿
+译
+浸
+泉
+帽
+迟
+硅
+疆
+贷
+漏
+稿
+冠
+嫩
+胁
+芯
+牢
+叛
+蚀
+奥
+鸣
+岭
+羊
+凭
+串
+塘
+绘
+酵
+融
+盆
+锡
+庙
+筹
+冻
+辅
+摄
+袭
+筋
+拒
+僚
+旱
+钾
+鸟
+漆
+沈
+眉
+疏
+添
+棒
+穗
+硝
+韩
+逼
+扭
+侨
+凉
+挺
+碗
+栽
+炒
+杯
+患
+馏
+劝
+豪
+辽
+勃
+鸿
+旦
+吏
+拜
+狗
+埋
+辊
+掩
+饮
+搬
+骂
+辞
+勾
+扣
+估
+蒋
+绒
+雾
+丈
+朵
+姆
+拟
+宇
+辑
+陕
+雕
+偿
+蓄
+崇
+剪
+倡
+厅
+咬
+驶
+薯
+刷
+斥
+番
+赋
+奉
+佛
+浇
+漫
+曼
+扇
+钙
+桃
+扶
+仔
+返
+俗
+亏
+腔
+鞋
+棱
+覆
+框
+悄
+叔
+撞
+骗
+勘
+旺
+沸
+孤
+吐
+孟
+渠
+屈
+疾
+妙
+惜
+仰
+狠
+胀
+谐
+抛
+霉
+桑
+岗
+嘛
+衰
+盗
+渗
+脏
+赖
+涌
+甜
+曹
+阅
+肌
+哩
+厉
+烃
+纬
+毅
+昨
+伪
+症
+煮
+叹
+钉
+搭
+茎
+笼
+酷
+偷
+弓
+锥
+恒
+杰
+坑
+鼻
+翼
+纶
+叙
+狱
+逮
+罐
+络
+棚
+抑
+膨
+蔬
+寺
+骤
+穆
+冶
+枯
+册
+尸
+凸
+绅
+坯
+牺
+焰
+轰
+欣
+晋
+瘦
+御
+锭
+锦
+丧
+旬
+锻
+垄
+搜
+扑
+邀
+亭
+酯
+迈
+舒
+脆
+酶
+闲
+忧
+酚
+顽
+羽
+涨
+卸
+仗
+陪
+辟
+惩
+杭
+姚
+肚
+捉
+飘
+漂
+昆
+欺
+吾
+郎
+烷
+汁
+呵
+饰
+萧
+雅
+邮
+迁
+燕
+撒
+姻
+赴
+宴
+烦
+债
+帐
+斑
+铃
+旨
+醇
+董
+饼
+雏
+姿
+拌
+傅
+腹
+妥
+揉
+贤
+拆
+歪
+葡
+胺
+丢
+浩
+徽
+昂
+垫
+挡
+览
+贪
+慰
+缴
+汪
+慌
+冯
+诺
+姜
+谊
+凶
+劣
+诬
+耀
+昏
+躺
+盈
+骑
+乔
+溪
+丛
+卢
+抹
+闷
+咨
+刮
+驾
+缆
+悟
+摘
+铒
+掷
+颇
+幻
+柄
+惠
+惨
+佳
+仇
+腊
+窝
+涤
+剑
+瞧
+堡
+泼
+葱
+罩
+霍
+捞
+胎
+苍
+滨
+俩
+捅
+湘
+砍
+霞
+邵
+萄
+疯
+淮
+遂
+熊
+粪
+烘
+宿
+档
+戈
+驳
+嫂
+裕
+徙
+箭
+捐
+肠
+撑
+晒
+辨
+殿
+莲
+摊
+搅
+酱
+屏
+疫
+哀
+蔡
+堵
+沫
+皱
+畅
+叠
+阁
+莱
+敲
+辖
+钩
+痕
+坝
+巷
+饿
+祸
+丘
+玄
+溜
+曰
+逻
+彭
+尝
+卿
+妨
+艇
+吞
+韦
+怨
+矮
+歇
diff --git a/electrum/wordlist/english.txt b/electrum/wordlist/english.txt
new file mode 100644
index 000000000..942040ed5
--- /dev/null
+++ b/electrum/wordlist/english.txt
@@ -0,0 +1,2048 @@
+abandon
+ability
+able
+about
+above
+absent
+absorb
+abstract
+absurd
+abuse
+access
+accident
+account
+accuse
+achieve
+acid
+acoustic
+acquire
+across
+act
+action
+actor
+actress
+actual
+adapt
+add
+addict
+address
+adjust
+admit
+adult
+advance
+advice
+aerobic
+affair
+afford
+afraid
+again
+age
+agent
+agree
+ahead
+aim
+air
+airport
+aisle
+alarm
+album
+alcohol
+alert
+alien
+all
+alley
+allow
+almost
+alone
+alpha
+already
+also
+alter
+always
+amateur
+amazing
+among
+amount
+amused
+analyst
+anchor
+ancient
+anger
+angle
+angry
+animal
+ankle
+announce
+annual
+another
+answer
+antenna
+antique
+anxiety
+any
+apart
+apology
+appear
+apple
+approve
+april
+arch
+arctic
+area
+arena
+argue
+arm
+armed
+armor
+army
+around
+arrange
+arrest
+arrive
+arrow
+art
+artefact
+artist
+artwork
+ask
+aspect
+assault
+asset
+assist
+assume
+asthma
+athlete
+atom
+attack
+attend
+attitude
+attract
+auction
+audit
+august
+aunt
+author
+auto
+autumn
+average
+avocado
+avoid
+awake
+aware
+away
+awesome
+awful
+awkward
+axis
+baby
+bachelor
+bacon
+badge
+bag
+balance
+balcony
+ball
+bamboo
+banana
+banner
+bar
+barely
+bargain
+barrel
+base
+basic
+basket
+battle
+beach
+bean
+beauty
+because
+become
+beef
+before
+begin
+behave
+behind
+believe
+below
+belt
+bench
+benefit
+best
+betray
+better
+between
+beyond
+bicycle
+bid
+bike
+bind
+biology
+bird
+birth
+bitter
+black
+blade
+blame
+blanket
+blast
+bleak
+bless
+blind
+blood
+blossom
+blouse
+blue
+blur
+blush
+board
+boat
+body
+boil
+bomb
+bone
+bonus
+book
+boost
+border
+boring
+borrow
+boss
+bottom
+bounce
+box
+boy
+bracket
+brain
+brand
+brass
+brave
+bread
+breeze
+brick
+bridge
+brief
+bright
+bring
+brisk
+broccoli
+broken
+bronze
+broom
+brother
+brown
+brush
+bubble
+buddy
+budget
+buffalo
+build
+bulb
+bulk
+bullet
+bundle
+bunker
+burden
+burger
+burst
+bus
+business
+busy
+butter
+buyer
+buzz
+cabbage
+cabin
+cable
+cactus
+cage
+cake
+call
+calm
+camera
+camp
+can
+canal
+cancel
+candy
+cannon
+canoe
+canvas
+canyon
+capable
+capital
+captain
+car
+carbon
+card
+cargo
+carpet
+carry
+cart
+case
+cash
+casino
+castle
+casual
+cat
+catalog
+catch
+category
+cattle
+caught
+cause
+caution
+cave
+ceiling
+celery
+cement
+census
+century
+cereal
+certain
+chair
+chalk
+champion
+change
+chaos
+chapter
+charge
+chase
+chat
+cheap
+check
+cheese
+chef
+cherry
+chest
+chicken
+chief
+child
+chimney
+choice
+choose
+chronic
+chuckle
+chunk
+churn
+cigar
+cinnamon
+circle
+citizen
+city
+civil
+claim
+clap
+clarify
+claw
+clay
+clean
+clerk
+clever
+click
+client
+cliff
+climb
+clinic
+clip
+clock
+clog
+close
+cloth
+cloud
+clown
+club
+clump
+cluster
+clutch
+coach
+coast
+coconut
+code
+coffee
+coil
+coin
+collect
+color
+column
+combine
+come
+comfort
+comic
+common
+company
+concert
+conduct
+confirm
+congress
+connect
+consider
+control
+convince
+cook
+cool
+copper
+copy
+coral
+core
+corn
+correct
+cost
+cotton
+couch
+country
+couple
+course
+cousin
+cover
+coyote
+crack
+cradle
+craft
+cram
+crane
+crash
+crater
+crawl
+crazy
+cream
+credit
+creek
+crew
+cricket
+crime
+crisp
+critic
+crop
+cross
+crouch
+crowd
+crucial
+cruel
+cruise
+crumble
+crunch
+crush
+cry
+crystal
+cube
+culture
+cup
+cupboard
+curious
+current
+curtain
+curve
+cushion
+custom
+cute
+cycle
+dad
+damage
+damp
+dance
+danger
+daring
+dash
+daughter
+dawn
+day
+deal
+debate
+debris
+decade
+december
+decide
+decline
+decorate
+decrease
+deer
+defense
+define
+defy
+degree
+delay
+deliver
+demand
+demise
+denial
+dentist
+deny
+depart
+depend
+deposit
+depth
+deputy
+derive
+describe
+desert
+design
+desk
+despair
+destroy
+detail
+detect
+develop
+device
+devote
+diagram
+dial
+diamond
+diary
+dice
+diesel
+diet
+differ
+digital
+dignity
+dilemma
+dinner
+dinosaur
+direct
+dirt
+disagree
+discover
+disease
+dish
+dismiss
+disorder
+display
+distance
+divert
+divide
+divorce
+dizzy
+doctor
+document
+dog
+doll
+dolphin
+domain
+donate
+donkey
+donor
+door
+dose
+double
+dove
+draft
+dragon
+drama
+drastic
+draw
+dream
+dress
+drift
+drill
+drink
+drip
+drive
+drop
+drum
+dry
+duck
+dumb
+dune
+during
+dust
+dutch
+duty
+dwarf
+dynamic
+eager
+eagle
+early
+earn
+earth
+easily
+east
+easy
+echo
+ecology
+economy
+edge
+edit
+educate
+effort
+egg
+eight
+either
+elbow
+elder
+electric
+elegant
+element
+elephant
+elevator
+elite
+else
+embark
+embody
+embrace
+emerge
+emotion
+employ
+empower
+empty
+enable
+enact
+end
+endless
+endorse
+enemy
+energy
+enforce
+engage
+engine
+enhance
+enjoy
+enlist
+enough
+enrich
+enroll
+ensure
+enter
+entire
+entry
+envelope
+episode
+equal
+equip
+era
+erase
+erode
+erosion
+error
+erupt
+escape
+essay
+essence
+estate
+eternal
+ethics
+evidence
+evil
+evoke
+evolve
+exact
+example
+excess
+exchange
+excite
+exclude
+excuse
+execute
+exercise
+exhaust
+exhibit
+exile
+exist
+exit
+exotic
+expand
+expect
+expire
+explain
+expose
+express
+extend
+extra
+eye
+eyebrow
+fabric
+face
+faculty
+fade
+faint
+faith
+fall
+false
+fame
+family
+famous
+fan
+fancy
+fantasy
+farm
+fashion
+fat
+fatal
+father
+fatigue
+fault
+favorite
+feature
+february
+federal
+fee
+feed
+feel
+female
+fence
+festival
+fetch
+fever
+few
+fiber
+fiction
+field
+figure
+file
+film
+filter
+final
+find
+fine
+finger
+finish
+fire
+firm
+first
+fiscal
+fish
+fit
+fitness
+fix
+flag
+flame
+flash
+flat
+flavor
+flee
+flight
+flip
+float
+flock
+floor
+flower
+fluid
+flush
+fly
+foam
+focus
+fog
+foil
+fold
+follow
+food
+foot
+force
+forest
+forget
+fork
+fortune
+forum
+forward
+fossil
+foster
+found
+fox
+fragile
+frame
+frequent
+fresh
+friend
+fringe
+frog
+front
+frost
+frown
+frozen
+fruit
+fuel
+fun
+funny
+furnace
+fury
+future
+gadget
+gain
+galaxy
+gallery
+game
+gap
+garage
+garbage
+garden
+garlic
+garment
+gas
+gasp
+gate
+gather
+gauge
+gaze
+general
+genius
+genre
+gentle
+genuine
+gesture
+ghost
+giant
+gift
+giggle
+ginger
+giraffe
+girl
+give
+glad
+glance
+glare
+glass
+glide
+glimpse
+globe
+gloom
+glory
+glove
+glow
+glue
+goat
+goddess
+gold
+good
+goose
+gorilla
+gospel
+gossip
+govern
+gown
+grab
+grace
+grain
+grant
+grape
+grass
+gravity
+great
+green
+grid
+grief
+grit
+grocery
+group
+grow
+grunt
+guard
+guess
+guide
+guilt
+guitar
+gun
+gym
+habit
+hair
+half
+hammer
+hamster
+hand
+happy
+harbor
+hard
+harsh
+harvest
+hat
+have
+hawk
+hazard
+head
+health
+heart
+heavy
+hedgehog
+height
+hello
+helmet
+help
+hen
+hero
+hidden
+high
+hill
+hint
+hip
+hire
+history
+hobby
+hockey
+hold
+hole
+holiday
+hollow
+home
+honey
+hood
+hope
+horn
+horror
+horse
+hospital
+host
+hotel
+hour
+hover
+hub
+huge
+human
+humble
+humor
+hundred
+hungry
+hunt
+hurdle
+hurry
+hurt
+husband
+hybrid
+ice
+icon
+idea
+identify
+idle
+ignore
+ill
+illegal
+illness
+image
+imitate
+immense
+immune
+impact
+impose
+improve
+impulse
+inch
+include
+income
+increase
+index
+indicate
+indoor
+industry
+infant
+inflict
+inform
+inhale
+inherit
+initial
+inject
+injury
+inmate
+inner
+innocent
+input
+inquiry
+insane
+insect
+inside
+inspire
+install
+intact
+interest
+into
+invest
+invite
+involve
+iron
+island
+isolate
+issue
+item
+ivory
+jacket
+jaguar
+jar
+jazz
+jealous
+jeans
+jelly
+jewel
+job
+join
+joke
+journey
+joy
+judge
+juice
+jump
+jungle
+junior
+junk
+just
+kangaroo
+keen
+keep
+ketchup
+key
+kick
+kid
+kidney
+kind
+kingdom
+kiss
+kit
+kitchen
+kite
+kitten
+kiwi
+knee
+knife
+knock
+know
+lab
+label
+labor
+ladder
+lady
+lake
+lamp
+language
+laptop
+large
+later
+latin
+laugh
+laundry
+lava
+law
+lawn
+lawsuit
+layer
+lazy
+leader
+leaf
+learn
+leave
+lecture
+left
+leg
+legal
+legend
+leisure
+lemon
+lend
+length
+lens
+leopard
+lesson
+letter
+level
+liar
+liberty
+library
+license
+life
+lift
+light
+like
+limb
+limit
+link
+lion
+liquid
+list
+little
+live
+lizard
+load
+loan
+lobster
+local
+lock
+logic
+lonely
+long
+loop
+lottery
+loud
+lounge
+love
+loyal
+lucky
+luggage
+lumber
+lunar
+lunch
+luxury
+lyrics
+machine
+mad
+magic
+magnet
+maid
+mail
+main
+major
+make
+mammal
+man
+manage
+mandate
+mango
+mansion
+manual
+maple
+marble
+march
+margin
+marine
+market
+marriage
+mask
+mass
+master
+match
+material
+math
+matrix
+matter
+maximum
+maze
+meadow
+mean
+measure
+meat
+mechanic
+medal
+media
+melody
+melt
+member
+memory
+mention
+menu
+mercy
+merge
+merit
+merry
+mesh
+message
+metal
+method
+middle
+midnight
+milk
+million
+mimic
+mind
+minimum
+minor
+minute
+miracle
+mirror
+misery
+miss
+mistake
+mix
+mixed
+mixture
+mobile
+model
+modify
+mom
+moment
+monitor
+monkey
+monster
+month
+moon
+moral
+more
+morning
+mosquito
+mother
+motion
+motor
+mountain
+mouse
+move
+movie
+much
+muffin
+mule
+multiply
+muscle
+museum
+mushroom
+music
+must
+mutual
+myself
+mystery
+myth
+naive
+name
+napkin
+narrow
+nasty
+nation
+nature
+near
+neck
+need
+negative
+neglect
+neither
+nephew
+nerve
+nest
+net
+network
+neutral
+never
+news
+next
+nice
+night
+noble
+noise
+nominee
+noodle
+normal
+north
+nose
+notable
+note
+nothing
+notice
+novel
+now
+nuclear
+number
+nurse
+nut
+oak
+obey
+object
+oblige
+obscure
+observe
+obtain
+obvious
+occur
+ocean
+october
+odor
+off
+offer
+office
+often
+oil
+okay
+old
+olive
+olympic
+omit
+once
+one
+onion
+online
+only
+open
+opera
+opinion
+oppose
+option
+orange
+orbit
+orchard
+order
+ordinary
+organ
+orient
+original
+orphan
+ostrich
+other
+outdoor
+outer
+output
+outside
+oval
+oven
+over
+own
+owner
+oxygen
+oyster
+ozone
+pact
+paddle
+page
+pair
+palace
+palm
+panda
+panel
+panic
+panther
+paper
+parade
+parent
+park
+parrot
+party
+pass
+patch
+path
+patient
+patrol
+pattern
+pause
+pave
+payment
+peace
+peanut
+pear
+peasant
+pelican
+pen
+penalty
+pencil
+people
+pepper
+perfect
+permit
+person
+pet
+phone
+photo
+phrase
+physical
+piano
+picnic
+picture
+piece
+pig
+pigeon
+pill
+pilot
+pink
+pioneer
+pipe
+pistol
+pitch
+pizza
+place
+planet
+plastic
+plate
+play
+please
+pledge
+pluck
+plug
+plunge
+poem
+poet
+point
+polar
+pole
+police
+pond
+pony
+pool
+popular
+portion
+position
+possible
+post
+potato
+pottery
+poverty
+powder
+power
+practice
+praise
+predict
+prefer
+prepare
+present
+pretty
+prevent
+price
+pride
+primary
+print
+priority
+prison
+private
+prize
+problem
+process
+produce
+profit
+program
+project
+promote
+proof
+property
+prosper
+protect
+proud
+provide
+public
+pudding
+pull
+pulp
+pulse
+pumpkin
+punch
+pupil
+puppy
+purchase
+purity
+purpose
+purse
+push
+put
+puzzle
+pyramid
+quality
+quantum
+quarter
+question
+quick
+quit
+quiz
+quote
+rabbit
+raccoon
+race
+rack
+radar
+radio
+rail
+rain
+raise
+rally
+ramp
+ranch
+random
+range
+rapid
+rare
+rate
+rather
+raven
+raw
+razor
+ready
+real
+reason
+rebel
+rebuild
+recall
+receive
+recipe
+record
+recycle
+reduce
+reflect
+reform
+refuse
+region
+regret
+regular
+reject
+relax
+release
+relief
+rely
+remain
+remember
+remind
+remove
+render
+renew
+rent
+reopen
+repair
+repeat
+replace
+report
+require
+rescue
+resemble
+resist
+resource
+response
+result
+retire
+retreat
+return
+reunion
+reveal
+review
+reward
+rhythm
+rib
+ribbon
+rice
+rich
+ride
+ridge
+rifle
+right
+rigid
+ring
+riot
+ripple
+risk
+ritual
+rival
+river
+road
+roast
+robot
+robust
+rocket
+romance
+roof
+rookie
+room
+rose
+rotate
+rough
+round
+route
+royal
+rubber
+rude
+rug
+rule
+run
+runway
+rural
+sad
+saddle
+sadness
+safe
+sail
+salad
+salmon
+salon
+salt
+salute
+same
+sample
+sand
+satisfy
+satoshi
+sauce
+sausage
+save
+say
+scale
+scan
+scare
+scatter
+scene
+scheme
+school
+science
+scissors
+scorpion
+scout
+scrap
+screen
+script
+scrub
+sea
+search
+season
+seat
+second
+secret
+section
+security
+seed
+seek
+segment
+select
+sell
+seminar
+senior
+sense
+sentence
+series
+service
+session
+settle
+setup
+seven
+shadow
+shaft
+shallow
+share
+shed
+shell
+sheriff
+shield
+shift
+shine
+ship
+shiver
+shock
+shoe
+shoot
+shop
+short
+shoulder
+shove
+shrimp
+shrug
+shuffle
+shy
+sibling
+sick
+side
+siege
+sight
+sign
+silent
+silk
+silly
+silver
+similar
+simple
+since
+sing
+siren
+sister
+situate
+six
+size
+skate
+sketch
+ski
+skill
+skin
+skirt
+skull
+slab
+slam
+sleep
+slender
+slice
+slide
+slight
+slim
+slogan
+slot
+slow
+slush
+small
+smart
+smile
+smoke
+smooth
+snack
+snake
+snap
+sniff
+snow
+soap
+soccer
+social
+sock
+soda
+soft
+solar
+soldier
+solid
+solution
+solve
+someone
+song
+soon
+sorry
+sort
+soul
+sound
+soup
+source
+south
+space
+spare
+spatial
+spawn
+speak
+special
+speed
+spell
+spend
+sphere
+spice
+spider
+spike
+spin
+spirit
+split
+spoil
+sponsor
+spoon
+sport
+spot
+spray
+spread
+spring
+spy
+square
+squeeze
+squirrel
+stable
+stadium
+staff
+stage
+stairs
+stamp
+stand
+start
+state
+stay
+steak
+steel
+stem
+step
+stereo
+stick
+still
+sting
+stock
+stomach
+stone
+stool
+story
+stove
+strategy
+street
+strike
+strong
+struggle
+student
+stuff
+stumble
+style
+subject
+submit
+subway
+success
+such
+sudden
+suffer
+sugar
+suggest
+suit
+summer
+sun
+sunny
+sunset
+super
+supply
+supreme
+sure
+surface
+surge
+surprise
+surround
+survey
+suspect
+sustain
+swallow
+swamp
+swap
+swarm
+swear
+sweet
+swift
+swim
+swing
+switch
+sword
+symbol
+symptom
+syrup
+system
+table
+tackle
+tag
+tail
+talent
+talk
+tank
+tape
+target
+task
+taste
+tattoo
+taxi
+teach
+team
+tell
+ten
+tenant
+tennis
+tent
+term
+test
+text
+thank
+that
+theme
+then
+theory
+there
+they
+thing
+this
+thought
+three
+thrive
+throw
+thumb
+thunder
+ticket
+tide
+tiger
+tilt
+timber
+time
+tiny
+tip
+tired
+tissue
+title
+toast
+tobacco
+today
+toddler
+toe
+together
+toilet
+token
+tomato
+tomorrow
+tone
+tongue
+tonight
+tool
+tooth
+top
+topic
+topple
+torch
+tornado
+tortoise
+toss
+total
+tourist
+toward
+tower
+town
+toy
+track
+trade
+traffic
+tragic
+train
+transfer
+trap
+trash
+travel
+tray
+treat
+tree
+trend
+trial
+tribe
+trick
+trigger
+trim
+trip
+trophy
+trouble
+truck
+true
+truly
+trumpet
+trust
+truth
+try
+tube
+tuition
+tumble
+tuna
+tunnel
+turkey
+turn
+turtle
+twelve
+twenty
+twice
+twin
+twist
+two
+type
+typical
+ugly
+umbrella
+unable
+unaware
+uncle
+uncover
+under
+undo
+unfair
+unfold
+unhappy
+uniform
+unique
+unit
+universe
+unknown
+unlock
+until
+unusual
+unveil
+update
+upgrade
+uphold
+upon
+upper
+upset
+urban
+urge
+usage
+use
+used
+useful
+useless
+usual
+utility
+vacant
+vacuum
+vague
+valid
+valley
+valve
+van
+vanish
+vapor
+various
+vast
+vault
+vehicle
+velvet
+vendor
+venture
+venue
+verb
+verify
+version
+very
+vessel
+veteran
+viable
+vibrant
+vicious
+victory
+video
+view
+village
+vintage
+violin
+virtual
+virus
+visa
+visit
+visual
+vital
+vivid
+vocal
+voice
+void
+volcano
+volume
+vote
+voyage
+wage
+wagon
+wait
+walk
+wall
+walnut
+want
+warfare
+warm
+warrior
+wash
+wasp
+waste
+water
+wave
+way
+wealth
+weapon
+wear
+weasel
+weather
+web
+wedding
+weekend
+weird
+welcome
+west
+wet
+whale
+what
+wheat
+wheel
+when
+where
+whip
+whisper
+wide
+width
+wife
+wild
+will
+win
+window
+wine
+wing
+wink
+winner
+winter
+wire
+wisdom
+wise
+wish
+witness
+wolf
+woman
+wonder
+wood
+wool
+word
+work
+world
+worry
+worth
+wrap
+wreck
+wrestle
+wrist
+write
+wrong
+yard
+year
+yellow
+you
+young
+youth
+zebra
+zero
+zone
+zoo
diff --git a/electrum/wordlist/japanese.txt b/electrum/wordlist/japanese.txt
new file mode 100644
index 000000000..c4c9dca4e
--- /dev/null
+++ b/electrum/wordlist/japanese.txt
@@ -0,0 +1,2048 @@
+あいこくしん
+あいさつ
+あいだ
+あおぞら
+あかちゃん
+あきる
+あけがた
+あける
+あこがれる
+あさい
+あさひ
+あしあと
+あじわう
+あずかる
+あずき
+あそぶ
+あたえる
+あたためる
+あたりまえ
+あたる
+あつい
+あつかう
+あっしゅく
+あつまり
+あつめる
+あてな
+あてはまる
+あひる
+あぶら
+あぶる
+あふれる
+あまい
+あまど
+あまやかす
+あまり
+あみもの
+あめりか
+あやまる
+あゆむ
+あらいぐま
+あらし
+あらすじ
+あらためる
+あらゆる
+あらわす
+ありがとう
+あわせる
+あわてる
+あんい
+あんがい
+あんこ
+あんぜん
+あんてい
+あんない
+あんまり
+いいだす
+いおん
+いがい
+いがく
+いきおい
+いきなり
+いきもの
+いきる
+いくじ
+いくぶん
+いけばな
+いけん
+いこう
+いこく
+いこつ
+いさましい
+いさん
+いしき
+いじゅう
+いじょう
+いじわる
+いずみ
+いずれ
+いせい
+いせえび
+いせかい
+いせき
+いぜん
+いそうろう
+いそがしい
+いだい
+いだく
+いたずら
+いたみ
+いたりあ
+いちおう
+いちじ
+いちど
+いちば
+いちぶ
+いちりゅう
+いつか
+いっしゅん
+いっせい
+いっそう
+いったん
+いっち
+いってい
+いっぽう
+いてざ
+いてん
+いどう
+いとこ
+いない
+いなか
+いねむり
+いのち
+いのる
+いはつ
+いばる
+いはん
+いびき
+いひん
+いふく
+いへん
+いほう
+いみん
+いもうと
+いもたれ
+いもり
+いやがる
+いやす
+いよかん
+いよく
+いらい
+いらすと
+いりぐち
+いりょう
+いれい
+いれもの
+いれる
+いろえんぴつ
+いわい
+いわう
+いわかん
+いわば
+いわゆる
+いんげんまめ
+いんさつ
+いんしょう
+いんよう
+うえき
+うえる
+うおざ
+うがい
+うかぶ
+うかべる
+うきわ
+うくらいな
+うくれれ
+うけたまわる
+うけつけ
+うけとる
+うけもつ
+うける
+うごかす
+うごく
+うこん
+うさぎ
+うしなう
+うしろがみ
+うすい
+うすぎ
+うすぐらい
+うすめる
+うせつ
+うちあわせ
+うちがわ
+うちき
+うちゅう
+うっかり
+うつくしい
+うったえる
+うつる
+うどん
+うなぎ
+うなじ
+うなずく
+うなる
+うねる
+うのう
+うぶげ
+うぶごえ
+うまれる
+うめる
+うもう
+うやまう
+うよく
+うらがえす
+うらぐち
+うらない
+うりあげ
+うりきれ
+うるさい
+うれしい
+うれゆき
+うれる
+うろこ
+うわき
+うわさ
+うんこう
+うんちん
+うんてん
+うんどう
+えいえん
+えいが
+えいきょう
+えいご
+えいせい
+えいぶん
+えいよう
+えいわ
+えおり
+えがお
+えがく
+えきたい
+えくせる
+えしゃく
+えすて
+えつらん
+えのぐ
+えほうまき
+えほん
+えまき
+えもじ
+えもの
+えらい
+えらぶ
+えりあ
+えんえん
+えんかい
+えんぎ
+えんげき
+えんしゅう
+えんぜつ
+えんそく
+えんちょう
+えんとつ
+おいかける
+おいこす
+おいしい
+おいつく
+おうえん
+おうさま
+おうじ
+おうせつ
+おうたい
+おうふく
+おうべい
+おうよう
+おえる
+おおい
+おおう
+おおどおり
+おおや
+おおよそ
+おかえり
+おかず
+おがむ
+おかわり
+おぎなう
+おきる
+おくさま
+おくじょう
+おくりがな
+おくる
+おくれる
+おこす
+おこなう
+おこる
+おさえる
+おさない
+おさめる
+おしいれ
+おしえる
+おじぎ
+おじさん
+おしゃれ
+おそらく
+おそわる
+おたがい
+おたく
+おだやか
+おちつく
+おっと
+おつり
+おでかけ
+おとしもの
+おとなしい
+おどり
+おどろかす
+おばさん
+おまいり
+おめでとう
+おもいで
+おもう
+おもたい
+おもちゃ
+おやつ
+おやゆび
+およぼす
+おらんだ
+おろす
+おんがく
+おんけい
+おんしゃ
+おんせん
+おんだん
+おんちゅう
+おんどけい
+かあつ
+かいが
+がいき
+がいけん
+がいこう
+かいさつ
+かいしゃ
+かいすいよく
+かいぜん
+かいぞうど
+かいつう
+かいてん
+かいとう
+かいふく
+がいへき
+かいほう
+かいよう
+がいらい
+かいわ
+かえる
+かおり
+かかえる
+かがく
+かがし
+かがみ
+かくご
+かくとく
+かざる
+がぞう
+かたい
+かたち
+がちょう
+がっきゅう
+がっこう
+がっさん
+がっしょう
+かなざわし
+かのう
+がはく
+かぶか
+かほう
+かほご
+かまう
+かまぼこ
+かめれおん
+かゆい
+かようび
+からい
+かるい
+かろう
+かわく
+かわら
+がんか
+かんけい
+かんこう
+かんしゃ
+かんそう
+かんたん
+かんち
+がんばる
+きあい
+きあつ
+きいろ
+ぎいん
+きうい
+きうん
+きえる
+きおう
+きおく
+きおち
+きおん
+きかい
+きかく
+きかんしゃ
+ききて
+きくばり
+きくらげ
+きけんせい
+きこう
+きこえる
+きこく
+きさい
+きさく
+きさま
+きさらぎ
+ぎじかがく
+ぎしき
+ぎじたいけん
+ぎじにってい
+ぎじゅつしゃ
+きすう
+きせい
+きせき
+きせつ
+きそう
+きぞく
+きぞん
+きたえる
+きちょう
+きつえん
+ぎっちり
+きつつき
+きつね
+きてい
+きどう
+きどく
+きない
+きなが
+きなこ
+きぬごし
+きねん
+きのう
+きのした
+きはく
+きびしい
+きひん
+きふく
+きぶん
+きぼう
+きほん
+きまる
+きみつ
+きむずかしい
+きめる
+きもだめし
+きもち
+きもの
+きゃく
+きやく
+ぎゅうにく
+きよう
+きょうりゅう
+きらい
+きらく
+きりん
+きれい
+きれつ
+きろく
+ぎろん
+きわめる
+ぎんいろ
+きんかくじ
+きんじょ
+きんようび
+ぐあい
+くいず
+くうかん
+くうき
+くうぐん
+くうこう
+ぐうせい
+くうそう
+ぐうたら
+くうふく
+くうぼ
+くかん
+くきょう
+くげん
+ぐこう
+くさい
+くさき
+くさばな
+くさる
+くしゃみ
+くしょう
+くすのき
+くすりゆび
+くせげ
+くせん
+ぐたいてき
+くださる
+くたびれる
+くちこみ
+くちさき
+くつした
+ぐっすり
+くつろぐ
+くとうてん
+くどく
+くなん
+くねくね
+くのう
+くふう
+くみあわせ
+くみたてる
+くめる
+くやくしょ
+くらす
+くらべる
+くるま
+くれる
+くろう
+くわしい
+ぐんかん
+ぐんしょく
+ぐんたい
+ぐんて
+けあな
+けいかく
+けいけん
+けいこ
+けいさつ
+げいじゅつ
+けいたい
+げいのうじん
+けいれき
+けいろ
+けおとす
+けおりもの
+げきか
+げきげん
+げきだん
+げきちん
+げきとつ
+げきは
+げきやく
+げこう
+げこくじょう
+げざい
+けさき
+げざん
+けしき
+けしごむ
+けしょう
+げすと
+けたば
+けちゃっぷ
+けちらす
+けつあつ
+けつい
+けつえき
+けっこん
+けつじょ
+けっせき
+けってい
+けつまつ
+げつようび
+げつれい
+けつろん
+げどく
+けとばす
+けとる
+けなげ
+けなす
+けなみ
+けぬき
+げねつ
+けねん
+けはい
+げひん
+けぶかい
+げぼく
+けまり
+けみかる
+けむし
+けむり
+けもの
+けらい
+けろけろ
+けわしい
+けんい
+けんえつ
+けんお
+けんか
+げんき
+けんげん
+けんこう
+けんさく
+けんしゅう
+けんすう
+げんそう
+けんちく
+けんてい
+けんとう
+けんない
+けんにん
+げんぶつ
+けんま
+けんみん
+けんめい
+けんらん
+けんり
+こあくま
+こいぬ
+こいびと
+ごうい
+こうえん
+こうおん
+こうかん
+ごうきゅう
+ごうけい
+こうこう
+こうさい
+こうじ
+こうすい
+ごうせい
+こうそく
+こうたい
+こうちゃ
+こうつう
+こうてい
+こうどう
+こうない
+こうはい
+ごうほう
+ごうまん
+こうもく
+こうりつ
+こえる
+こおり
+ごかい
+ごがつ
+ごかん
+こくご
+こくさい
+こくとう
+こくない
+こくはく
+こぐま
+こけい
+こける
+ここのか
+こころ
+こさめ
+こしつ
+こすう
+こせい
+こせき
+こぜん
+こそだて
+こたい
+こたえる
+こたつ
+こちょう
+こっか
+こつこつ
+こつばん
+こつぶ
+こてい
+こてん
+ことがら
+ことし
+ことば
+ことり
+こなごな
+こねこね
+このまま
+このみ
+このよ
+ごはん
+こひつじ
+こふう
+こふん
+こぼれる
+ごまあぶら
+こまかい
+ごますり
+こまつな
+こまる
+こむぎこ
+こもじ
+こもち
+こもの
+こもん
+こやく
+こやま
+こゆう
+こゆび
+こよい
+こよう
+こりる
+これくしょん
+ころっけ
+こわもて
+こわれる
+こんいん
+こんかい
+こんき
+こんしゅう
+こんすい
+こんだて
+こんとん
+こんなん
+こんびに
+こんぽん
+こんまけ
+こんや
+こんれい
+こんわく
+ざいえき
+さいかい
+さいきん
+ざいげん
+ざいこ
+さいしょ
+さいせい
+ざいたく
+ざいちゅう
+さいてき
+ざいりょう
+さうな
+さかいし
+さがす
+さかな
+さかみち
+さがる
+さぎょう
+さくし
+さくひん
+さくら
+さこく
+さこつ
+さずかる
+ざせき
+さたん
+さつえい
+ざつおん
+ざっか
+ざつがく
+さっきょく
+ざっし
+さつじん
+ざっそう
+さつたば
+さつまいも
+さてい
+さといも
+さとう
+さとおや
+さとし
+さとる
+さのう
+さばく
+さびしい
+さべつ
+さほう
+さほど
+さます
+さみしい
+さみだれ
+さむけ
+さめる
+さやえんどう
+さゆう
+さよう
+さよく
+さらだ
+ざるそば
+さわやか
+さわる
+さんいん
+さんか
+さんきゃく
+さんこう
+さんさい
+ざんしょ
+さんすう
+さんせい
+さんそ
+さんち
+さんま
+さんみ
+さんらん
+しあい
+しあげ
+しあさって
+しあわせ
+しいく
+しいん
+しうち
+しえい
+しおけ
+しかい
+しかく
+じかん
+しごと
+しすう
+じだい
+したうけ
+したぎ
+したて
+したみ
+しちょう
+しちりん
+しっかり
+しつじ
+しつもん
+してい
+してき
+してつ
+じてん
+じどう
+しなぎれ
+しなもの
+しなん
+しねま
+しねん
+しのぐ
+しのぶ
+しはい
+しばかり
+しはつ
+しはらい
+しはん
+しひょう
+しふく
+じぶん
+しへい
+しほう
+しほん
+しまう
+しまる
+しみん
+しむける
+じむしょ
+しめい
+しめる
+しもん
+しゃいん
+しゃうん
+しゃおん
+じゃがいも
+しやくしょ
+しゃくほう
+しゃけん
+しゃこ
+しゃざい
+しゃしん
+しゃせん
+しゃそう
+しゃたい
+しゃちょう
+しゃっきん
+じゃま
+しゃりん
+しゃれい
+じゆう
+じゅうしょ
+しゅくはく
+じゅしん
+しゅっせき
+しゅみ
+しゅらば
+じゅんばん
+しょうかい
+しょくたく
+しょっけん
+しょどう
+しょもつ
+しらせる
+しらべる
+しんか
+しんこう
+じんじゃ
+しんせいじ
+しんちく
+しんりん
+すあげ
+すあし
+すあな
+ずあん
+すいえい
+すいか
+すいとう
+ずいぶん
+すいようび
+すうがく
+すうじつ
+すうせん
+すおどり
+すきま
+すくう
+すくない
+すける
+すごい
+すこし
+ずさん
+すずしい
+すすむ
+すすめる
+すっかり
+ずっしり
+ずっと
+すてき
+すてる
+すねる
+すのこ
+すはだ
+すばらしい
+ずひょう
+ずぶぬれ
+すぶり
+すふれ
+すべて
+すべる
+ずほう
+すぼん
+すまい
+すめし
+すもう
+すやき
+すらすら
+するめ
+すれちがう
+すろっと
+すわる
+すんぜん
+すんぽう
+せあぶら
+せいかつ
+せいげん
+せいじ
+せいよう
+せおう
+せかいかん
+せきにん
+せきむ
+せきゆ
+せきらんうん
+せけん
+せこう
+せすじ
+せたい
+せたけ
+せっかく
+せっきゃく
+ぜっく
+せっけん
+せっこつ
+せっさたくま
+せつぞく
+せつだん
+せつでん
+せっぱん
+せつび
+せつぶん
+せつめい
+せつりつ
+せなか
+せのび
+せはば
+せびろ
+せぼね
+せまい
+せまる
+せめる
+せもたれ
+せりふ
+ぜんあく
+せんい
+せんえい
+せんか
+せんきょ
+せんく
+せんげん
+ぜんご
+せんさい
+せんしゅ
+せんすい
+せんせい
+せんぞ
+せんたく
+せんちょう
+せんてい
+せんとう
+せんぬき
+せんねん
+せんぱい
+ぜんぶ
+ぜんぽう
+せんむ
+せんめんじょ
+せんもん
+せんやく
+せんゆう
+せんよう
+ぜんら
+ぜんりゃく
+せんれい
+せんろ
+そあく
+そいとげる
+そいね
+そうがんきょう
+そうき
+そうご
+そうしん
+そうだん
+そうなん
+そうび
+そうめん
+そうり
+そえもの
+そえん
+そがい
+そげき
+そこう
+そこそこ
+そざい
+そしな
+そせい
+そせん
+そそぐ
+そだてる
+そつう
+そつえん
+そっかん
+そつぎょう
+そっけつ
+そっこう
+そっせん
+そっと
+そとがわ
+そとづら
+そなえる
+そなた
+そふぼ
+そぼく
+そぼろ
+そまつ
+そまる
+そむく
+そむりえ
+そめる
+そもそも
+そよかぜ
+そらまめ
+そろう
+そんかい
+そんけい
+そんざい
+そんしつ
+そんぞく
+そんちょう
+ぞんび
+ぞんぶん
+そんみん
+たあい
+たいいん
+たいうん
+たいえき
+たいおう
+だいがく
+たいき
+たいぐう
+たいけん
+たいこ
+たいざい
+だいじょうぶ
+だいすき
+たいせつ
+たいそう
+だいたい
+たいちょう
+たいてい
+だいどころ
+たいない
+たいねつ
+たいのう
+たいはん
+だいひょう
+たいふう
+たいへん
+たいほ
+たいまつばな
+たいみんぐ
+たいむ
+たいめん
+たいやき
+たいよう
+たいら
+たいりょく
+たいる
+たいわん
+たうえ
+たえる
+たおす
+たおる
+たおれる
+たかい
+たかね
+たきび
+たくさん
+たこく
+たこやき
+たさい
+たしざん
+だじゃれ
+たすける
+たずさわる
+たそがれ
+たたかう
+たたく
+ただしい
+たたみ
+たちばな
+だっかい
+だっきゃく
+だっこ
+だっしゅつ
+だったい
+たてる
+たとえる
+たなばた
+たにん
+たぬき
+たのしみ
+たはつ
+たぶん
+たべる
+たぼう
+たまご
+たまる
+だむる
+ためいき
+ためす
+ためる
+たもつ
+たやすい
+たよる
+たらす
+たりきほんがん
+たりょう
+たりる
+たると
+たれる
+たれんと
+たろっと
+たわむれる
+だんあつ
+たんい
+たんおん
+たんか
+たんき
+たんけん
+たんご
+たんさん
+たんじょうび
+だんせい
+たんそく
+たんたい
+だんち
+たんてい
+たんとう
+だんな
+たんにん
+だんねつ
+たんのう
+たんぴん
+だんぼう
+たんまつ
+たんめい
+だんれつ
+だんろ
+だんわ
+ちあい
+ちあん
+ちいき
+ちいさい
+ちえん
+ちかい
+ちから
+ちきゅう
+ちきん
+ちけいず
+ちけん
+ちこく
+ちさい
+ちしき
+ちしりょう
+ちせい
+ちそう
+ちたい
+ちたん
+ちちおや
+ちつじょ
+ちてき
+ちてん
+ちぬき
+ちぬり
+ちのう
+ちひょう
+ちへいせん
+ちほう
+ちまた
+ちみつ
+ちみどろ
+ちめいど
+ちゃんこなべ
+ちゅうい
+ちゆりょく
+ちょうし
+ちょさくけん
+ちらし
+ちらみ
+ちりがみ
+ちりょう
+ちるど
+ちわわ
+ちんたい
+ちんもく
+ついか
+ついたち
+つうか
+つうじょう
+つうはん
+つうわ
+つかう
+つかれる
+つくね
+つくる
+つけね
+つける
+つごう
+つたえる
+つづく
+つつじ
+つつむ
+つとめる
+つながる
+つなみ
+つねづね
+つのる
+つぶす
+つまらない
+つまる
+つみき
+つめたい
+つもり
+つもる
+つよい
+つるぼ
+つるみく
+つわもの
+つわり
+てあし
+てあて
+てあみ
+ていおん
+ていか
+ていき
+ていけい
+ていこく
+ていさつ
+ていし
+ていせい
+ていたい
+ていど
+ていねい
+ていひょう
+ていへん
+ていぼう
+てうち
+ておくれ
+てきとう
+てくび
+でこぼこ
+てさぎょう
+てさげ
+てすり
+てそう
+てちがい
+てちょう
+てつがく
+てつづき
+でっぱ
+てつぼう
+てつや
+でぬかえ
+てぬき
+てぬぐい
+てのひら
+てはい
+てぶくろ
+てふだ
+てほどき
+てほん
+てまえ
+てまきずし
+てみじか
+てみやげ
+てらす
+てれび
+てわけ
+てわたし
+でんあつ
+てんいん
+てんかい
+てんき
+てんぐ
+てんけん
+てんごく
+てんさい
+てんし
+てんすう
+でんち
+てんてき
+てんとう
+てんない
+てんぷら
+てんぼうだい
+てんめつ
+てんらんかい
+でんりょく
+でんわ
+どあい
+といれ
+どうかん
+とうきゅう
+どうぐ
+とうし
+とうむぎ
+とおい
+とおか
+とおく
+とおす
+とおる
+とかい
+とかす
+ときおり
+ときどき
+とくい
+とくしゅう
+とくてん
+とくに
+とくべつ
+とけい
+とける
+とこや
+とさか
+としょかん
+とそう
+とたん
+とちゅう
+とっきゅう
+とっくん
+とつぜん
+とつにゅう
+とどける
+ととのえる
+とない
+となえる
+となり
+とのさま
+とばす
+どぶがわ
+とほう
+とまる
+とめる
+ともだち
+ともる
+どようび
+とらえる
+とんかつ
+どんぶり
+ないかく
+ないこう
+ないしょ
+ないす
+ないせん
+ないそう
+なおす
+ながい
+なくす
+なげる
+なこうど
+なさけ
+なたでここ
+なっとう
+なつやすみ
+ななおし
+なにごと
+なにもの
+なにわ
+なのか
+なふだ
+なまいき
+なまえ
+なまみ
+なみだ
+なめらか
+なめる
+なやむ
+ならう
+ならび
+ならぶ
+なれる
+なわとび
+なわばり
+にあう
+にいがた
+にうけ
+におい
+にかい
+にがて
+にきび
+にくしみ
+にくまん
+にげる
+にさんかたんそ
+にしき
+にせもの
+にちじょう
+にちようび
+にっか
+にっき
+にっけい
+にっこう
+にっさん
+にっしょく
+にっすう
+にっせき
+にってい
+になう
+にほん
+にまめ
+にもつ
+にやり
+にゅういん
+にりんしゃ
+にわとり
+にんい
+にんか
+にんき
+にんげん
+にんしき
+にんずう
+にんそう
+にんたい
+にんち
+にんてい
+にんにく
+にんぷ
+にんまり
+にんむ
+にんめい
+にんよう
+ぬいくぎ
+ぬかす
+ぬぐいとる
+ぬぐう
+ぬくもり
+ぬすむ
+ぬまえび
+ぬめり
+ぬらす
+ぬんちゃく
+ねあげ
+ねいき
+ねいる
+ねいろ
+ねぐせ
+ねくたい
+ねくら
+ねこぜ
+ねこむ
+ねさげ
+ねすごす
+ねそべる
+ねだん
+ねつい
+ねっしん
+ねつぞう
+ねったいぎょ
+ねぶそく
+ねふだ
+ねぼう
+ねほりはほり
+ねまき
+ねまわし
+ねみみ
+ねむい
+ねむたい
+ねもと
+ねらう
+ねわざ
+ねんいり
+ねんおし
+ねんかん
+ねんきん
+ねんぐ
+ねんざ
+ねんし
+ねんちゃく
+ねんど
+ねんぴ
+ねんぶつ
+ねんまつ
+ねんりょう
+ねんれい
+のいず
+のおづま
+のがす
+のきなみ
+のこぎり
+のこす
+のこる
+のせる
+のぞく
+のぞむ
+のたまう
+のちほど
+のっく
+のばす
+のはら
+のべる
+のぼる
+のみもの
+のやま
+のらいぬ
+のらねこ
+のりもの
+のりゆき
+のれん
+のんき
+ばあい
+はあく
+ばあさん
+ばいか
+ばいく
+はいけん
+はいご
+はいしん
+はいすい
+はいせん
+はいそう
+はいち
+ばいばい
+はいれつ
+はえる
+はおる
+はかい
+ばかり
+はかる
+はくしゅ
+はけん
+はこぶ
+はさみ
+はさん
+はしご
+ばしょ
+はしる
+はせる
+ぱそこん
+はそん
+はたん
+はちみつ
+はつおん
+はっかく
+はづき
+はっきり
+はっくつ
+はっけん
+はっこう
+はっさん
+はっしん
+はったつ
+はっちゅう
+はってん
+はっぴょう
+はっぽう
+はなす
+はなび
+はにかむ
+はぶらし
+はみがき
+はむかう
+はめつ
+はやい
+はやし
+はらう
+はろうぃん
+はわい
+はんい
+はんえい
+はんおん
+はんかく
+はんきょう
+ばんぐみ
+はんこ
+はんしゃ
+はんすう
+はんだん
+ぱんち
+ぱんつ
+はんてい
+はんとし
+はんのう
+はんぱ
+はんぶん
+はんぺん
+はんぼうき
+はんめい
+はんらん
+はんろん
+ひいき
+ひうん
+ひえる
+ひかく
+ひかり
+ひかる
+ひかん
+ひくい
+ひけつ
+ひこうき
+ひこく
+ひさい
+ひさしぶり
+ひさん
+びじゅつかん
+ひしょ
+ひそか
+ひそむ
+ひたむき
+ひだり
+ひたる
+ひつぎ
+ひっこし
+ひっし
+ひつじゅひん
+ひっす
+ひつぜん
+ぴったり
+ぴっちり
+ひつよう
+ひてい
+ひとごみ
+ひなまつり
+ひなん
+ひねる
+ひはん
+ひびく
+ひひょう
+ひほう
+ひまわり
+ひまん
+ひみつ
+ひめい
+ひめじし
+ひやけ
+ひやす
+ひよう
+びょうき
+ひらがな
+ひらく
+ひりつ
+ひりょう
+ひるま
+ひるやすみ
+ひれい
+ひろい
+ひろう
+ひろき
+ひろゆき
+ひんかく
+ひんけつ
+ひんこん
+ひんしゅ
+ひんそう
+ぴんち
+ひんぱん
+びんぼう
+ふあん
+ふいうち
+ふうけい
+ふうせん
+ぷうたろう
+ふうとう
+ふうふ
+ふえる
+ふおん
+ふかい
+ふきん
+ふくざつ
+ふくぶくろ
+ふこう
+ふさい
+ふしぎ
+ふじみ
+ふすま
+ふせい
+ふせぐ
+ふそく
+ぶたにく
+ふたん
+ふちょう
+ふつう
+ふつか
+ふっかつ
+ふっき
+ふっこく
+ぶどう
+ふとる
+ふとん
+ふのう
+ふはい
+ふひょう
+ふへん
+ふまん
+ふみん
+ふめつ
+ふめん
+ふよう
+ふりこ
+ふりる
+ふるい
+ふんいき
+ぶんがく
+ぶんぐ
+ふんしつ
+ぶんせき
+ふんそう
+ぶんぽう
+へいあん
+へいおん
+へいがい
+へいき
+へいげん
+へいこう
+へいさ
+へいしゃ
+へいせつ
+へいそ
+へいたく
+へいてん
+へいねつ
+へいわ
+へきが
+へこむ
+べにいろ
+べにしょうが
+へらす
+へんかん
+べんきょう
+べんごし
+へんさい
+へんたい
+べんり
+ほあん
+ほいく
+ぼうぎょ
+ほうこく
+ほうそう
+ほうほう
+ほうもん
+ほうりつ
+ほえる
+ほおん
+ほかん
+ほきょう
+ぼきん
+ほくろ
+ほけつ
+ほけん
+ほこう
+ほこる
+ほしい
+ほしつ
+ほしゅ
+ほしょう
+ほせい
+ほそい
+ほそく
+ほたて
+ほたる
+ぽちぶくろ
+ほっきょく
+ほっさ
+ほったん
+ほとんど
+ほめる
+ほんい
+ほんき
+ほんけ
+ほんしつ
+ほんやく
+まいにち
+まかい
+まかせる
+まがる
+まける
+まこと
+まさつ
+まじめ
+ますく
+まぜる
+まつり
+まとめ
+まなぶ
+まぬけ
+まねく
+まほう
+まもる
+まゆげ
+まよう
+まろやか
+まわす
+まわり
+まわる
+まんが
+まんきつ
+まんぞく
+まんなか
+みいら
+みうち
+みえる
+みがく
+みかた
+みかん
+みけん
+みこん
+みじかい
+みすい
+みすえる
+みせる
+みっか
+みつかる
+みつける
+みてい
+みとめる
+みなと
+みなみかさい
+みねらる
+みのう
+みのがす
+みほん
+みもと
+みやげ
+みらい
+みりょく
+みわく
+みんか
+みんぞく
+むいか
+むえき
+むえん
+むかい
+むかう
+むかえ
+むかし
+むぎちゃ
+むける
+むげん
+むさぼる
+むしあつい
+むしば
+むじゅん
+むしろ
+むすう
+むすこ
+むすぶ
+むすめ
+むせる
+むせん
+むちゅう
+むなしい
+むのう
+むやみ
+むよう
+むらさき
+むりょう
+むろん
+めいあん
+めいうん
+めいえん
+めいかく
+めいきょく
+めいさい
+めいし
+めいそう
+めいぶつ
+めいれい
+めいわく
+めぐまれる
+めざす
+めした
+めずらしい
+めだつ
+めまい
+めやす
+めんきょ
+めんせき
+めんどう
+もうしあげる
+もうどうけん
+もえる
+もくし
+もくてき
+もくようび
+もちろん
+もどる
+もらう
+もんく
+もんだい
+やおや
+やける
+やさい
+やさしい
+やすい
+やすたろう
+やすみ
+やせる
+やそう
+やたい
+やちん
+やっと
+やっぱり
+やぶる
+やめる
+ややこしい
+やよい
+やわらかい
+ゆうき
+ゆうびんきょく
+ゆうべ
+ゆうめい
+ゆけつ
+ゆしゅつ
+ゆせん
+ゆそう
+ゆたか
+ゆちゃく
+ゆでる
+ゆにゅう
+ゆびわ
+ゆらい
+ゆれる
+ようい
+ようか
+ようきゅう
+ようじ
+ようす
+ようちえん
+よかぜ
+よかん
+よきん
+よくせい
+よくぼう
+よけい
+よごれる
+よさん
+よしゅう
+よそう
+よそく
+よっか
+よてい
+よどがわく
+よねつ
+よやく
+よゆう
+よろこぶ
+よろしい
+らいう
+らくがき
+らくご
+らくさつ
+らくだ
+らしんばん
+らせん
+らぞく
+らたい
+らっか
+られつ
+りえき
+りかい
+りきさく
+りきせつ
+りくぐん
+りくつ
+りけん
+りこう
+りせい
+りそう
+りそく
+りてん
+りねん
+りゆう
+りゅうがく
+りよう
+りょうり
+りょかん
+りょくちゃ
+りょこう
+りりく
+りれき
+りろん
+りんご
+るいけい
+るいさい
+るいじ
+るいせき
+るすばん
+るりがわら
+れいかん
+れいぎ
+れいせい
+れいぞうこ
+れいとう
+れいぼう
+れきし
+れきだい
+れんあい
+れんけい
+れんこん
+れんさい
+れんしゅう
+れんぞく
+れんらく
+ろうか
+ろうご
+ろうじん
+ろうそく
+ろくが
+ろこつ
+ろじうら
+ろしゅつ
+ろせん
+ろてん
+ろめん
+ろれつ
+ろんぎ
+ろんぱ
+ろんぶん
+ろんり
+わかす
+わかめ
+わかやま
+わかれる
+わしつ
+わじまし
+わすれもの
+わらう
+われる
diff --git a/electrum/wordlist/portuguese.txt b/electrum/wordlist/portuguese.txt
new file mode 100644
index 000000000..394c88da2
--- /dev/null
+++ b/electrum/wordlist/portuguese.txt
@@ -0,0 +1,1654 @@
+# Copyright (c) 2014, The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+abaular
+abdominal
+abeto
+abissinio
+abjeto
+ablucao
+abnegar
+abotoar
+abrutalhar
+absurdo
+abutre
+acautelar
+accessorios
+acetona
+achocolatado
+acirrar
+acne
+acovardar
+acrostico
+actinomicete
+acustico
+adaptavel
+adeus
+adivinho
+adjunto
+admoestar
+adnominal
+adotivo
+adquirir
+adriatico
+adsorcao
+adutora
+advogar
+aerossol
+afazeres
+afetuoso
+afixo
+afluir
+afortunar
+afrouxar
+aftosa
+afunilar
+agentes
+agito
+aglutinar
+aiatola
+aimore
+aino
+aipo
+airoso
+ajeitar
+ajoelhar
+ajudante
+ajuste
+alazao
+albumina
+alcunha
+alegria
+alexandre
+alforriar
+alguns
+alhures
+alivio
+almoxarife
+alotropico
+alpiste
+alquimista
+alsaciano
+altura
+aluviao
+alvura
+amazonico
+ambulatorio
+ametodico
+amizades
+amniotico
+amovivel
+amurada
+anatomico
+ancorar
+anexo
+anfora
+aniversario
+anjo
+anotar
+ansioso
+anturio
+anuviar
+anverso
+anzol
+aonde
+apaziguar
+apito
+aplicavel
+apoteotico
+aprimorar
+aprumo
+apto
+apuros
+aquoso
+arauto
+arbusto
+arduo
+aresta
+arfar
+arguto
+aritmetico
+arlequim
+armisticio
+aromatizar
+arpoar
+arquivo
+arrumar
+arsenio
+arturiano
+aruaque
+arvores
+asbesto
+ascorbico
+aspirina
+asqueroso
+assustar
+astuto
+atazanar
+ativo
+atletismo
+atmosferico
+atormentar
+atroz
+aturdir
+audivel
+auferir
+augusto
+aula
+aumento
+aurora
+autuar
+avatar
+avexar
+avizinhar
+avolumar
+avulso
+axiomatico
+azerbaijano
+azimute
+azoto
+azulejo
+bacteriologista
+badulaque
+baforada
+baixote
+bajular
+balzaquiana
+bambuzal
+banzo
+baoba
+baqueta
+barulho
+bastonete
+batuta
+bauxita
+bavaro
+bazuca
+bcrepuscular
+beato
+beduino
+begonia
+behaviorista
+beisebol
+belzebu
+bemol
+benzido
+beocio
+bequer
+berro
+besuntar
+betume
+bexiga
+bezerro
+biatlon
+biboca
+bicuspide
+bidirecional
+bienio
+bifurcar
+bigorna
+bijuteria
+bimotor
+binormal
+bioxido
+bipolarizacao
+biquini
+birutice
+bisturi
+bituca
+biunivoco
+bivalve
+bizarro
+blasfemo
+blenorreia
+blindar
+bloqueio
+blusao
+boazuda
+bofete
+bojudo
+bolso
+bombordo
+bonzo
+botina
+boquiaberto
+bostoniano
+botulismo
+bourbon
+bovino
+boximane
+bravura
+brevidade
+britar
+broxar
+bruno
+bruxuleio
+bubonico
+bucolico
+buda
+budista
+bueiro
+buffer
+bugre
+bujao
+bumerangue
+burundines
+busto
+butique
+buzios
+caatinga
+cabuqui
+cacunda
+cafuzo
+cajueiro
+camurca
+canudo
+caquizeiro
+carvoeiro
+casulo
+catuaba
+cauterizar
+cebolinha
+cedula
+ceifeiro
+celulose
+cerzir
+cesto
+cetro
+ceus
+cevar
+chavena
+cheroqui
+chita
+chovido
+chuvoso
+ciatico
+cibernetico
+cicuta
+cidreira
+cientistas
+cifrar
+cigarro
+cilio
+cimo
+cinzento
+cioso
+cipriota
+cirurgico
+cisto
+citrico
+ciumento
+civismo
+clavicula
+clero
+clitoris
+cluster
+coaxial
+cobrir
+cocota
+codorniz
+coexistir
+cogumelo
+coito
+colusao
+compaixao
+comutativo
+contentamento
+convulsivo
+coordenativa
+coquetel
+correto
+corvo
+costureiro
+cotovia
+covil
+cozinheiro
+cretino
+cristo
+crivo
+crotalo
+cruzes
+cubo
+cucuia
+cueiro
+cuidar
+cujo
+cultural
+cunilingua
+cupula
+curvo
+custoso
+cutucar
+czarismo
+dablio
+dacota
+dados
+daguerreotipo
+daiquiri
+daltonismo
+damista
+dantesco
+daquilo
+darwinista
+dasein
+dativo
+deao
+debutantes
+decurso
+deduzir
+defunto
+degustar
+dejeto
+deltoide
+demover
+denunciar
+deputado
+deque
+dervixe
+desvirtuar
+deturpar
+deuteronomio
+devoto
+dextrose
+dezoito
+diatribe
+dicotomico
+didatico
+dietista
+difuso
+digressao
+diluvio
+diminuto
+dinheiro
+dinossauro
+dioxido
+diplomatico
+dique
+dirimivel
+disturbio
+diurno
+divulgar
+dizivel
+doar
+dobro
+docura
+dodoi
+doer
+dogue
+doloso
+domo
+donzela
+doping
+dorsal
+dossie
+dote
+doutro
+doze
+dravidico
+dreno
+driver
+dropes
+druso
+dubnio
+ducto
+dueto
+dulija
+dundum
+duodeno
+duquesa
+durou
+duvidoso
+duzia
+ebano
+ebrio
+eburneo
+echarpe
+eclusa
+ecossistema
+ectoplasma
+ecumenismo
+eczema
+eden
+editorial
+edredom
+edulcorar
+efetuar
+efigie
+efluvio
+egiptologo
+egresso
+egua
+einsteiniano
+eira
+eivar
+eixos
+ejetar
+elastomero
+eldorado
+elixir
+elmo
+eloquente
+elucidativo
+emaranhar
+embutir
+emerito
+emfa
+emitir
+emotivo
+empuxo
+emulsao
+enamorar
+encurvar
+enduro
+enevoar
+enfurnar
+enguico
+enho
+enigmista
+enlutar
+enormidade
+enpreendimento
+enquanto
+enriquecer
+enrugar
+entusiastico
+enunciar
+envolvimento
+enxuto
+enzimatico
+eolico
+epiteto
+epoxi
+epura
+equivoco
+erario
+erbio
+ereto
+erguido
+erisipela
+ermo
+erotizar
+erros
+erupcao
+ervilha
+esburacar
+escutar
+esfuziante
+esguio
+esloveno
+esmurrar
+esoterismo
+esperanca
+espirito
+espurio
+essencialmente
+esturricar
+esvoacar
+etario
+eterno
+etiquetar
+etnologo
+etos
+etrusco
+euclidiano
+euforico
+eugenico
+eunuco
+europio
+eustaquio
+eutanasia
+evasivo
+eventualidade
+evitavel
+evoluir
+exaustor
+excursionista
+exercito
+exfoliado
+exito
+exotico
+expurgo
+exsudar
+extrusora
+exumar
+fabuloso
+facultativo
+fado
+fagulha
+faixas
+fajuto
+faltoso
+famoso
+fanzine
+fapesp
+faquir
+fartura
+fastio
+faturista
+fausto
+favorito
+faxineira
+fazer
+fealdade
+febril
+fecundo
+fedorento
+feerico
+feixe
+felicidade
+felipe
+feltro
+femur
+fenotipo
+fervura
+festivo
+feto
+feudo
+fevereiro
+fezinha
+fiasco
+fibra
+ficticio
+fiduciario
+fiesp
+fifa
+figurino
+fijiano
+filtro
+finura
+fiorde
+fiquei
+firula
+fissurar
+fitoteca
+fivela
+fixo
+flavio
+flexor
+flibusteiro
+flotilha
+fluxograma
+fobos
+foco
+fofura
+foguista
+foie
+foliculo
+fominha
+fonte
+forum
+fosso
+fotossintese
+foxtrote
+fraudulento
+frevo
+frivolo
+frouxo
+frutose
+fuba
+fucsia
+fugitivo
+fuinha
+fujao
+fulustreco
+fumo
+funileiro
+furunculo
+fustigar
+futurologo
+fuxico
+fuzue
+gabriel
+gado
+gaelico
+gafieira
+gaguejo
+gaivota
+gajo
+galvanoplastico
+gamo
+ganso
+garrucha
+gastronomo
+gatuno
+gaussiano
+gaviao
+gaxeta
+gazeteiro
+gear
+geiser
+geminiano
+generoso
+genuino
+geossinclinal
+gerundio
+gestual
+getulista
+gibi
+gigolo
+gilete
+ginseng
+giroscopio
+glaucio
+glacial
+gleba
+glifo
+glote
+glutonia
+gnostico
+goela
+gogo
+goitaca
+golpista
+gomo
+gonzo
+gorro
+gostou
+goticula
+gourmet
+governo
+gozo
+graxo
+grevista
+grito
+grotesco
+gruta
+guaxinim
+gude
+gueto
+guizo
+guloso
+gume
+guru
+gustativo
+gustavo
+gutural
+habitue
+haitiano
+halterofilista
+hamburguer
+hanseniase
+happening
+harpista
+hastear
+haveres
+hebreu
+hectometro
+hedonista
+hegira
+helena
+helminto
+hemorroidas
+henrique
+heptassilabo
+hertziano
+hesitar
+heterossexual
+heuristico
+hexagono
+hiato
+hibrido
+hidrostatico
+hieroglifo
+hifenizar
+higienizar
+hilario
+himen
+hino
+hippie
+hirsuto
+historiografia
+hitlerista
+hodometro
+hoje
+holograma
+homus
+honroso
+hoquei
+horto
+hostilizar
+hotentote
+huguenote
+humilde
+huno
+hurra
+hutu
+iaia
+ialorixa
+iambico
+iansa
+iaque
+iara
+iatista
+iberico
+ibis
+icar
+iceberg
+icosagono
+idade
+ideologo
+idiotice
+idoso
+iemenita
+iene
+igarape
+iglu
+ignorar
+igreja
+iguaria
+iidiche
+ilativo
+iletrado
+ilharga
+ilimitado
+ilogismo
+ilustrissimo
+imaturo
+imbuzeiro
+imerso
+imitavel
+imovel
+imputar
+imutavel
+inaveriguavel
+incutir
+induzir
+inextricavel
+infusao
+ingua
+inhame
+iniquo
+injusto
+inning
+inoxidavel
+inquisitorial
+insustentavel
+intumescimento
+inutilizavel
+invulneravel
+inzoneiro
+iodo
+iogurte
+ioio
+ionosfera
+ioruba
+iota
+ipsilon
+irascivel
+iris
+irlandes
+irmaos
+iroques
+irrupcao
+isca
+isento
+islandes
+isotopo
+isqueiro
+israelita
+isso
+isto
+iterbio
+itinerario
+itrio
+iuane
+iugoslavo
+jabuticabeira
+jacutinga
+jade
+jagunco
+jainista
+jaleco
+jambo
+jantarada
+japones
+jaqueta
+jarro
+jasmim
+jato
+jaula
+javel
+jazz
+jegue
+jeitoso
+jejum
+jenipapo
+jeova
+jequitiba
+jersei
+jesus
+jetom
+jiboia
+jihad
+jilo
+jingle
+jipe
+jocoso
+joelho
+joguete
+joio
+jojoba
+jorro
+jota
+joule
+joviano
+jubiloso
+judoca
+jugular
+juizo
+jujuba
+juliano
+jumento
+junto
+jururu
+justo
+juta
+juventude
+labutar
+laguna
+laico
+lajota
+lanterninha
+lapso
+laquear
+lastro
+lauto
+lavrar
+laxativo
+lazer
+leasing
+lebre
+lecionar
+ledo
+leguminoso
+leitura
+lele
+lemure
+lento
+leonardo
+leopardo
+lepton
+leque
+leste
+letreiro
+leucocito
+levitico
+lexicologo
+lhama
+lhufas
+liame
+licoroso
+lidocaina
+liliputiano
+limusine
+linotipo
+lipoproteina
+liquidos
+lirismo
+lisura
+liturgico
+livros
+lixo
+lobulo
+locutor
+lodo
+logro
+lojista
+lombriga
+lontra
+loop
+loquaz
+lorota
+losango
+lotus
+louvor
+luar
+lubrificavel
+lucros
+lugubre
+luis
+luminoso
+luneta
+lustroso
+luto
+luvas
+luxuriante
+luzeiro
+maduro
+maestro
+mafioso
+magro
+maiuscula
+majoritario
+malvisto
+mamute
+manutencao
+mapoteca
+maquinista
+marzipa
+masturbar
+matuto
+mausoleu
+mavioso
+maxixe
+mazurca
+meandro
+mecha
+medusa
+mefistofelico
+megera
+meirinho
+melro
+memorizar
+menu
+mequetrefe
+mertiolate
+mestria
+metroviario
+mexilhao
+mezanino
+miau
+microssegundo
+midia
+migratorio
+mimosa
+minuto
+miosotis
+mirtilo
+misturar
+mitzvah
+miudos
+mixuruca
+mnemonico
+moagem
+mobilizar
+modulo
+moer
+mofo
+mogno
+moita
+molusco
+monumento
+moqueca
+morubixaba
+mostruario
+motriz
+mouse
+movivel
+mozarela
+muarra
+muculmano
+mudo
+mugir
+muitos
+mumunha
+munir
+muon
+muquira
+murros
+musselina
+nacoes
+nado
+naftalina
+nago
+naipe
+naja
+nalgum
+namoro
+nanquim
+napolitano
+naquilo
+nascimento
+nautilo
+navios
+nazista
+nebuloso
+nectarina
+nefrologo
+negus
+nelore
+nenufar
+nepotismo
+nervura
+neste
+netuno
+neutron
+nevoeiro
+newtoniano
+nexo
+nhenhenhem
+nhoque
+nigeriano
+niilista
+ninho
+niobio
+niponico
+niquelar
+nirvana
+nisto
+nitroglicerina
+nivoso
+nobreza
+nocivo
+noel
+nogueira
+noivo
+nojo
+nominativo
+nonuplo
+noruegues
+nostalgico
+noturno
+nouveau
+nuanca
+nublar
+nucleotideo
+nudista
+nulo
+numismatico
+nunquinha
+nupcias
+nutritivo
+nuvens
+oasis
+obcecar
+obeso
+obituario
+objetos
+oblongo
+obnoxio
+obrigatorio
+obstruir
+obtuso
+obus
+obvio
+ocaso
+occipital
+oceanografo
+ocioso
+oclusivo
+ocorrer
+ocre
+octogono
+odalisca
+odisseia
+odorifico
+oersted
+oeste
+ofertar
+ofidio
+oftalmologo
+ogiva
+ogum
+oigale
+oitavo
+oitocentos
+ojeriza
+olaria
+oleoso
+olfato
+olhos
+oliveira
+olmo
+olor
+olvidavel
+ombudsman
+omeleteira
+omitir
+omoplata
+onanismo
+ondular
+oneroso
+onomatopeico
+ontologico
+onus
+onze
+opalescente
+opcional
+operistico
+opio
+oposto
+oprobrio
+optometrista
+opusculo
+oratorio
+orbital
+orcar
+orfao
+orixa
+orla
+ornitologo
+orquidea
+ortorrombico
+orvalho
+osculo
+osmotico
+ossudo
+ostrogodo
+otario
+otite
+ouro
+ousar
+outubro
+ouvir
+ovario
+overnight
+oviparo
+ovni
+ovoviviparo
+ovulo
+oxala
+oxente
+oxiuro
+oxossi
+ozonizar
+paciente
+pactuar
+padronizar
+paete
+pagodeiro
+paixao
+pajem
+paludismo
+pampas
+panturrilha
+papudo
+paquistanes
+pastoso
+patua
+paulo
+pauzinhos
+pavoroso
+paxa
+pazes
+peao
+pecuniario
+pedunculo
+pegaso
+peixinho
+pejorativo
+pelvis
+penuria
+pequno
+petunia
+pezada
+piauiense
+pictorico
+pierro
+pigmeu
+pijama
+pilulas
+pimpolho
+pintura
+piorar
+pipocar
+piqueteiro
+pirulito
+pistoleiro
+pituitaria
+pivotar
+pixote
+pizzaria
+plistoceno
+plotar
+pluviometrico
+pneumonico
+poco
+podridao
+poetisa
+pogrom
+pois
+polvorosa
+pomposo
+ponderado
+pontudo
+populoso
+poquer
+porvir
+posudo
+potro
+pouso
+povoar
+prazo
+prezar
+privilegios
+proximo
+prussiano
+pseudopode
+psoriase
+pterossauros
+ptialina
+ptolemaico
+pudor
+pueril
+pufe
+pugilista
+puir
+pujante
+pulverizar
+pumba
+punk
+purulento
+pustula
+putsch
+puxe
+quatrocentos
+quetzal
+quixotesco
+quotizavel
+rabujice
+racista
+radonio
+rafia
+ragu
+rajado
+ralo
+rampeiro
+ranzinza
+raptor
+raquitismo
+raro
+rasurar
+ratoeira
+ravioli
+razoavel
+reavivar
+rebuscar
+recusavel
+reduzivel
+reexposicao
+refutavel
+regurgitar
+reivindicavel
+rejuvenescimento
+relva
+remuneravel
+renunciar
+reorientar
+repuxo
+requisito
+resumo
+returno
+reutilizar
+revolvido
+rezonear
+riacho
+ribossomo
+ricota
+ridiculo
+rifle
+rigoroso
+rijo
+rimel
+rins
+rios
+riqueza
+riquixa
+rissole
+ritualistico
+rivalizar
+rixa
+robusto
+rococo
+rodoviario
+roer
+rogo
+rojao
+rolo
+rompimento
+ronronar
+roqueiro
+rorqual
+rosto
+rotundo
+rouxinol
+roxo
+royal
+ruas
+rucula
+rudimentos
+ruela
+rufo
+rugoso
+ruivo
+rule
+rumoroso
+runico
+ruptura
+rural
+rustico
+rutilar
+saariano
+sabujo
+sacudir
+sadomasoquista
+safra
+sagui
+sais
+samurai
+santuario
+sapo
+saquear
+sartriano
+saturno
+saude
+sauva
+saveiro
+saxofonista
+sazonal
+scherzo
+script
+seara
+seborreia
+secura
+seduzir
+sefardim
+seguro
+seja
+selvas
+sempre
+senzala
+sepultura
+sequoia
+sestercio
+setuplo
+seus
+seviciar
+sezonismo
+shalom
+siames
+sibilante
+sicrano
+sidra
+sifilitico
+signos
+silvo
+simultaneo
+sinusite
+sionista
+sirio
+sisudo
+situar
+sivan
+slide
+slogan
+soar
+sobrio
+socratico
+sodomizar
+soerguer
+software
+sogro
+soja
+solver
+somente
+sonso
+sopro
+soquete
+sorveteiro
+sossego
+soturno
+sousafone
+sovinice
+sozinho
+suavizar
+subverter
+sucursal
+sudoriparo
+sufragio
+sugestoes
+suite
+sujo
+sultao
+sumula
+suntuoso
+suor
+supurar
+suruba
+susto
+suturar
+suvenir
+tabuleta
+taco
+tadjique
+tafeta
+tagarelice
+taitiano
+talvez
+tampouco
+tanzaniano
+taoista
+tapume
+taquion
+tarugo
+tascar
+tatuar
+tautologico
+tavola
+taxionomista
+tchecoslovaco
+teatrologo
+tectonismo
+tedioso
+teflon
+tegumento
+teixo
+telurio
+temporas
+tenue
+teosofico
+tepido
+tequila
+terrorista
+testosterona
+tetrico
+teutonico
+teve
+texugo
+tiara
+tibia
+tiete
+tifoide
+tigresa
+tijolo
+tilintar
+timpano
+tintureiro
+tiquete
+tiroteio
+tisico
+titulos
+tive
+toar
+toboga
+tofu
+togoles
+toicinho
+tolueno
+tomografo
+tontura
+toponimo
+toquio
+torvelinho
+tostar
+toto
+touro
+toxina
+trazer
+trezentos
+trivialidade
+trovoar
+truta
+tuaregue
+tubular
+tucano
+tudo
+tufo
+tuiste
+tulipa
+tumultuoso
+tunisino
+tupiniquim
+turvo
+tutu
+ucraniano
+udenista
+ufanista
+ufologo
+ugaritico
+uiste
+uivo
+ulceroso
+ulema
+ultravioleta
+umbilical
+umero
+umido
+umlaut
+unanimidade
+unesco
+ungulado
+unheiro
+univoco
+untuoso
+urano
+urbano
+urdir
+uretra
+urgente
+urinol
+urna
+urologo
+urro
+ursulina
+urtiga
+urupe
+usavel
+usbeque
+usei
+usineiro
+usurpar
+utero
+utilizar
+utopico
+uvular
+uxoricidio
+vacuo
+vadio
+vaguear
+vaivem
+valvula
+vampiro
+vantajoso
+vaporoso
+vaquinha
+varziano
+vasto
+vaticinio
+vaudeville
+vazio
+veado
+vedico
+veemente
+vegetativo
+veio
+veja
+veludo
+venusiano
+verdade
+verve
+vestuario
+vetusto
+vexatorio
+vezes
+viavel
+vibratorio
+victor
+vicunha
+vidros
+vietnamita
+vigoroso
+vilipendiar
+vime
+vintem
+violoncelo
+viquingue
+virus
+visualizar
+vituperio
+viuvo
+vivo
+vizir
+voar
+vociferar
+vodu
+vogar
+voile
+volver
+vomito
+vontade
+vortice
+vosso
+voto
+vovozinha
+voyeuse
+vozes
+vulva
+vupt
+western
+xadrez
+xale
+xampu
+xango
+xarope
+xaual
+xavante
+xaxim
+xenonio
+xepa
+xerox
+xicara
+xifopago
+xiita
+xilogravura
+xinxim
+xistoso
+xixi
+xodo
+xogum
+xucro
+zabumba
+zagueiro
+zambiano
+zanzar
+zarpar
+zebu
+zefiro
+zeloso
+zenite
+zumbi
diff --git a/electrum/wordlist/spanish.txt b/electrum/wordlist/spanish.txt
new file mode 100644
index 000000000..d0900c2c7
--- /dev/null
+++ b/electrum/wordlist/spanish.txt
@@ -0,0 +1,2048 @@
+ábaco
+abdomen
+abeja
+abierto
+abogado
+abono
+aborto
+abrazo
+abrir
+abuelo
+abuso
+acabar
+academia
+acceso
+acción
+aceite
+acelga
+acento
+aceptar
+ácido
+aclarar
+acné
+acoger
+acoso
+activo
+acto
+actriz
+actuar
+acudir
+acuerdo
+acusar
+adicto
+admitir
+adoptar
+adorno
+aduana
+adulto
+aéreo
+afectar
+afición
+afinar
+afirmar
+ágil
+agitar
+agonía
+agosto
+agotar
+agregar
+agrio
+agua
+agudo
+águila
+aguja
+ahogo
+ahorro
+aire
+aislar
+ajedrez
+ajeno
+ajuste
+alacrán
+alambre
+alarma
+alba
+álbum
+alcalde
+aldea
+alegre
+alejar
+alerta
+aleta
+alfiler
+alga
+algodón
+aliado
+aliento
+alivio
+alma
+almeja
+almíbar
+altar
+alteza
+altivo
+alto
+altura
+alumno
+alzar
+amable
+amante
+amapola
+amargo
+amasar
+ámbar
+ámbito
+ameno
+amigo
+amistad
+amor
+amparo
+amplio
+ancho
+anciano
+ancla
+andar
+andén
+anemia
+ángulo
+anillo
+ánimo
+anís
+anotar
+antena
+antiguo
+antojo
+anual
+anular
+anuncio
+añadir
+añejo
+año
+apagar
+aparato
+apetito
+apio
+aplicar
+apodo
+aporte
+apoyo
+aprender
+aprobar
+apuesta
+apuro
+arado
+araña
+arar
+árbitro
+árbol
+arbusto
+archivo
+arco
+arder
+ardilla
+arduo
+área
+árido
+aries
+armonía
+arnés
+aroma
+arpa
+arpón
+arreglo
+arroz
+arruga
+arte
+artista
+asa
+asado
+asalto
+ascenso
+asegurar
+aseo
+asesor
+asiento
+asilo
+asistir
+asno
+asombro
+áspero
+astilla
+astro
+astuto
+asumir
+asunto
+atajo
+ataque
+atar
+atento
+ateo
+ático
+atleta
+átomo
+atraer
+atroz
+atún
+audaz
+audio
+auge
+aula
+aumento
+ausente
+autor
+aval
+avance
+avaro
+ave
+avellana
+avena
+avestruz
+avión
+aviso
+ayer
+ayuda
+ayuno
+azafrán
+azar
+azote
+azúcar
+azufre
+azul
+baba
+babor
+bache
+bahía
+baile
+bajar
+balanza
+balcón
+balde
+bambú
+banco
+banda
+baño
+barba
+barco
+barniz
+barro
+báscula
+bastón
+basura
+batalla
+batería
+batir
+batuta
+baúl
+bazar
+bebé
+bebida
+bello
+besar
+beso
+bestia
+bicho
+bien
+bingo
+blanco
+bloque
+blusa
+boa
+bobina
+bobo
+boca
+bocina
+boda
+bodega
+boina
+bola
+bolero
+bolsa
+bomba
+bondad
+bonito
+bono
+bonsái
+borde
+borrar
+bosque
+bote
+botín
+bóveda
+bozal
+bravo
+brazo
+brecha
+breve
+brillo
+brinco
+brisa
+broca
+broma
+bronce
+brote
+bruja
+brusco
+bruto
+buceo
+bucle
+bueno
+buey
+bufanda
+bufón
+búho
+buitre
+bulto
+burbuja
+burla
+burro
+buscar
+butaca
+buzón
+caballo
+cabeza
+cabina
+cabra
+cacao
+cadáver
+cadena
+caer
+café
+caída
+caimán
+caja
+cajón
+cal
+calamar
+calcio
+caldo
+calidad
+calle
+calma
+calor
+calvo
+cama
+cambio
+camello
+camino
+campo
+cáncer
+candil
+canela
+canguro
+canica
+canto
+caña
+cañón
+caoba
+caos
+capaz
+capitán
+capote
+captar
+capucha
+cara
+carbón
+cárcel
+careta
+carga
+cariño
+carne
+carpeta
+carro
+carta
+casa
+casco
+casero
+caspa
+castor
+catorce
+catre
+caudal
+causa
+cazo
+cebolla
+ceder
+cedro
+celda
+célebre
+celoso
+célula
+cemento
+ceniza
+centro
+cerca
+cerdo
+cereza
+cero
+cerrar
+certeza
+césped
+cetro
+chacal
+chaleco
+champú
+chancla
+chapa
+charla
+chico
+chiste
+chivo
+choque
+choza
+chuleta
+chupar
+ciclón
+ciego
+cielo
+cien
+cierto
+cifra
+cigarro
+cima
+cinco
+cine
+cinta
+ciprés
+circo
+ciruela
+cisne
+cita
+ciudad
+clamor
+clan
+claro
+clase
+clave
+cliente
+clima
+clínica
+cobre
+cocción
+cochino
+cocina
+coco
+código
+codo
+cofre
+coger
+cohete
+cojín
+cojo
+cola
+colcha
+colegio
+colgar
+colina
+collar
+colmo
+columna
+combate
+comer
+comida
+cómodo
+compra
+conde
+conejo
+conga
+conocer
+consejo
+contar
+copa
+copia
+corazón
+corbata
+corcho
+cordón
+corona
+correr
+coser
+cosmos
+costa
+cráneo
+cráter
+crear
+crecer
+creído
+crema
+cría
+crimen
+cripta
+crisis
+cromo
+crónica
+croqueta
+crudo
+cruz
+cuadro
+cuarto
+cuatro
+cubo
+cubrir
+cuchara
+cuello
+cuento
+cuerda
+cuesta
+cueva
+cuidar
+culebra
+culpa
+culto
+cumbre
+cumplir
+cuna
+cuneta
+cuota
+cupón
+cúpula
+curar
+curioso
+curso
+curva
+cutis
+dama
+danza
+dar
+dardo
+dátil
+deber
+débil
+década
+decir
+dedo
+defensa
+definir
+dejar
+delfín
+delgado
+delito
+demora
+denso
+dental
+deporte
+derecho
+derrota
+desayuno
+deseo
+desfile
+desnudo
+destino
+desvío
+detalle
+detener
+deuda
+día
+diablo
+diadema
+diamante
+diana
+diario
+dibujo
+dictar
+diente
+dieta
+diez
+difícil
+digno
+dilema
+diluir
+dinero
+directo
+dirigir
+disco
+diseño
+disfraz
+diva
+divino
+doble
+doce
+dolor
+domingo
+don
+donar
+dorado
+dormir
+dorso
+dos
+dosis
+dragón
+droga
+ducha
+duda
+duelo
+dueño
+dulce
+dúo
+duque
+durar
+dureza
+duro
+ébano
+ebrio
+echar
+eco
+ecuador
+edad
+edición
+edificio
+editor
+educar
+efecto
+eficaz
+eje
+ejemplo
+elefante
+elegir
+elemento
+elevar
+elipse
+élite
+elixir
+elogio
+eludir
+embudo
+emitir
+emoción
+empate
+empeño
+empleo
+empresa
+enano
+encargo
+enchufe
+encía
+enemigo
+enero
+enfado
+enfermo
+engaño
+enigma
+enlace
+enorme
+enredo
+ensayo
+enseñar
+entero
+entrar
+envase
+envío
+época
+equipo
+erizo
+escala
+escena
+escolar
+escribir
+escudo
+esencia
+esfera
+esfuerzo
+espada
+espejo
+espía
+esposa
+espuma
+esquí
+estar
+este
+estilo
+estufa
+etapa
+eterno
+ética
+etnia
+evadir
+evaluar
+evento
+evitar
+exacto
+examen
+exceso
+excusa
+exento
+exigir
+exilio
+existir
+éxito
+experto
+explicar
+exponer
+extremo
+fábrica
+fábula
+fachada
+fácil
+factor
+faena
+faja
+falda
+fallo
+falso
+faltar
+fama
+familia
+famoso
+faraón
+farmacia
+farol
+farsa
+fase
+fatiga
+fauna
+favor
+fax
+febrero
+fecha
+feliz
+feo
+feria
+feroz
+fértil
+fervor
+festín
+fiable
+fianza
+fiar
+fibra
+ficción
+ficha
+fideo
+fiebre
+fiel
+fiera
+fiesta
+figura
+fijar
+fijo
+fila
+filete
+filial
+filtro
+fin
+finca
+fingir
+finito
+firma
+flaco
+flauta
+flecha
+flor
+flota
+fluir
+flujo
+flúor
+fobia
+foca
+fogata
+fogón
+folio
+folleto
+fondo
+forma
+forro
+fortuna
+forzar
+fosa
+foto
+fracaso
+frágil
+franja
+frase
+fraude
+freír
+freno
+fresa
+frío
+frito
+fruta
+fuego
+fuente
+fuerza
+fuga
+fumar
+función
+funda
+furgón
+furia
+fusil
+fútbol
+futuro
+gacela
+gafas
+gaita
+gajo
+gala
+galería
+gallo
+gamba
+ganar
+gancho
+ganga
+ganso
+garaje
+garza
+gasolina
+gastar
+gato
+gavilán
+gemelo
+gemir
+gen
+género
+genio
+gente
+geranio
+gerente
+germen
+gesto
+gigante
+gimnasio
+girar
+giro
+glaciar
+globo
+gloria
+gol
+golfo
+goloso
+golpe
+goma
+gordo
+gorila
+gorra
+gota
+goteo
+gozar
+grada
+gráfico
+grano
+grasa
+gratis
+grave
+grieta
+grillo
+gripe
+gris
+grito
+grosor
+grúa
+grueso
+grumo
+grupo
+guante
+guapo
+guardia
+guerra
+guía
+guiño
+guion
+guiso
+guitarra
+gusano
+gustar
+haber
+hábil
+hablar
+hacer
+hacha
+hada
+hallar
+hamaca
+harina
+haz
+hazaña
+hebilla
+hebra
+hecho
+helado
+helio
+hembra
+herir
+hermano
+héroe
+hervir
+hielo
+hierro
+hígado
+higiene
+hijo
+himno
+historia
+hocico
+hogar
+hoguera
+hoja
+hombre
+hongo
+honor
+honra
+hora
+hormiga
+horno
+hostil
+hoyo
+hueco
+huelga
+huerta
+hueso
+huevo
+huida
+huir
+humano
+húmedo
+humilde
+humo
+hundir
+huracán
+hurto
+icono
+ideal
+idioma
+ídolo
+iglesia
+iglú
+igual
+ilegal
+ilusión
+imagen
+imán
+imitar
+impar
+imperio
+imponer
+impulso
+incapaz
+índice
+inerte
+infiel
+informe
+ingenio
+inicio
+inmenso
+inmune
+innato
+insecto
+instante
+interés
+íntimo
+intuir
+inútil
+invierno
+ira
+iris
+ironía
+isla
+islote
+jabalí
+jabón
+jamón
+jarabe
+jardín
+jarra
+jaula
+jazmín
+jefe
+jeringa
+jinete
+jornada
+joroba
+joven
+joya
+juerga
+jueves
+juez
+jugador
+jugo
+juguete
+juicio
+junco
+jungla
+junio
+juntar
+júpiter
+jurar
+justo
+juvenil
+juzgar
+kilo
+koala
+labio
+lacio
+lacra
+lado
+ladrón
+lagarto
+lágrima
+laguna
+laico
+lamer
+lámina
+lámpara
+lana
+lancha
+langosta
+lanza
+lápiz
+largo
+larva
+lástima
+lata
+látex
+latir
+laurel
+lavar
+lazo
+leal
+lección
+leche
+lector
+leer
+legión
+legumbre
+lejano
+lengua
+lento
+leña
+león
+leopardo
+lesión
+letal
+letra
+leve
+leyenda
+libertad
+libro
+licor
+líder
+lidiar
+lienzo
+liga
+ligero
+lima
+límite
+limón
+limpio
+lince
+lindo
+línea
+lingote
+lino
+linterna
+líquido
+liso
+lista
+litera
+litio
+litro
+llaga
+llama
+llanto
+llave
+llegar
+llenar
+llevar
+llorar
+llover
+lluvia
+lobo
+loción
+loco
+locura
+lógica
+logro
+lombriz
+lomo
+lonja
+lote
+lucha
+lucir
+lugar
+lujo
+luna
+lunes
+lupa
+lustro
+luto
+luz
+maceta
+macho
+madera
+madre
+maduro
+maestro
+mafia
+magia
+mago
+maíz
+maldad
+maleta
+malla
+malo
+mamá
+mambo
+mamut
+manco
+mando
+manejar
+manga
+maniquí
+manjar
+mano
+manso
+manta
+mañana
+mapa
+máquina
+mar
+marco
+marea
+marfil
+margen
+marido
+mármol
+marrón
+martes
+marzo
+masa
+máscara
+masivo
+matar
+materia
+matiz
+matriz
+máximo
+mayor
+mazorca
+mecha
+medalla
+medio
+médula
+mejilla
+mejor
+melena
+melón
+memoria
+menor
+mensaje
+mente
+menú
+mercado
+merengue
+mérito
+mes
+mesón
+meta
+meter
+método
+metro
+mezcla
+miedo
+miel
+miembro
+miga
+mil
+milagro
+militar
+millón
+mimo
+mina
+minero
+mínimo
+minuto
+miope
+mirar
+misa
+miseria
+misil
+mismo
+mitad
+mito
+mochila
+moción
+moda
+modelo
+moho
+mojar
+molde
+moler
+molino
+momento
+momia
+monarca
+moneda
+monja
+monto
+moño
+morada
+morder
+moreno
+morir
+morro
+morsa
+mortal
+mosca
+mostrar
+motivo
+mover
+móvil
+mozo
+mucho
+mudar
+mueble
+muela
+muerte
+muestra
+mugre
+mujer
+mula
+muleta
+multa
+mundo
+muñeca
+mural
+muro
+músculo
+museo
+musgo
+música
+muslo
+nácar
+nación
+nadar
+naipe
+naranja
+nariz
+narrar
+nasal
+natal
+nativo
+natural
+náusea
+naval
+nave
+navidad
+necio
+néctar
+negar
+negocio
+negro
+neón
+nervio
+neto
+neutro
+nevar
+nevera
+nicho
+nido
+niebla
+nieto
+niñez
+niño
+nítido
+nivel
+nobleza
+noche
+nómina
+noria
+norma
+norte
+nota
+noticia
+novato
+novela
+novio
+nube
+nuca
+núcleo
+nudillo
+nudo
+nuera
+nueve
+nuez
+nulo
+número
+nutria
+oasis
+obeso
+obispo
+objeto
+obra
+obrero
+observar
+obtener
+obvio
+oca
+ocaso
+océano
+ochenta
+ocho
+ocio
+ocre
+octavo
+octubre
+oculto
+ocupar
+ocurrir
+odiar
+odio
+odisea
+oeste
+ofensa
+oferta
+oficio
+ofrecer
+ogro
+oído
+oír
+ojo
+ola
+oleada
+olfato
+olivo
+olla
+olmo
+olor
+olvido
+ombligo
+onda
+onza
+opaco
+opción
+ópera
+opinar
+oponer
+optar
+óptica
+opuesto
+oración
+orador
+oral
+órbita
+orca
+orden
+oreja
+órgano
+orgía
+orgullo
+oriente
+origen
+orilla
+oro
+orquesta
+oruga
+osadía
+oscuro
+osezno
+oso
+ostra
+otoño
+otro
+oveja
+óvulo
+óxido
+oxígeno
+oyente
+ozono
+pacto
+padre
+paella
+página
+pago
+país
+pájaro
+palabra
+palco
+paleta
+pálido
+palma
+paloma
+palpar
+pan
+panal
+pánico
+pantera
+pañuelo
+papá
+papel
+papilla
+paquete
+parar
+parcela
+pared
+parir
+paro
+párpado
+parque
+párrafo
+parte
+pasar
+paseo
+pasión
+paso
+pasta
+pata
+patio
+patria
+pausa
+pauta
+pavo
+payaso
+peatón
+pecado
+pecera
+pecho
+pedal
+pedir
+pegar
+peine
+pelar
+peldaño
+pelea
+peligro
+pellejo
+pelo
+peluca
+pena
+pensar
+peñón
+peón
+peor
+pepino
+pequeño
+pera
+percha
+perder
+pereza
+perfil
+perico
+perla
+permiso
+perro
+persona
+pesa
+pesca
+pésimo
+pestaña
+pétalo
+petróleo
+pez
+pezuña
+picar
+pichón
+pie
+piedra
+pierna
+pieza
+pijama
+pilar
+piloto
+pimienta
+pino
+pintor
+pinza
+piña
+piojo
+pipa
+pirata
+pisar
+piscina
+piso
+pista
+pitón
+pizca
+placa
+plan
+plata
+playa
+plaza
+pleito
+pleno
+plomo
+pluma
+plural
+pobre
+poco
+poder
+podio
+poema
+poesía
+poeta
+polen
+policía
+pollo
+polvo
+pomada
+pomelo
+pomo
+pompa
+poner
+porción
+portal
+posada
+poseer
+posible
+poste
+potencia
+potro
+pozo
+prado
+precoz
+pregunta
+premio
+prensa
+preso
+previo
+primo
+príncipe
+prisión
+privar
+proa
+probar
+proceso
+producto
+proeza
+profesor
+programa
+prole
+promesa
+pronto
+propio
+próximo
+prueba
+público
+puchero
+pudor
+pueblo
+puerta
+puesto
+pulga
+pulir
+pulmón
+pulpo
+pulso
+puma
+punto
+puñal
+puño
+pupa
+pupila
+puré
+quedar
+queja
+quemar
+querer
+queso
+quieto
+química
+quince
+quitar
+rábano
+rabia
+rabo
+ración
+radical
+raíz
+rama
+rampa
+rancho
+rango
+rapaz
+rápido
+rapto
+rasgo
+raspa
+rato
+rayo
+raza
+razón
+reacción
+realidad
+rebaño
+rebote
+recaer
+receta
+rechazo
+recoger
+recreo
+recto
+recurso
+red
+redondo
+reducir
+reflejo
+reforma
+refrán
+refugio
+regalo
+regir
+regla
+regreso
+rehén
+reino
+reír
+reja
+relato
+relevo
+relieve
+relleno
+reloj
+remar
+remedio
+remo
+rencor
+rendir
+renta
+reparto
+repetir
+reposo
+reptil
+res
+rescate
+resina
+respeto
+resto
+resumen
+retiro
+retorno
+retrato
+reunir
+revés
+revista
+rey
+rezar
+rico
+riego
+rienda
+riesgo
+rifa
+rígido
+rigor
+rincón
+riñón
+río
+riqueza
+risa
+ritmo
+rito
+rizo
+roble
+roce
+rociar
+rodar
+rodeo
+rodilla
+roer
+rojizo
+rojo
+romero
+romper
+ron
+ronco
+ronda
+ropa
+ropero
+rosa
+rosca
+rostro
+rotar
+rubí
+rubor
+rudo
+rueda
+rugir
+ruido
+ruina
+ruleta
+rulo
+rumbo
+rumor
+ruptura
+ruta
+rutina
+sábado
+saber
+sabio
+sable
+sacar
+sagaz
+sagrado
+sala
+saldo
+salero
+salir
+salmón
+salón
+salsa
+salto
+salud
+salvar
+samba
+sanción
+sandía
+sanear
+sangre
+sanidad
+sano
+santo
+sapo
+saque
+sardina
+sartén
+sastre
+satán
+sauna
+saxofón
+sección
+seco
+secreto
+secta
+sed
+seguir
+seis
+sello
+selva
+semana
+semilla
+senda
+sensor
+señal
+señor
+separar
+sepia
+sequía
+ser
+serie
+sermón
+servir
+sesenta
+sesión
+seta
+setenta
+severo
+sexo
+sexto
+sidra
+siesta
+siete
+siglo
+signo
+sílaba
+silbar
+silencio
+silla
+símbolo
+simio
+sirena
+sistema
+sitio
+situar
+sobre
+socio
+sodio
+sol
+solapa
+soldado
+soledad
+sólido
+soltar
+solución
+sombra
+sondeo
+sonido
+sonoro
+sonrisa
+sopa
+soplar
+soporte
+sordo
+sorpresa
+sorteo
+sostén
+sótano
+suave
+subir
+suceso
+sudor
+suegra
+suelo
+sueño
+suerte
+sufrir
+sujeto
+sultán
+sumar
+superar
+suplir
+suponer
+supremo
+sur
+surco
+sureño
+surgir
+susto
+sutil
+tabaco
+tabique
+tabla
+tabú
+taco
+tacto
+tajo
+talar
+talco
+talento
+talla
+talón
+tamaño
+tambor
+tango
+tanque
+tapa
+tapete
+tapia
+tapón
+taquilla
+tarde
+tarea
+tarifa
+tarjeta
+tarot
+tarro
+tarta
+tatuaje
+tauro
+taza
+tazón
+teatro
+techo
+tecla
+técnica
+tejado
+tejer
+tejido
+tela
+teléfono
+tema
+temor
+templo
+tenaz
+tender
+tener
+tenis
+tenso
+teoría
+terapia
+terco
+término
+ternura
+terror
+tesis
+tesoro
+testigo
+tetera
+texto
+tez
+tibio
+tiburón
+tiempo
+tienda
+tierra
+tieso
+tigre
+tijera
+tilde
+timbre
+tímido
+timo
+tinta
+tío
+típico
+tipo
+tira
+tirón
+titán
+títere
+título
+tiza
+toalla
+tobillo
+tocar
+tocino
+todo
+toga
+toldo
+tomar
+tono
+tonto
+topar
+tope
+toque
+tórax
+torero
+tormenta
+torneo
+toro
+torpedo
+torre
+torso
+tortuga
+tos
+tosco
+toser
+tóxico
+trabajo
+tractor
+traer
+tráfico
+trago
+traje
+tramo
+trance
+trato
+trauma
+trazar
+trébol
+tregua
+treinta
+tren
+trepar
+tres
+tribu
+trigo
+tripa
+triste
+triunfo
+trofeo
+trompa
+tronco
+tropa
+trote
+trozo
+truco
+trueno
+trufa
+tubería
+tubo
+tuerto
+tumba
+tumor
+túnel
+túnica
+turbina
+turismo
+turno
+tutor
+ubicar
+úlcera
+umbral
+unidad
+unir
+universo
+uno
+untar
+uña
+urbano
+urbe
+urgente
+urna
+usar
+usuario
+útil
+utopía
+uva
+vaca
+vacío
+vacuna
+vagar
+vago
+vaina
+vajilla
+vale
+válido
+valle
+valor
+válvula
+vampiro
+vara
+variar
+varón
+vaso
+vecino
+vector
+vehículo
+veinte
+vejez
+vela
+velero
+veloz
+vena
+vencer
+venda
+veneno
+vengar
+venir
+venta
+venus
+ver
+verano
+verbo
+verde
+vereda
+verja
+verso
+verter
+vía
+viaje
+vibrar
+vicio
+víctima
+vida
+vídeo
+vidrio
+viejo
+viernes
+vigor
+vil
+villa
+vinagre
+vino
+viñedo
+violín
+viral
+virgo
+virtud
+visor
+víspera
+vista
+vitamina
+viudo
+vivaz
+vivero
+vivir
+vivo
+volcán
+volumen
+volver
+voraz
+votar
+voto
+voz
+vuelo
+vulgar
+yacer
+yate
+yegua
+yema
+yerno
+yeso
+yodo
+yoga
+yogur
+zafiro
+zanja
+zapato
+zarza
+zona
+zorro
+zumo
+zurdo
diff --git a/electrum/x509.py b/electrum/x509.py
new file mode 100644
index 000000000..f03d3051e
--- /dev/null
+++ b/electrum/x509.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2014 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+from . import util
+from .util import profiler, bh2u
+import ecdsa
+import hashlib
+import time
+
+# algo OIDs
+ALGO_RSA_SHA1 = '1.2.840.113549.1.1.5'
+ALGO_RSA_SHA256 = '1.2.840.113549.1.1.11'
+ALGO_RSA_SHA384 = '1.2.840.113549.1.1.12'
+ALGO_RSA_SHA512 = '1.2.840.113549.1.1.13'
+ALGO_ECDSA_SHA256 = '1.2.840.10045.4.3.2'
+
+# prefixes, see http://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm
+PREFIX_RSA_SHA256 = bytearray(
+ [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20])
+PREFIX_RSA_SHA384 = bytearray(
+ [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30])
+PREFIX_RSA_SHA512 = bytearray(
+ [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40])
+
+# types used in ASN1 structured data
+ASN1_TYPES = {
+ 'BOOLEAN' : 0x01,
+ 'INTEGER' : 0x02,
+ 'BIT STRING' : 0x03,
+ 'OCTET STRING' : 0x04,
+ 'NULL' : 0x05,
+ 'OBJECT IDENTIFIER': 0x06,
+ 'SEQUENCE' : 0x70,
+ 'SET' : 0x71,
+ 'PrintableString' : 0x13,
+ 'IA5String' : 0x16,
+ 'UTCTime' : 0x17,
+ 'GeneralizedTime' : 0x18,
+ 'ENUMERATED' : 0x0A,
+ 'UTF8String' : 0x0C,
+}
+
+
+class CertificateError(Exception):
+ pass
+
+
+# helper functions
+def bitstr_to_bytestr(s):
+ if s[0] != 0x00:
+ raise TypeError('no padding')
+ return s[1:]
+
+
+def bytestr_to_int(s):
+ i = 0
+ for char in s:
+ i <<= 8
+ i |= char
+ return i
+
+
+def decode_OID(s):
+ r = []
+ r.append(s[0] // 40)
+ r.append(s[0] % 40)
+ k = 0
+ for i in s[1:]:
+ if i < 128:
+ r.append(i + 128 * k)
+ k = 0
+ else:
+ k = (i - 128) + 128 * k
+ return '.'.join(map(str, r))
+
+
+def encode_OID(oid):
+ x = [int(i) for i in oid.split('.')]
+ s = chr(x[0] * 40 + x[1])
+ for i in x[2:]:
+ ss = chr(i % 128)
+ while i > 128:
+ i //= 128
+ ss = chr(128 + i % 128) + ss
+ s += ss
+ return s
+
+
+class ASN1_Node(bytes):
+ def get_node(self, ix):
+ # return index of first byte, first content byte and last byte.
+ first = self[ix + 1]
+ if (first & 0x80) == 0:
+ length = first
+ ixf = ix + 2
+ ixl = ixf + length - 1
+ else:
+ lengthbytes = first & 0x7F
+ length = bytestr_to_int(self[ix + 2:ix + 2 + lengthbytes])
+ ixf = ix + 2 + lengthbytes
+ ixl = ixf + length - 1
+ return ix, ixf, ixl
+
+ def root(self):
+ return self.get_node(0)
+
+ def next_node(self, node):
+ ixs, ixf, ixl = node
+ return self.get_node(ixl + 1)
+
+ def first_child(self, node):
+ ixs, ixf, ixl = node
+ if self[ixs] & 0x20 != 0x20:
+ raise TypeError('Can only open constructed types.', hex(self[ixs]))
+ return self.get_node(ixf)
+
+ def is_child_of(node1, node2):
+ ixs, ixf, ixl = node1
+ jxs, jxf, jxl = node2
+ return ((ixf <= jxs) and (jxl <= ixl)) or ((jxf <= ixs) and (ixl <= jxl))
+
+ def get_all(self, node):
+ # return type + length + value
+ ixs, ixf, ixl = node
+ return self[ixs:ixl + 1]
+
+ def get_value_of_type(self, node, asn1_type):
+ # verify type byte and return content
+ ixs, ixf, ixl = node
+ if ASN1_TYPES[asn1_type] != self[ixs]:
+ raise TypeError('Wrong type:', hex(self[ixs]), hex(ASN1_TYPES[asn1_type]))
+ return self[ixf:ixl + 1]
+
+ def get_value(self, node):
+ ixs, ixf, ixl = node
+ return self[ixf:ixl + 1]
+
+ def get_children(self, node):
+ nodes = []
+ ii = self.first_child(node)
+ nodes.append(ii)
+ while ii[2] < node[2]:
+ ii = self.next_node(ii)
+ nodes.append(ii)
+ return nodes
+
+ def get_sequence(self):
+ return list(map(lambda j: self.get_value(j), self.get_children(self.root())))
+
+ def get_dict(self, node):
+ p = {}
+ for ii in self.get_children(node):
+ for iii in self.get_children(ii):
+ iiii = self.first_child(iii)
+ oid = decode_OID(self.get_value_of_type(iiii, 'OBJECT IDENTIFIER'))
+ iiii = self.next_node(iiii)
+ value = self.get_value(iiii)
+ p[oid] = value
+ return p
+
+ def decode_time(self, ii):
+ GENERALIZED_TIMESTAMP_FMT = '%Y%m%d%H%M%SZ'
+ UTCTIME_TIMESTAMP_FMT = '%y%m%d%H%M%SZ'
+
+ try:
+ return time.strptime(self.get_value_of_type(ii, 'UTCTime').decode('ascii'), UTCTIME_TIMESTAMP_FMT)
+ except TypeError:
+ return time.strptime(self.get_value_of_type(ii, 'GeneralizedTime').decode('ascii'), GENERALIZED_TIMESTAMP_FMT)
+
+class X509(object):
+ def __init__(self, b):
+
+ self.bytes = bytearray(b)
+
+ der = ASN1_Node(b)
+ root = der.root()
+ cert = der.first_child(root)
+ # data for signature
+ self.data = der.get_all(cert)
+
+ # optional version field
+ if der.get_value(cert)[0] == 0xa0:
+ version = der.first_child(cert)
+ serial_number = der.next_node(version)
+ else:
+ serial_number = der.first_child(cert)
+ self.serial_number = bytestr_to_int(der.get_value_of_type(serial_number, 'INTEGER'))
+
+ # signature algorithm
+ sig_algo = der.next_node(serial_number)
+ ii = der.first_child(sig_algo)
+ self.sig_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))
+
+ # issuer
+ issuer = der.next_node(sig_algo)
+ self.issuer = der.get_dict(issuer)
+
+ # validity
+ validity = der.next_node(issuer)
+ ii = der.first_child(validity)
+ self.notBefore = der.decode_time(ii)
+ ii = der.next_node(ii)
+ self.notAfter = der.decode_time(ii)
+
+ # subject
+ subject = der.next_node(validity)
+ self.subject = der.get_dict(subject)
+ subject_pki = der.next_node(subject)
+ public_key_algo = der.first_child(subject_pki)
+ ii = der.first_child(public_key_algo)
+ self.public_key_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))
+
+ if self.public_key_algo != '1.2.840.10045.2.1': # for non EC public key
+ # pubkey modulus and exponent
+ subject_public_key = der.next_node(public_key_algo)
+ spk = der.get_value_of_type(subject_public_key, 'BIT STRING')
+ spk = ASN1_Node(bitstr_to_bytestr(spk))
+ r = spk.root()
+ modulus = spk.first_child(r)
+ exponent = spk.next_node(modulus)
+ rsa_n = spk.get_value_of_type(modulus, 'INTEGER')
+ rsa_e = spk.get_value_of_type(exponent, 'INTEGER')
+ self.modulus = ecdsa.util.string_to_number(rsa_n)
+ self.exponent = ecdsa.util.string_to_number(rsa_e)
+ else:
+ subject_public_key = der.next_node(public_key_algo)
+ spk = der.get_value_of_type(subject_public_key, 'BIT STRING')
+ self.ec_public_key = spk
+
+ # extensions
+ self.CA = False
+ self.AKI = None
+ self.SKI = None
+ i = subject_pki
+ while i[2] < cert[2]:
+ i = der.next_node(i)
+ d = der.get_dict(i)
+ for oid, value in d.items():
+ value = ASN1_Node(value)
+ if oid == '2.5.29.19':
+ # Basic Constraints
+ self.CA = bool(value)
+ elif oid == '2.5.29.14':
+ # Subject Key Identifier
+ r = value.root()
+ value = value.get_value_of_type(r, 'OCTET STRING')
+ self.SKI = bh2u(value)
+ elif oid == '2.5.29.35':
+ # Authority Key Identifier
+ self.AKI = bh2u(value.get_sequence()[0])
+ else:
+ pass
+
+ # cert signature
+ cert_sig_algo = der.next_node(cert)
+ ii = der.first_child(cert_sig_algo)
+ self.cert_sig_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))
+ cert_sig = der.next_node(cert_sig_algo)
+ self.signature = der.get_value(cert_sig)[1:]
+
+ def get_keyID(self):
+ # http://security.stackexchange.com/questions/72077/validating-an-ssl-certificate-chain-according-to-rfc-5280-am-i-understanding-th
+ return self.SKI if self.SKI else repr(self.subject)
+
+ def get_issuer_keyID(self):
+ return self.AKI if self.AKI else repr(self.issuer)
+
+ def get_common_name(self):
+ return self.subject.get('2.5.4.3', b'unknown').decode()
+
+ def get_signature(self):
+ return self.cert_sig_algo, self.signature, self.data
+
+ def check_ca(self):
+ return self.CA
+
+ def check_date(self):
+ now = time.gmtime()
+ if self.notBefore > now:
+ raise CertificateError('Certificate has not entered its valid date range. (%s)' % self.get_common_name())
+ if self.notAfter <= now:
+ raise CertificateError('Certificate has expired. (%s)' % self.get_common_name())
+
+ def getFingerprint(self):
+ return hashlib.sha1(self.bytes).digest()
+
+
+@profiler
+def load_certificates(ca_path):
+ from . import pem
+ ca_list = {}
+ ca_keyID = {}
+ # ca_path = '/tmp/tmp.txt'
+ with open(ca_path, 'r', encoding='utf-8') as f:
+ s = f.read()
+ bList = pem.dePemList(s, "CERTIFICATE")
+ for b in bList:
+ try:
+ x = X509(b)
+ x.check_date()
+ except BaseException as e:
+ # with open('/tmp/tmp.txt', 'w') as f:
+ # f.write(pem.pem(b, 'CERTIFICATE').decode('ascii'))
+ util.print_error("cert error:", e)
+ continue
+
+ fp = x.getFingerprint()
+ ca_list[fp] = x
+ ca_keyID[x.get_keyID()] = fp
+
+ return ca_list, ca_keyID
+
+
+if __name__ == "__main__":
+ import requests
+
+ util.set_verbosity(True)
+ ca_path = requests.certs.where()
+ ca_list, ca_keyID = load_certificates(ca_path)
diff --git a/icons.qrc b/icons.qrc
new file mode 100644
index 000000000..f677d025d
--- /dev/null
+++ b/icons.qrc
@@ -0,0 +1,64 @@
+
+
+ icons/electrum_BTX.png
+ icons/clock1.png
+ icons/clock2.png
+ icons/clock3.png
+ icons/clock4.png
+ icons/clock5.png
+ icons/confirmed.png
+ icons/copy.png
+ icons/digitalbitbox.png
+ icons/digitalbitbox_unpaired.png
+ icons/expired.png
+ icons/electrum_light_icon.png
+ icons/electrum_dark_icon.png
+ icons/electrumb.png
+ icons/eye1.png
+ icons/file.png
+ icons/info.png
+ icons/keepkey.png
+ icons/keepkey_unpaired.png
+ icons/key.png
+ icons/ledger.png
+ icons/ledger_unpaired.png
+ icons/lock.png
+ icons/microphone.png
+ icons/network.png
+ icons/offline_tx.png
+ icons/revealer.png
+ icons/revealer_c.png
+ icons/qrcode.png
+ icons/qrcode_white.png
+ icons/preferences.png
+ icons/safe-t_unpaired.png
+ icons/safe-t.png
+ icons/seed.png
+ icons/status_connected.png
+ icons/status_connected_proxy.png
+ icons/status_disconnected.png
+ icons/status_waiting.png
+ icons/status_lagging.png
+ icons/seal.png
+ icons/tab_addresses.png
+ icons/tab_coins.png
+ icons/tab_console.png
+ icons/tab_contacts.png
+ icons/tab_history.png
+ icons/tab_receive.png
+ icons/tab_send.png
+ icons/tor_logo.png
+ icons/speaker.png
+ icons/trezor_unpaired.png
+ icons/trezor.png
+ icons/coldcard.png
+ icons/coldcard_unpaired.png
+ icons/trustedcoin-status.png
+ icons/trustedcoin-wizard.png
+ icons/unconfirmed.png
+ icons/unpaid.png
+ icons/unlock.png
+ icons/warning.png
+ icons/zoom.png
+
+
diff --git a/icons/clock1.png b/icons/clock1.png
new file mode 100644
index 000000000..4850431b8
Binary files /dev/null and b/icons/clock1.png differ
diff --git a/icons/clock2.png b/icons/clock2.png
new file mode 100644
index 000000000..4bbad0549
Binary files /dev/null and b/icons/clock2.png differ
diff --git a/icons/clock3.png b/icons/clock3.png
new file mode 100644
index 000000000..bb74995ef
Binary files /dev/null and b/icons/clock3.png differ
diff --git a/icons/clock4.png b/icons/clock4.png
new file mode 100644
index 000000000..e640dfd3d
Binary files /dev/null and b/icons/clock4.png differ
diff --git a/icons/clock5.png b/icons/clock5.png
new file mode 100644
index 000000000..327eb4831
Binary files /dev/null and b/icons/clock5.png differ
diff --git a/icons/coldcard.png b/icons/coldcard.png
new file mode 100644
index 000000000..74104d76e
Binary files /dev/null and b/icons/coldcard.png differ
diff --git a/icons/coldcard_unpaired.png b/icons/coldcard_unpaired.png
new file mode 100644
index 000000000..2560407e8
Binary files /dev/null and b/icons/coldcard_unpaired.png differ
diff --git a/icons/confirmed.png b/icons/confirmed.png
new file mode 100644
index 000000000..2023abd15
Binary files /dev/null and b/icons/confirmed.png differ
diff --git a/icons/confirmed.svg b/icons/confirmed.svg
new file mode 100644
index 000000000..710b3f8c3
--- /dev/null
+++ b/icons/confirmed.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/copy.png b/icons/copy.png
new file mode 100644
index 000000000..b82da41bf
Binary files /dev/null and b/icons/copy.png differ
diff --git a/icons/digitalbitbox.png b/icons/digitalbitbox.png
new file mode 100644
index 000000000..ed6d72e6d
Binary files /dev/null and b/icons/digitalbitbox.png differ
diff --git a/icons/digitalbitbox_unpaired.png b/icons/digitalbitbox_unpaired.png
new file mode 100644
index 000000000..2e88ed488
Binary files /dev/null and b/icons/digitalbitbox_unpaired.png differ
diff --git a/icons/electrum.ico b/icons/electrum.ico
new file mode 100644
index 000000000..47147480c
Binary files /dev/null and b/icons/electrum.ico differ
diff --git a/icons/electrum.png b/icons/electrum.png
new file mode 100644
index 000000000..248f25081
Binary files /dev/null and b/icons/electrum.png differ
diff --git a/icons/electrumBTX.png b/icons/electrumBTX.png
new file mode 100644
index 000000000..248f25081
Binary files /dev/null and b/icons/electrumBTX.png differ
diff --git a/icons/electrum_BTX.png b/icons/electrum_BTX.png
new file mode 100644
index 000000000..eb1824728
Binary files /dev/null and b/icons/electrum_BTX.png differ
diff --git a/icons/electrum_dark_icon.png b/icons/electrum_dark_icon.png
new file mode 100644
index 000000000..6fee7de9f
Binary files /dev/null and b/icons/electrum_dark_icon.png differ
diff --git a/icons/electrum_launcher.png b/icons/electrum_launcher.png
new file mode 100644
index 000000000..cd8acc26e
Binary files /dev/null and b/icons/electrum_launcher.png differ
diff --git a/icons/electrum_light_icon.png b/icons/electrum_light_icon.png
new file mode 100644
index 000000000..da52cb3c1
Binary files /dev/null and b/icons/electrum_light_icon.png differ
diff --git a/icons/electrum_presplash.png b/icons/electrum_presplash.png
new file mode 100644
index 000000000..3fe6517fd
Binary files /dev/null and b/icons/electrum_presplash.png differ
diff --git a/icons/electrumb.png b/icons/electrumb.png
new file mode 100644
index 000000000..5f3738394
Binary files /dev/null and b/icons/electrumb.png differ
diff --git a/icons/expired.png b/icons/expired.png
new file mode 100644
index 000000000..6400b8ba6
Binary files /dev/null and b/icons/expired.png differ
diff --git a/icons/eye1.png b/icons/eye1.png
new file mode 100644
index 000000000..0bded339f
Binary files /dev/null and b/icons/eye1.png differ
diff --git a/icons/file.png b/icons/file.png
new file mode 100644
index 000000000..ae84e2599
Binary files /dev/null and b/icons/file.png differ
diff --git a/icons/info.png b/icons/info.png
new file mode 100644
index 000000000..f11f99694
Binary files /dev/null and b/icons/info.png differ
diff --git a/icons/keepkey.png b/icons/keepkey.png
new file mode 100644
index 000000000..dd5c244fc
Binary files /dev/null and b/icons/keepkey.png differ
diff --git a/icons/keepkey_unpaired.png b/icons/keepkey_unpaired.png
new file mode 100644
index 000000000..ac15d2e85
Binary files /dev/null and b/icons/keepkey_unpaired.png differ
diff --git a/icons/key.png b/icons/key.png
new file mode 100644
index 000000000..4470abfe8
Binary files /dev/null and b/icons/key.png differ
diff --git a/icons/ledger.png b/icons/ledger.png
new file mode 100644
index 000000000..bab69ec52
Binary files /dev/null and b/icons/ledger.png differ
diff --git a/icons/ledger_unpaired.png b/icons/ledger_unpaired.png
new file mode 100644
index 000000000..65db1ea87
Binary files /dev/null and b/icons/ledger_unpaired.png differ
diff --git a/icons/lock.png b/icons/lock.png
new file mode 100644
index 000000000..a190964ed
Binary files /dev/null and b/icons/lock.png differ
diff --git a/icons/lock.svg b/icons/lock.svg
new file mode 100644
index 000000000..9024d2a37
--- /dev/null
+++ b/icons/lock.svg
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/microphone.png b/icons/microphone.png
new file mode 100644
index 000000000..f9a271c55
Binary files /dev/null and b/icons/microphone.png differ
diff --git a/icons/network.png b/icons/network.png
new file mode 100644
index 000000000..6a5bcba97
Binary files /dev/null and b/icons/network.png differ
diff --git a/icons/offline_tx.png b/icons/offline_tx.png
new file mode 100644
index 000000000..32fee54dd
Binary files /dev/null and b/icons/offline_tx.png differ
diff --git a/icons/preferences.png b/icons/preferences.png
new file mode 100644
index 000000000..b10ba6ea0
Binary files /dev/null and b/icons/preferences.png differ
diff --git a/icons/preferences.svg b/icons/preferences.svg
new file mode 100644
index 000000000..39f7bd143
--- /dev/null
+++ b/icons/preferences.svg
@@ -0,0 +1,686 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+ System Preferences
+
+
+ Andreas Nilsson
+
+
+
+
+ category
+ system
+ preferences
+ settings
+ control center
+
+
+
+
+ Jakub Steiner
+Ulisse Perusin
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+ Preferences
+
+
+ Andreas Nilsson
+
+
+
+
+ Lapo Calamandrei, Ulisse Perusin, Jakub Steiner
+
+
+
+
+
+ category
+ system
+ preferences
+ settings
+ control center
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/qrcode.png b/icons/qrcode.png
new file mode 100644
index 000000000..41a84aa1d
Binary files /dev/null and b/icons/qrcode.png differ
diff --git a/icons/qrcode_white.png b/icons/qrcode_white.png
new file mode 100644
index 000000000..23ea91655
Binary files /dev/null and b/icons/qrcode_white.png differ
diff --git a/icons/revealer.png b/icons/revealer.png
new file mode 100644
index 000000000..9caef09dd
Binary files /dev/null and b/icons/revealer.png differ
diff --git a/icons/revealer_c.png b/icons/revealer_c.png
new file mode 100644
index 000000000..993d8fc90
Binary files /dev/null and b/icons/revealer_c.png differ
diff --git a/icons/safe-t.png b/icons/safe-t.png
new file mode 100644
index 000000000..7a574dec8
Binary files /dev/null and b/icons/safe-t.png differ
diff --git a/icons/safe-t_unpaired.png b/icons/safe-t_unpaired.png
new file mode 100644
index 000000000..c67a344f1
Binary files /dev/null and b/icons/safe-t_unpaired.png differ
diff --git a/icons/seal.png b/icons/seal.png
new file mode 100644
index 000000000..f6d51baa3
Binary files /dev/null and b/icons/seal.png differ
diff --git a/icons/seed.png b/icons/seed.png
new file mode 100644
index 000000000..54c38b6ab
Binary files /dev/null and b/icons/seed.png differ
diff --git a/icons/speaker.png b/icons/speaker.png
new file mode 100644
index 000000000..963db53f9
Binary files /dev/null and b/icons/speaker.png differ
diff --git a/icons/status_connected.png b/icons/status_connected.png
new file mode 100644
index 000000000..1fe3dace9
Binary files /dev/null and b/icons/status_connected.png differ
diff --git a/icons/status_connected.svg b/icons/status_connected.svg
new file mode 100644
index 000000000..e0779998c
--- /dev/null
+++ b/icons/status_connected.svg
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+ Lapo Calamandrei
+
+
+
+
+
+
+
+ record
+ media
+
+
+
+
+ Jakub Steiner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/status_connected_proxy.png b/icons/status_connected_proxy.png
new file mode 100644
index 000000000..ff553d9f1
Binary files /dev/null and b/icons/status_connected_proxy.png differ
diff --git a/icons/status_connected_proxy.svg b/icons/status_connected_proxy.svg
new file mode 100644
index 000000000..5e44b5e51
--- /dev/null
+++ b/icons/status_connected_proxy.svg
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+ Lapo Calamandrei
+
+
+
+
+
+
+
+ record
+ media
+
+
+
+
+ Jakub Steiner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/status_disconnected.png b/icons/status_disconnected.png
new file mode 100644
index 000000000..cb5ac1b9f
Binary files /dev/null and b/icons/status_disconnected.png differ
diff --git a/icons/status_disconnected.svg b/icons/status_disconnected.svg
new file mode 100644
index 000000000..46d1a1d7f
--- /dev/null
+++ b/icons/status_disconnected.svg
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+ Lapo Calamandrei
+
+
+
+
+ Record
+
+
+ record
+ media
+
+
+
+
+ Jakub Steiner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/status_lagging.png b/icons/status_lagging.png
new file mode 100644
index 000000000..b558791f5
Binary files /dev/null and b/icons/status_lagging.png differ
diff --git a/icons/status_lagging.svg b/icons/status_lagging.svg
new file mode 100644
index 000000000..1fd487964
--- /dev/null
+++ b/icons/status_lagging.svg
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+ Lapo Calamandrei
+
+
+
+
+
+
+
+ record
+ media
+
+
+
+
+ Jakub Steiner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/status_waiting.png b/icons/status_waiting.png
new file mode 100644
index 000000000..d5513838f
Binary files /dev/null and b/icons/status_waiting.png differ
diff --git a/icons/status_waiting.svg b/icons/status_waiting.svg
new file mode 100644
index 000000000..069a4d3dd
--- /dev/null
+++ b/icons/status_waiting.svg
@@ -0,0 +1,398 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+ Jakub Steiner
+
+
+ http://jimmac.musichall.cz
+
+ View Refresh
+
+
+ reload
+ refresh
+ view
+
+
+
+
+ Ricardo 'Rick' González
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/tab_addresses.png b/icons/tab_addresses.png
new file mode 100644
index 000000000..449b23ba1
Binary files /dev/null and b/icons/tab_addresses.png differ
diff --git a/icons/tab_coins.png b/icons/tab_coins.png
new file mode 100644
index 000000000..a6c8265da
Binary files /dev/null and b/icons/tab_coins.png differ
diff --git a/icons/tab_console.png b/icons/tab_console.png
new file mode 100644
index 000000000..4f70e6f60
Binary files /dev/null and b/icons/tab_console.png differ
diff --git a/icons/tab_contacts.png b/icons/tab_contacts.png
new file mode 100644
index 000000000..d21ae9ed6
Binary files /dev/null and b/icons/tab_contacts.png differ
diff --git a/icons/tab_history.png b/icons/tab_history.png
new file mode 100644
index 000000000..ecdc46819
Binary files /dev/null and b/icons/tab_history.png differ
diff --git a/icons/tab_receive.png b/icons/tab_receive.png
new file mode 100644
index 000000000..ba486694c
Binary files /dev/null and b/icons/tab_receive.png differ
diff --git a/icons/tab_send.png b/icons/tab_send.png
new file mode 100644
index 000000000..7ef90a422
Binary files /dev/null and b/icons/tab_send.png differ
diff --git a/icons/tor_logo.png b/icons/tor_logo.png
new file mode 100644
index 000000000..a4afd8492
Binary files /dev/null and b/icons/tor_logo.png differ
diff --git a/icons/trezor.png b/icons/trezor.png
new file mode 100644
index 000000000..3edee94f8
Binary files /dev/null and b/icons/trezor.png differ
diff --git a/icons/trezor_unpaired.png b/icons/trezor_unpaired.png
new file mode 100644
index 000000000..c9854be1a
Binary files /dev/null and b/icons/trezor_unpaired.png differ
diff --git a/icons/trustedcoin-status.png b/icons/trustedcoin-status.png
new file mode 100644
index 000000000..ee920c13e
Binary files /dev/null and b/icons/trustedcoin-status.png differ
diff --git a/icons/trustedcoin-wizard.png b/icons/trustedcoin-wizard.png
new file mode 100644
index 000000000..19f4f6e72
Binary files /dev/null and b/icons/trustedcoin-wizard.png differ
diff --git a/icons/unconfirmed.png b/icons/unconfirmed.png
new file mode 100644
index 000000000..6ebfe290e
Binary files /dev/null and b/icons/unconfirmed.png differ
diff --git a/icons/unlock.png b/icons/unlock.png
new file mode 100644
index 000000000..869e4de32
Binary files /dev/null and b/icons/unlock.png differ
diff --git a/icons/unlock.svg b/icons/unlock.svg
new file mode 100644
index 000000000..b22e40207
--- /dev/null
+++ b/icons/unlock.svg
@@ -0,0 +1,509 @@
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/icons/unpaid.png b/icons/unpaid.png
new file mode 100644
index 000000000..579ec4eb5
Binary files /dev/null and b/icons/unpaid.png differ
diff --git a/icons/warning.png b/icons/warning.png
new file mode 100644
index 000000000..ee3eacea9
Binary files /dev/null and b/icons/warning.png differ
diff --git a/icons/zoom.png b/icons/zoom.png
new file mode 100644
index 000000000..c5b58a39b
Binary files /dev/null and b/icons/zoom.png differ
diff --git a/pubkeys/Animazing.asc b/pubkeys/Animazing.asc
new file mode 100644
index 000000000..8ff17548d
--- /dev/null
+++ b/pubkeys/Animazing.asc
@@ -0,0 +1,38 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: SKS 1.1.4
+Comment: Hostname: keys.fedoraproject.org
+
+mQENBFD1gzgBCADGVp8wVMk0viUTR3FBzCfdGGULYcyiKFNdGlKxORyrdWJF6/g5JuYPU0NX
+CRJ7hqX4hopOiID+oxE7p/vP/i/Sm6B6xZMq8bJ+PiJ2h8ZqnourgL8tkAqlDV+zLana+XeQ
+PsPhJPeARAQDtl5QhQbvWm0idMyd1zuWdt4OVIYnhJ7w7Mw0CdUmBbkTc1P23J8vwqiyyuHq
+V3JimkNJLm4vyWGFig6ElgRMbV5YWci41OTH3x8qkWHFdB1h0ODP/28bBwpVVXqnObrp/Lsr
+9aQSP5VujGIL/cOdel/7xD2Dc5qS/HXYZgJUk6x2WkLmO47NSpW6rone3W2A82gkKRwXABEB
+AAG0H0FuaW1hemluZyA8YW5pbWF6aW5nQGdtYWlsLmNvbT6JATgEEwECACIFAlD1gzgCGwMG
+CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECJFMARpVQb95i8IAISo2NvZW8Zln4ivuo5l
+6PirIrzSIlec3Xa/xmkgrHTfBVT3WYeGp54NkEcpgarD1qjt5ZFF7wExn9WLErGlsIkPmegO
+Zjx2mn3ZyB+Y5y0cJeGPM72aD0Z3jaBi+htv37GMb2mV6yO700dG1x3vwS1YYyRLgR6pVwlR
+w+0l+5B/5zHsKdRvpRcuTpmrg8rtgTxZOsRyORn6ck6w7lUbno3+1XMBTFTbVf4/SymMU3Wl
+9eru2agYRfkB3puXd8DMYlSzZiiUWTHV6bOsB0N001sJtmFf6KmM1xF8Q/eqMlWevS2sd0XG
+OWFQu4RGyAJqN7h1Z3cym278WVCMhYzJn/SJAhwEEAECAAYFAlHaodMACgkQuW8jAK0Ry+6x
+ohAAj1Cka1AoIDmPcU5IlPLPwsYoyfLmkAbcpeNKZA6Yf4pm96i3tKXbLgCxSrejSmraVIYZ
+oNGFtmAx+rpwdZtDWyFWGlMkDhjRY1rQ6nlkO8CtPU/brbyZJci2J67U7hUsJt+O5gaftclG
+2RcC61BQz3Ee0Fl8JKSE5V2KqOD2W1BTCTYrnpY5YdMB8qSalR8txZaSqkA3xpSrIwJ04EHT
+1uyQAJiacQ0ynHpcU/xfVBnzj3Hc0mRxBTI5Nznk8NAHleY5OSiZX5YJb3mvycdV34mGXnna
+yk9S8HotWs5kLpMQ2TpXOiZK4/lJBlp+q3ksj+01qoqTrRXijI+6z9QRS6y7AkSWO0PaV9Ui
+uARH5bBV5qzzT2q9vtYTEkoGHj1IyoW70l2TxyfQLQBVNcYp864+TQJmxiVIVUEMSsJ7Vnoi
+fzIvgBz8/d62POQH7zvmnWA7VvYD8Pn3dXVOF7sSOxqJ+YLxcOkjpgqI8ef0Jiypa0w5Tguj
+pJ/SVWpeP410SlK4w+ICmxc3shR/pdJfVhgRGS6e8A4GCHnDHg8ksLJ8qoUmPlOWJjdfjakz
+QD5vvAV0KwWQ0mu9ca3C3NjivYSWZdiHzeQE5hXqVV8wlghshFKOcm81vdB/hsay5hthilSS
+L9trmnNdYNppZHc0bV6PYpYpHWaD1NpWQSbIeiW5AQ0EUPWDOAEIAMzeN8NjFRB53Fid/Iag
+QxxQZeD1Y9cn9S6fJ+nEoZWMPQ+iVCictH3gmqm+eZ0qOrqSTxkXgyIh7Uiu2lwjm7nmeBK3
+28k2ei1e3bpp0I2oMiShVAehxbXx5Jh29nlPDS8US/xehj6WnUeojR2qXVOSAtK7+V7taSyi
+gOpYr/+5fcq+/8Z+d+NaVp97MUmoH5LVNU2jCR+qyIM07qX2G4Vx+hU4tjc79Kl6m1equ6/s
+HwnkmMIsGvLHZG+Pq+1wHIZwt1p39MhFvob9B2RFqbFtihTal36D0NBQMdlp9PtKtQZSQR5h
+WsmWG+m17/vyux7ZtwnTRbR+p/adFx6fsTcAEQEAAYkBHwQYAQIACQUCUPWDOAIbDAAKCRAi
+RTAEaVUG/aARB/9pYfkB4rDe4OulrUXBusTxOGKm1yPjZaXveOqQkD8/2Vwxu7tYFMVMLDKY
+tvxpmrUdb6oz9s8XrTkMIW7ZM3FmhNQhxsfEj9g6UGJnCXcf+Zw9wbqmzONNCupfN5wPOtoG
+d+KFUR5dVl3+BsUoIEcAcPR5AdP9gbqDSbrDyuk8+j1CfiLVAQXud3u2rkt8GWbTJ/LzKtF1
+UM0X3YnE7fxi61DDgX6A1EJfss3aFj2lwmb38Yp041WKik2ZZEQpyQ8rXeYG8Wl0wSEYaMPD
+edEHRDoIAYRshRHglZYCD80LF7c31koGLGzVWio3erJgUYUwhy1QUltdVL+5PcVyUlm1
+=4eNp
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/pubkeys/ThomasV.asc b/pubkeys/ThomasV.asc
new file mode 100644
index 000000000..45202bdbe
--- /dev/null
+++ b/pubkeys/ThomasV.asc
@@ -0,0 +1,87 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+mQINBE34z9wBEACT31iv9i8Jx/6MhywWmytSGWojS7aJwGiH/wlHQcjeleGnW8HF
+Z8R73ICgvpcWM2mfx0R/YIzRIbbT+E2PJ+iTw0BTGU7irRKrdLXReH130K3bDg05
++DaYFf0qY/t/e4WDXRVnr8L28hRQ4/9SnvgNcUBzd0IDOUiicZvhkIm6TikL+xSr
+5Gcn/PaJFS1VpbWklXaLfvci9l4fINL3vMyLiV/75b1laSP5LPEvbfd7W9T6HeCX
+63epTHmGBmB4ycGqkwOgq6NxxaLHxRWlfylRXRWpI/9B66x8vOUd70jjjyqG+mhQ
++1+qfydeSW3R6Dr2vzDyDrBXbdVMTL2VFXqNG03FYcv191H7zJgPlJGyaO4IZxj+
++O8LaoJuFqAr8/+NX4K4UfWPvcrJ2i+eUkbkDJHo4GQK712/DtSLAA+YGeIF9HAn
+zKvaMkZDMwY8z3gBSE/jMV2IcONvpUUOFPQgTmCvlJZAFTPeLTDv+HX8GfhmjAJY
+T5rTcvyPEkoq9fWhQiFp5HRpYrD36yLVrpznh2Mx7B1Iy8Rq/7avadwVn87C6scJ
+ouPu+0PF3IeVmYfCScbfxtx1FaEczm8wGBlaB/jkDEhx0RR8PYKKTIEM7T2LH2p6
+s/+Ei4V7mqkcveF/DPnScMPBprJwuoGNFdx2qKmgCKLycWlSnwec+hdyTwARAQAB
+tBlUaG9tYXNWIDx0aG9tYXN2MUBnbXguZGU+iQI4BBMBAgAiBQJN+M/cAhsDBgsJ
+CAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAr1YJLf5Rw5hlhD/9T4I/sBCleS9nH
+njTJqcOnG28c9C3CRYIizjEui/pKmXz9fB1N9QrCaruPUQx2UacDVCl6dKxac+7s
+s3/a6lsjaRn0/2OM/sCVLScyxNPNPQs2b6jkodSNPIM8zv51g+flhwtfrO6h6B4j
+IhZgSjFdvqtZd5jaly9rA0uMX045CC4K6HGnq8n4F2p31z0L0LaHBf5EcsCM0MMp
+QVkY0aUrNg9uVMGXBHn3osHnOtQaODqcIbpa/OG+Tlt6pVOiDJ7i8TkpQKT7sOaM
+VdL//TEoDIOC7qVCN82q2q/gtiBXbziaERVs/eU0O52aX5qUhXu3VIjXTp/riRim
+R/f9BPB1dgDZbF2aPZ/rJm26v82ft7gP1Sf52E9MrAaZATTfI0/TUHXeBzN93EA9
+xb6/ENAMTX74u+NjlynWPD+hl64eBzJ2ionZF1bJFTgBkMfRYnhllvleCjcq9YfX
+md5HKCwtxfygBIujUQSwyUzn0f5DbVCJ7/B19bKdvHGSSBgBEjxqXWQskm2wc0In
+ww63goZAGDQliKhIT8xnwOBbLkqSobq4tD9zpQyxvMA2rhy7/gfFRp7TTak7MZHf
+lTJ37S5LvcWHm/ccWUZDUN7akoEDc+m6jX3uIEPMD3PQvcHhWv0amco3zDr1qb/+
+rXM7TJKd7DPX0E2dRzKu6aYRMTbklbkCDQRN+M/cARAAvktgmJflLg8bz3VMyKF6
+OHkpWBzfr9HOBZgmQTborznm35Z0BrqykcpHVGalNOydpNdoL9s3Elmr7S+L0YqX
+D3i3AS567S8KeKKenLoAV7J294KL6p2vldDiHHUNjXWSe0YEvbAw62tnigB4Wee0
+dhwAxhowFjmnyB3dZVHZ2Ai+k5x7NAEvCGgwec3oD4kRDbtkdyiXAM72Jz63hgC2
+XnnYM/XEptxPgPUkPQnpboVtQkSU5dF5KM0YK5ItllOwLMyDQ/pEfBZQq0O9Eqsr
+8zc2H8vYSxbmYBCdimzpr6HJWRV32QuyiC0c8TjG/fenNsMHliP7bOp2NhSo784N
++Q/4eM1G/KO4cvpbduNToWe4lBdfiWWySWziGUoM9rBrdy5aex1p+i4Bk7FbAGUb
+KxFnsFoc4URM6UB5Q3URh8WtyRx+RjNNfs0MDRL31pfvfTP9z1eueoznY470utIV
+Gdhn4rczzPYUW+j39m2qpMMfT+gf5rLQLC2jBKi9lSiWMBDzOaaLwK08MUeDiytp
+7xjFbh962ftC0/qs5VHBALI/XYvO0LuhgY55y1Tkb0aRUO5s1bjxW57HT+6k/1E5
+GMo3xq0GuhJyaiZHmFKHGaB0kHLgj9MORUVtmr+FE9JMAeCdyIQLrtNiGzPNc/ed
+Ofn8CHTAIYJ3cd+I1fobsx0AEQEAAYkCHwQYAQIACQUCTfjP3AIbDAAKCRAr1YJL
+f5Rw5olED/90sdEGN4CQ1tGxjkY1NZRnNevdULm82qnCk/2srhBMCamXeJTkX7Cy
+AwbvyJLZc5X6KNkDwq+KyGV61Amg0zFLU9TiwDhjfiFhvrWm0ez4+bA2lx2CMBf6
+YKH3FDTzXobIhpe3G3FpwklbcrTt+ooiQmMhI87JMtJ37CXUu1ETZ8Drukyakwcb
+y35E+rV2n97eqnovuNzpfdT5ufabu7ZyARVu4CdJooagxIyex3vUu1/Vvr8PC9nP
+8cG20SByBKY+V4dRXIEPgsOtyNMMrvN0QzDtv9w8Ge7oKZNyiAUSq8Hif1ih2j7i
+KPsCd1RPPgmPWny8WWce+FGDzYwN8ZQ8n66MGNthdE6z5YCZoJDxOCNnd0AU0Ws6
+oBti7pCVjbF+7u+P6u5zoCt2YGs6WwkWY2zEVEl+B1S6DkkUhQ3vsZuOKweNZ6bx
+xjezO7ISR7wfamLzTcIC5W2Rqg2Y0MewQGJ5+3fhmjRX4D+rbCSLHdL2/JGM4qqE
+atbfSrGdXpkV7G55a47eo5DRlTn9MgfSR+3afjn9F7F4Gr8innbMTW8S9ORCYEJ8
+IZmapO771GjyqyN5zFU3HJU+XCGWKavKJD509olho5J+LAQQyHYzJXlzq3qo32fq
+MV2IIs6WaYdkUDzEOh2yh9ASaW/4j/HIRnudrqfFullW48LcHQkW1pkBDQRQ9YM4
+AQgAxlafMFTJNL4lE0dxQcwn3RhlC2HMoihTXRpSsTkcq3ViRev4OSbmD1NDVwkS
+e4al+IaKToiA/qMRO6f7z/4v0pugesWTKvGyfj4idofGap6Lq4C/LZAKpQ1fsy2p
+2vl3kD7D4ST3gEQEA7ZeUIUG71ptInTMndc7lnbeDlSGJ4Se8OzMNAnVJgW5E3NT
+9tyfL8Kossrh6ldyYppDSS5uL8lhhYoOhJYETG1eWFnIuNTkx98fKpFhxXQdYdDg
+z/9vGwcKVVV6pzm66fy7K/WkEj+VboxiC/3DnXpf+8Q9g3Oakvx12GYCVJOsdlpC
+5juOzUqVuq6J3t1tgPNoJCkcFwARAQABtB9BbmltYXppbmcgPGFuaW1hemluZ0Bn
+bWFpbC5jb20+iQE4BBMBAgAiBQJQ9YM4AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe
+AQIXgAAKCRAiRTAEaVUG/eYvCACEqNjb2VvGZZ+Ir7qOZej4qyK80iJXnN12v8Zp
+IKx03wVU91mHhqeeDZBHKYGqw9ao7eWRRe8BMZ/VixKxpbCJD5noDmY8dpp92cgf
+mOctHCXhjzO9mg9Gd42gYvobb9+xjG9plesju9NHRtcd78EtWGMkS4EeqVcJUcPt
+JfuQf+cx7CnUb6UXLk6Zq4PK7YE8WTrEcjkZ+nJOsO5VG56N/tVzAUxU21X+P0sp
+jFN1pfXq7tmoGEX5Ad6bl3fAzGJUs2YolFkx1emzrAdDdNNbCbZhX+ipjNcRfEP3
+qjJVnr0trHdFxjlhULuERsgCaje4dWd3Mptu/FlQjIWMyZ/0iQIcBBABAgAGBQJR
+2qHTAAoJELlvIwCtEcvusaIQAI9QpGtQKCA5j3FOSJTyz8LGKMny5pAG3KXjSmQO
+mH+KZveot7Sl2y4AsUq3o0pq2lSGGaDRhbZgMfq6cHWbQ1shVhpTJA4Y0WNa0Op5
+ZDvArT1P2628mSXItieu1O4VLCbfjuYGn7XJRtkXAutQUM9xHtBZfCSkhOVdiqjg
+9ltQUwk2K56WOWHTAfKkmpUfLcWWkqpAN8aUqyMCdOBB09bskACYmnENMpx6XFP8
+X1QZ849x3NJkcQUyOTc55PDQB5XmOTkomV+WCW95r8nHVd+Jhl552spPUvB6LVrO
+ZC6TENk6VzomSuP5SQZafqt5LI/tNaqKk60V4oyPus/UEUusuwJEljtD2lfVIrgE
+R+WwVeas809qvb7WExJKBh49SMqFu9Jdk8cn0C0AVTXGKfOuPk0CZsYlSFVBDErC
+e1Z6In8yL4Ac/P3etjzkB+875p1gO1b2A/D593V1The7EjsaifmC8XDpI6YKiPHn
+9CYsqWtMOU4Lo6Sf0lVqXj+NdEpSuMPiApsXN7IUf6XSX1YYERkunvAOBgh5wx4P
+JLCyfKqFJj5TliY3X42pM0A+b7wFdCsFkNJrvXGtwtzY4r2ElmXYh83kBOYV6lVf
+MJYIbIRSjnJvNb3Qf4bGsuYbYYpUki/ba5pzXWDaaWR3NG1ej2KWKR1mg9TaVkEm
+yHoluQENBFD1gzgBCADM3jfDYxUQedxYnfyGoEMcUGXg9WPXJ/UunyfpxKGVjD0P
+olQonLR94JqpvnmdKjq6kk8ZF4MiIe1IrtpcI5u55ngSt9vJNnotXt26adCNqDIk
+oVQHocW18eSYdvZ5Tw0vFEv8XoY+lp1HqI0dql1TkgLSu/le7WksooDqWK//uX3K
+vv/GfnfjWlafezFJqB+S1TVNowkfqsiDNO6l9huFcfoVOLY3O/SpeptXqruv7B8J
+5JjCLBryx2Rvj6vtcByGcLdad/TIRb6G/QdkRamxbYoU2pd+g9DQUDHZafT7SrUG
+UkEeYVrJlhvpte/78rse2bcJ00W0fqf2nRcen7E3ABEBAAGJAR8EGAECAAkFAlD1
+gzgCGwwACgkQIkUwBGlVBv2gEQf/aWH5AeKw3uDrpa1FwbrE8Thiptcj42Wl73jq
+kJA/P9lcMbu7WBTFTCwymLb8aZq1HW+qM/bPF605DCFu2TNxZoTUIcbHxI/YOlBi
+Zwl3H/mcPcG6pszjTQrqXzecDzraBnfihVEeXVZd/gbFKCBHAHD0eQHT/YG6g0m6
+w8rpPPo9Qn4i1QEF7nd7tq5LfBlm0yfy8yrRdVDNF92JxO38YutQw4F+gNRCX7LN
+2hY9pcJm9/GKdONViopNmWREKckPK13mBvFpdMEhGGjDw3nRB0Q6CAGEbIUR4JWW
+Ag/NCxe3N9ZKBixs1VoqN3qyYFGFMIctUFJbXVS/uT3FclJZtQ==
+=U0io
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/pubkeys/bauerj.asc b/pubkeys/bauerj.asc
new file mode 100644
index 000000000..b50bed1b1
--- /dev/null
+++ b/pubkeys/bauerj.asc
@@ -0,0 +1,166 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFRL5aABCACgnvbQOPgPeyBolejlFaY279tVUWaBeFEYQ17xfI3xo87Ywb7E
+DOq1xsQx6RNGOiriKFWyM41S8lcIu7fOAtfkilWiqUCoapn7bQlDyTl7LPKOQgNA
+txIKibKyfmDJ1xyMAcyF8kV+Gav3JgucpBlYjmTdNC3MvI/6MGd4GdxG1l/4aGLc
+1xV9a38RvjZnDD0HOfyUGbqE1dY5nEVla0sgMp1h7mSyBebjLkOareidXJxK5N7v
+o+/yFidN2BiyKSQLzpftx4OIJx2hWfaTRbn+l1WF35Bu6iYhBtsvrZFZBK1bjc/A
+xHTu15kJsS+GuP3v8qH/QB5fcGah44QjM7FdABEBAAG0IEpvaGFubiBCYXVlciA8
+YmF1ZXJqQHR2NHVzZXIuZGU+iQE/BBMBAgApBQJUS/v2AhsDBQkB4MKABwsJCAcD
+AgEGFQgCCQoLBBYCAwECHgECF4AACgkQhPG/klsfSE2JAQf7BE7GHWifVHMjiciN
+bvS0SQ/hx33hn42Yd/jwYsXsIBuJcJ/81s0sq+O/JRXrhZxSrOx4ekKQ+8tQURvw
+42MAXN8QTp9lXno3jPvyTHPLlmW3Ig1wQ31Kh5daKv/dmRTrsgP2aBH0YRLQ28Qr
+gRiCEK8Ea1ujoUq6PzmmcRB3waKJm1eIUwEj1iP2rFB5MV+ESDfKXTyUiDpRRma1
+bgj4mKv6vDO0839Ho3tLyGnRYksCcS3XUqYU1nhsROzW+91YWQiD8zfTmnQ+q/t6
+VxXW9aRgq9EY8KZUy7I94f5ETRokhszOxxdv5zZRTKpWyKUt1e8zeLss2krUtJzl
+T3GWtokBPwQTAQIAKQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheABQJWlU5z
+BQkEKpxPAAoJEITxv5JbH0hNycIH+wYbhniOrfrmWhgyjWKFqvdhNA9Z1t6DPAqJ
+Di4Ow4GBEp6N4RmRrv6WateG/Mva+Fy1x/Rj6PgrJti+9CZUuvrlhCJ3SPQN6Ajr
+cwih0QyiFAPRXZ8FVOds93GUKyMy4SzLU/d/OOJ/0MxPCjbWnz6J+0snwzYAykuL
+WeB3PIeq3n97MM2XRSDMY3a5/6XpKBK+JPb95MwMbSeh6czqp1Xa96S2iW14Wa/v
+4shHXwBgC32Sk6CUu4qidi+w2eGK/tVWRKAffONULFB7cT5sFgm1l4gScxH4GrBH
+SsZWilFckkUXxxogh/FY5i60FJ58rLdGntZ8x7sO5lcdHTy5Uo6JAT8EEwECACkC
+GwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCWKW/OgUJCBxAjAAKCRCE8b+S
+Wx9ITZGmB/9CPtyBSOv9hMhf3NouFrrIZfVHW3RDvr0zPtF7Z1JQdQzccXMdboyc
+m9kAP4OzkG2uRhJtaTvGuiCd/B9X7xsbI2JkQo67rgQiesByZIuBHwugg/nmGerM
+vpApTqljTqd3yVxy68377mFRd2DU9byCyghPGyFMS8RAo5lMEEpk4kicfjSL75la
+9W4MAcHM1HZ1h0roqN3Nxwhn4RsD6ssOiGEO4LQUhzsaU4LSYk1OjHb2zvd7UHsV
+RNRLlSsj66y7nLuQFcJX0/YyqHWwhyUTKDRN24ifpCO3/HlD4PmO84FdF35b21DG
+SE5ZOywtpPSqP6R3gF1qxvSXFLxI7nePiQE/BBMBAgApAhsDBwsJCAcDAgEGFQgC
+CQoLBBYCAwECHgECF4AFAlilwkUFCQgcQ5gACgkQhPG/klsfSE0b8Qf+KBY+HW+z
+lvZbEzsZ9s/4Er/0InGSHWD8o9K1V2M2woThXlbiZZjvnJQaEzXXjvgdqd2BhAp4
+fPwcd28ww7mVBycDMqffGq4M1xKzwXSXC8oSC+zqP5po7cFppYZi0QnwATtJDdS1
+qBOCx4r6+TXndMP8wlXOAIYVPFPgvsAICOhBfFz/BPx7V/gEWj03TC6P4+chbPfW
+B9bFKUUlsW7IqM5nps9GHs/jkCArb29f2UiKEbMSlPzB30uHxqw1cma9CPvYjpXu
+5Rnw+nIThBdOhuTcAqBwgBRwI4StMAd2mBEeCUJ8OrR/tQ7BDHXWdgNrQJdybeS1
+tuEwSDm6f52vHbQgSm9oYW5uIEJhdWVyIDxqaG5uLmJyQGdtYWlsLmNvbT6JAT8E
+EwECACkFAlRL8mcCGwMFCQHgwoAHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAK
+CRCE8b+SWx9ITYN9B/sHBt/PZ26zsHYu+b8mLGENm7lw2jYgYsde03NWf+dT7a8p
+W5c1rt2ENmG2N68a8+aAgMxcn8ZJsXOF/APMmRbHfpHdshGTUMBs2wYaizlAwjYv
+nerBfSOvWSZpk7VqI2/+0Q+sYn5w1MjRu60upyEGQVM+ZIftwwrp0FolJdkDgihM
+zXcJuwxCSscqF6NsVukSxo1A5gKjJ1V9jvcXi4yUaYhfSw/hUSAjHo4hXeXbJNuA
+aBjLiTq+QMQ7d9dAflZCAvd+KsG3BBXuG8IQIz+OxTtdDnFvQQxTPzlcIq5KHI7O
+6IdXC+T7Fmf9x0h6QkhFuVS6OB81E0I000d2TMcViQE/BBMBAgApAhsDBwsJCAcD
+AgEGFQgCCQoLBBYCAwECHgECF4AFAlaVTnMFCQQqnE8ACgkQhPG/klsfSE177AgA
+hUXVzFWHpUXJbsMsdzuZ9d9ts72+NUY/0ilNaL3t6X1GFvKfTDxuc72ivP2W6Eo4
+aYWAHBYQb5a7SphvrknQetIwCM7ll5LZFlvkff0xb8DjLSLfVj4BBiT7N4pBJRsl
+2VQoqhdcul+EilXb7bYcPQGIU0ZK2epBbm8VfO0hetQtb4DxT6viuSOmkntMcgHG
+7zSgvhOkyZHjlw3sMqAr999xyV0hZRE3vUEHeO3f9L/nZ0msLpLrfKvczKrlHkNI
+IHzG80Tm5JzmVtmnc3nVGbskqZgTLgR8sIdNdTBN9j6I03wwvve8BqNaeh3W6I3P
+xgVgWxwF7ULLutld6z4mGokBPwQTAQIAKQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMB
+Ah4BAheABQJYpb86BQkIHECMAAoJEITxv5JbH0hNBxIH/jCr/qpjflwuWAIojmLQ
+i/HOZTssUym36zseOW0BN0pMdbqrinrzSXxrn7C+Yzf/1EZTy1bgE3tI0fmcPOJS
+dOCIIqeuMbF3uZ82imYg3aX1t4eaGF2/hnJWn8W054FCmR8iRO0/Ge8bPT8ZO79Z
+pvZzY1w31qnOVIflFNJla0+fXhi+2Bys6WpvEdAo6PfUh775RE2bRGO7i08nyJUP
+3fLuuWiF7rIrO14lCTBkwBYQUEfN2JbIFfckFJBieZPyirB+EHdHJG3qMZCeefee
+o8vkSIX4NfLkHB5qXkdYYwBKlXuVTXwZpD2FyIAuKRcbWJgJ8Uw0sLSRyYDXdlKz
+lgSJAT8EEwECACkCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCWKXCRQUJ
+CBxDmAAKCRCE8b+SWx9ITQtfB/4gzmhMaFp3RE1Swel2G5dMbgfnU+RkutdHWUtN
+QPZFzRE7aKDY5dNXU/NyjNgiD9EIrJwgalXo7m9TCBR4jwLqdFwLSQ1IgPNGoyRj
+x6IVudLX2apzR2ZDnJCFaJKNxxLH9pIouORk30XsBVPRSyVYJJaksdR8nyae3jNl
+LNgHTb9P+mMuMBErrFf9tEWOb4hqO52zTnKCeMdMneL7r1ZZthJhk4nKV7FUWjwZ
++8HEIhiJo2HgTUqdQlgJ+NKQw/FnO4XIJp+97eKD38W3rFjYKLH+gx+a6Ftxn2Hz
+rcwKvn59/P3BbkaS+m48nROy2lOIzolNGel8L60OkIAkX8EHtB9Kb2hhbm4gQmF1
+ZXIgPGJhdWVyakBiYXVlcmouZXU+iQE/BBMBAgApAhsDBwsJCAcDAgEGFQgCCQoL
+BBYCAwECHgECF4AFAlRL5gMFCQHgwoAACgkQhPG/klsfSE0DBggAkVZPbh84VxGs
+lLqhj6FLOJFEP52TPbmNWhKe3C5KT+tWawuBQDcnlmyly9A+fVcW8BE5JnAn/Q+q
+bwBZUZCF2tqgR0SHL3f1GOrpwWJ3VbCCodoeG/UFa3XSW9C1klre0m9vISl/NB4L
+ga/ILmXy9Y7M4igHGgzxEGdn0jo9X9o0tp3iPwLlO5nAZwL74YlH5ay1e7RsZQ/0
+RJDvrATd9Fuqog5vXFq4xJay9p8/KsMMMeJwh11BsN48DDW/JytB1juTGoTAG4UT
+0N8KFOsfKdEuEFJddyQAtS6ZtHKmmDDubYoAHPW0zXzkUXTFNM53xkjJOl0LwVPt
+Z/7u7TU7sIkBPwQTAQIAKQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheABQJW
+lU5zBQkEKpxPAAoJEITxv5JbH0hNPcEIAJwDysT3uBCsaoVQvxJB4HOussnvz8hA
+xuvB/GoUMF5lg9WUpImNM0iUEoCFWtYUBspPhP6XdVOHOwAUINqJTi+tEQZgRJvv
+PD3Y+oXhIV9SzXhVRzPvkRhcU6VVQKd7DqDyZceGGn6CRahRMdDhDWZuEBjb/Std
+Ov04GDwNYWSwpz+iU3pP5Ab2dT6oDrxKCLogu3LV2TuhTXypvOhTeFpspfGRacyf
+bcVezL+kHT/EbWVp/qZnh5v4AdqxYQulzW3JWzWt2LTdPDO4AsE+2UAse2vyPgGP
+//69RXfvrVoW9gilmP5sLuozP1AZ4KnFwOvTrv8BP/sSzUJumUdChR+JAT8EEwEC
+ACkCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCWKW/OgUJCBxAjAAKCRCE
+8b+SWx9ITdmHB/44opEJEEEboquNtYHiyjcvU6PI1jJPIRocE93klBDHfo91UbE3
+NwDp0TfeS6ooje+8Q+nWcTb19EdL+kDLRIj2i8O6amQ4p42ypd/6A04C0MJHM4Mw
+9zamihy25+ORtl25BG+qhF57jWn+r828TGgx3PWQbdenacjXm4bkyb7f67HkaEAD
+aiB1D0U2lrBaKoVYc4qTSC8mgcdh7hSB6iBMPsuqtriGqTeFsRs3Kl/P3IfWtbdN
+VAE9Le5dcllAX0OORolXgvQBBVRWz0LcqdRitRIevcZ902P4Jl4trMq4bel0Spqy
+PqNcn+Cswq95nSyLTEwlb+shK1vDs5icNiFriQE/BBMBAgApAhsDBwsJCAcDAgEG
+FQgCCQoLBBYCAwECHgECF4AFAlilwkUFCQgcQ5gACgkQhPG/klsfSE10Sgf+JPsZ
+5/dW/sDx+W3G0rfRU8PKiKgxvkmm7U4R9UuF1FoPv1iMrBMh/sdOeEwD6A6kZFmB
+rXgb+gPToc8Vavmo9QumbNVW6msj403H0oReGxxbbQ++XimTGrGQjLsjGIdmDWJm
+o1sZC1bVHMlRUEyaCRtBc5wJUGdo+m6zE6308XiSg9EcFw6ZQo15imevmiSdGSQ3
+ovlA9aJe878bJRy7MbilsDabXeasvUtCZ02zu46VfkbdlH5oDP/tKY2FdinVOED2
+94r2JJUid0chDb2FQW6cZ1WzidBfmJmwUKyMDx/Igmu4pNcYxt5q9KLuvoRMBbRg
+ylmG9Uyo0r8dXZCgObQqSm9oYW5uIEJhdWVyIDxqb2hhbm4uYmF1ZXJAdW5pLXJv
+c3RvY2suZGU+iQE/BBMBAgApBQJUS/JAAhsDBQkB4MKABwsJCAcDAgEGFQgCCQoL
+BBYCAwECHgECF4AACgkQhPG/klsfSE2+dQf9GmR7T30orDcptqjVA+63hiNR8RKi
+jJXRi8VsvX0gKacJ3E9o6MBMGWMuJAQ/oR2YYzS8T3vUbtLuvEOq3lkedyu032XO
+vDwCuEzs751Y/6YR2mitats3ze7Ey280hqYbq+NjZthFe1Ezr//ZsDYeOBhRGB/Z
+SBt7uhVmwc/17AbdrS5xJb+a2VmC5DdYTeR0bdE4A0TRKNQ/9kt9SIQ4aJ8b0ueh
+8tXO8PgFUlsvO07N/k9UkAkwWC8kd3FTVNZt5zabRUoy98ygOIiL3YlfIjaBK2xp
+n3DF5KRsmKmDtBXKs929KCgAolV8QjMJuZLe+UdynXA35E0gyUDT1j+hhYkBPwQT
+AQIAKQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheABQJWlU5zBQkEKpxPAAoJ
+EITxv5JbH0hN2LQH/0nJgXlfI1YAf+mD5JmY4FThzcnud2PpYuIUAZ5bzgMp9KGC
+idiuHa0m6HCGvZiQPJ+MEfVfZN0zvysrJhoo5uk6slf9hIaKgWQxaCSkw1pGj+2F
+8Qbg9Lx49Be04DKnk8C9KCqzA2vpaD3p6aiXYJ05FB7b19GxT4v0FAQNmI3tR9fu
+wrxMK/kl3lQok+I8fwVeWIvwia+DLJJa+Pf2bOrQginXPOrSr/Ysw0ZOJoDvrtXm
+I/RVGQnR3kJW29wJXIeQzwFFgjHI3qC9jiQqij6SCgaunGKrfdZ56qe7SwfXcXlp
+4C+FmA4tvPHwMHnrb9jXJutY1ECL3darU9QX5iGJAT8EEwECACkCGwMHCwkIBwMC
+AQYVCAIJCgsEFgIDAQIeAQIXgAUCWKW/OgUJCBxAjAAKCRCE8b+SWx9ITcAxB/9/
+Zc52sOSeyoyITBJlz2uCXcpvBuQBN7GoVEDmQEP7EBYBy3o5xs7TFbep6dVamzIF
+bp0V1TcW8aKk37Jac2WVbpdfBTu44AdLAYuOnLVuSu6sTGGct3tK41Op72RXXVYN
+1l8JAFXpHtP32z4t6tq3Tc6Rgr4G2aozYQjOzbgmBcPeZRSz5ubMTIsTDaVZILku
+YT8fwvBbRiiOoYfVThWlJxWtz7Xs23TFKwVdBYDyKQWQyvBnpIPKusd+GIjIAR4a
++P1Wujsxu88Mruhxp1iSB1gnbN7hum0MOu/ncEg4r2locX3133LU6t7fbAmleZ66
+uyYofllRyxY3FJrdBtsziQE/BBMBAgApAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwEC
+HgECF4AFAlilwkUFCQgcQ5gACgkQhPG/klsfSE34hQf/SPzpAjbpghUnPvYgUsRI
+AuQbGZANBgSBNj6K65RNNCz78M/eUdNSqyx/n/wMPLewNW1aJzZDV533ADzckvd5
+l1qfsE6iJlQlTwjlfirmVJ3eKYAS/7gn6Yrked7KjKMzL7E0Ytz6idzSXkDPyPWb
+Nl06Q70sU+kEKSEP5Q1W0u3BUOU3t0v4GsMeWK/OlIMUOxoEpj1sVnUFT8RtZBKp
+Q9VKZTdOX3TBeEx9O9NjbjTt62SSB1WCH34d0o2GAYLJOEhFNKt92lzaygytfOAt
+FY/TBJl/gnqY7CzMFtKgUHttrz98XdI2ze+GqZ2KRMCTfhWStAnwkxgMK0X++jIF
+EbQfSm9oYW5uIEJhdWVyIDxiYXVlcmpAYmF1ZXJqLmRlPokBPwQTAQIAKQUCVEwi
+OwIbAwUJAeDCgAcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEITxv5JbH0hN
+5OoH/0kWbdL7R4sznsrstkU+Z1Gi795M6tzk/1/oSkR8j9tf4B8RX2bSs6tVmHQP
+ByTVdKV48b16//k4MmznziJuQmjs8rJvMsxKleD6UTncH0DNzYUxpxhsAGj9ekf9
+UB7uRtQ00DuK+6z+aqfbBh2FgnxtpQrpsLbHvW9WI2DX0zvKmec3WlrhU4lsVwBp
+RWUyvAv++PB5ivkm4TBea10nVAy1RvLeBqPolniAW3nE+pTljQeMOMK0L5sDuMvA
+fiIiBAjMq1WUGirRmZDWRbgzD86BaVnY3+IB8pCjnG/uxX3lrpz5n+hYYeNt6q5h
+P3zixFFrA3W1+h/hBGBZtDV4iAiJAT8EEwECACkCGwMHCwkIBwMCAQYVCAIJCgsE
+FgIDAQIeAQIXgAUCVpVObwUJBCqcTwAKCRCE8b+SWx9ITXIqB/oD66hPC7m2g7NA
+cAe4sEp0qplr723lhn7fcJ3mBvCHUxUl01lQoKCSGIQX1ilVgd+xjPytPRhUy1Rr
+O1z0pldDyJfVazYP7VSq8qwbYNcAeU/efVuE57hlQJ1mlhJ+h3j1qkYL0k9pf23m
+Js1amiGb2FO7d0MSClERno4gJJ/BWSa47ZTtM/YJfvp2CV5mOD+LseEsCP4U+Uzd
+ONP0mTV4WgX0jdH5kAl7PvXb3g2n72kWuRV3QrTF1PV+3Et1BJinhGU3+YJb4/OB
+LnF0cufGiL8DR6A13pbskaFRBxqZs7x90E0lpAqGIz2Z/hy5KnqATUTF/TeDG0zg
+goqxX9fxiQE/BBMBAgApAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AFAlil
+vzoFCQgcQIwACgkQhPG/klsfSE2ViAf/a+Ayp4MdDT6zfRIt7RbAx4bdpYe3pWU6
+0jH3b4UJ36LtmqukPvoQzhfQBazwPPmOxnvo4Ias0XTgCx8lbNmLl9tlRbxYvgNx
+Nk6/Wtz6h/y9i2TPtzDe9xmeH9/nK0HvaDxWfFTp94LfJqlpYLwpalK6uC7uczh0
+kEl6Y/3pYuEtXb/hk6XjiZWj73gKkrienktHj9lQBsfph8Jjuweym7zRacZaycd0
+CiDOWBStHvq1gDqy1lggne7OPRhWN2Ttp+gEmkSboL1dV+7BDvBhzZ1efhE/DSfk
++2BR8MROCgaAGA8FoZvxlwfKJBCLygCmXUG1pcCvxbmcgN7OK+iw6okBPwQTAQIA
+KQIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheABQJYpcJFBQkIHEOYAAoJEITx
+v5JbH0hNer0H/j1XV3GiMAzbEQje2oGWss381CJyVnqJFVklpssUgNjRikfbnj4w
+06H4BCg3c5rzxVTd4aK4hyWWGH8tHwVhN7tfxLzr3OxnZOI8ftujvdwyOwHSXGJ7
+oad1Nsov7glzHFhkzBCjY1U9UERQ3T+9u+SJOZkhyTsipUIK7JPI0r4r2/A07jsJ
+Aj09yREC+Jz+sdtXrEWo+dz1ewamHPkha3HHfkgnw4yWRQ3BxRoxb5xaotlzOuVD
+z47oB8Y33FxdpXYZikajTZBPeX28zHjI5FPmkQBQ97sbyZTw5rg59Wg1A1gXV/jQ
+N//Q34fhExbcLyeVv4drUkFL5mDXYzFCB/u5AQ0EVEvloAEIAK/PFf19cxUVxu6a
+F5GXTqZXvhEszCWfurhPEiloSpoaH0aH31oFgi58KmivH2tworyUG8PeIBOcoUGm
+QUFJrXPsnNu3hdFIEkI2eeT1FBezF+newY0S3oOQG5aISgzLu7r3vvbY4JW3AUFA
+gVVwJmatBplNPrnoLwG+Nn8oBtOdMMvkOOaHnW3z62I4JLwCnFRG2eDDFYCWsxh5
+Ekh0DgJEdYGXSKIsHPm+UD/18WNG78C8zC9GyUmbsZ3zibc6GmdW3Sh08lNdraAR
+S3V6Ty2aKXq6jdi682ehKzAeSvqtr0LEUPsmD5s6g2PhfXCX0Dc/9czmaGPVs05Z
+X/3Y/skAEQEAAYkBHwQYAQIACQUCVEvloAIbDAAKCRCE8b+SWx9ITffQB/9Q5AMw
+ElZu2g0cE5tfhh0dydN5D9Z3T892lYG3R2EQ/puCrLV8xg9R1/Oe3LYvpxavAeKQ
+afmj8BIHYzuGYwMmNRRQEOGTlkisQlFmuAVgPniOf2AEgjwly0Me4eib7CHVIEP+
+tHTU7FzcVw4PPl3PbHKyPNi7MF/LL68xaJthIgzKCQkl7vGkChHJFRwphFinNHAZ
+57und85/CMrDMK6/BHAkI+ShwxVGgZIwzOq9pKbaBUVeNWhvAQWl1JBRh+e/CCJT
+9hnJJGKUTdUMjIDNfH9mEFEYkAYMH+SATTwTDumdS8ixmMVaSX3E1zblogE3NO2P
+T2vtGNK2jhXLDcGeiQElBBgBAgAPAhsMBQJYpcJTBQkIHEOwAAoJEITxv5JbH0hN
+mUMH/2roD8oBNjQrhzkT2N0amWa8Wlg0Kyc1qbkEdi57b9PVEAuTmR6AGzIlLcJG
+7s8qZHMdyY/Rg62aJkJ+ma1YNF7cK4ALVW0LUjXNiyfTnUSBgwx/QobtMUcE3K+z
+4DRLa4QYE28qaweNAA7VKeHSzC9G86BnxGIKvZolRASPW6hwDiUZfHLLdt6jLVwf
+b/b7f/2fLQDzQmxm/nwMN+qLAkv/4+vhcKDcMNfAhz5DmuAAg3OrkZEghX54troN
+tpb9QxdWdhrgTZ6OocAloqc5aFOsTY5CFqmc5lQupMsVzpXhqLiYA2OXRbh7eQIA
+402TZWn+BlhGAFxa+Wzl46MVavI=
+=bDjo
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/pubkeys/kyuupichan.asc b/pubkeys/kyuupichan.asc
new file mode 100644
index 000000000..bbe756dcb
--- /dev/null
+++ b/pubkeys/kyuupichan.asc
@@ -0,0 +1,59 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+mQGiBD7CrZkRBADjXAk5ONxSGO01JMqUjwQFZAwGynWcEVF093g6SezPXsMN/HEP
+PoxndqVhtOOI5M7ZezusYzUEH4RJNEDc/JmrQh9d6Q+CanaLuOn3c84XvgKVAmID
+ogMrxeQ7biCn9NyZf6EjqP4QGBwWQdsrb2CvK+NO8h2oLo2Aycv+hciDlwCgoLPl
+scZLBvfwNRdcajG45g5NfmMEANqZLvax6e5Wr9lHpfZFQch3ai5hP5dDetA0jeR4
+nzeWdbwD4Aph3AXrZt4c64qwkSi3ElS5UKzNRsO6APF3+COR1VXJHyN5ve6jxpuY
+69zq9ZM3KwYHztoKfHlXJqTtOx9U0DSK/xwphI+Q1neMBr3RyL8muKQvlESNuN+3
+n47+A/9+5pWs8m060jRcs7W1zAYOBCv5fb3Zeot6TgkprDrrRFitcyXbB6k1zyMh
+iqUMce5Ht785AWYZuCTjvbxJ2yuT37s26OswyXDoBGwwcPgaFyEO3SBRGaoxvTrW
+NQ+gpiW8ILjXCBoJdgPcOlxjj+mhRp3OQ1TJZojMBgio56IWbbQtTmVpbCBCb290
+aCAoYWtpaGFiYXJhKSA8bmVpbEBkYWlrb2t1eWEuY28udWs+iFkEExECABkFAj7C
+rZkECwcDAgMVAgMDFgIBAh4BAheAAAoJEHV+VfRE0xInLkgAn0c/8n77dUl71F7q
+aMolNZ8KmxZcAJ93ESJQ6UWhnyUxHm8l4OFdXXu4RohGBBMRAgAGBQI+0it6AAoJ
+EMXAxcchjRjXF7MAoM88e9oNMPdUpeu/hUMmZw6AL+AqAKCPgRmICbdH2fQYR99E
+Lyw1wGwii4hGBBMRAgAGBQI+0jp/AAoJEDiaVjzCcqEmYpoAn1kwGm12ovyG4PwS
+5rN+Z46FE5wUAJ9tQkDrxF4yhTkCU6KVOje/tufBdohGBBMRAgAGBQI+0la2AAoJ
+ELfOmxk3oYfG1yoAoIhyQvVnzA3AUPmpJZP/sfCqzRrNAJ9LmVN+b63BfgyXkJbu
+K0w792EDS4hGBBIRAgAGBQI+0pzOAAoJECIYyB6OfAP/1G0An0XtPhXIf+Z8VHrb
+u9d+e7tJodQ1AJ4v1rH9v/NopQtHdcnxFvI9alEPH4hGBBMRAgAGBQI+06DGAAoJ
+EC4s9nt3lqYLLuoAnRm+RnRj3RJJxE42yTzLP7GslzazAKDO3CHEtgPbj8DseXKS
+2jiwi8g9RYhGBBMRAgAGBQI+1BTBAAoJEElFpTfXe0P7LQcAn23wIm2Pg6nO+dBO
+8oBrmARV0+ImAKCcO5k/5ByeqUMHy7lKx3HVzOET8YhGBBIRAgAGBQI+1K4rAAoJ
+ENGVGa1MfyvuLTIAoITJh0RbLqqsoyKMIPA3DWWs8iUOAJ4g4HO/rL4foavPOyzn
+BDaHoDBJ1IhGBBIRAgAGBQI+1Y1MAAoJEFC7KXQtWafSrP8An17bXzC3iyywvnC1
+W7RfvjohMzzQAJ9v6BJn9xRORSIHY4ifqfU6BPtVUIhGBBMRAgAGBQI+1M8FAAoJ
+EEXlkGj5G7efrxoAoIKXAOjFCrxhlNSIFUpDkgtxaPfyAJ9LYM88b7Jpz/octbEH
+6o6lx8B5lIhGBBMRAgAGBQI+1lmrAAoJEFI0hF3yuSD1WDgAn0CjIP3eHGFoNTMF
+BTefl5KQWFjlAJ0bt40eHF2hp46HKS6Bbl2p09FFyohGBBMRAgAGBQI+1mfTAAoJ
+EG4Dj17go4N34MMAn0GSwAMasmCfBUhFLRxy4uYSy2mhAKDZ2uklcaBauLQyjFoL
+M1NMhMCJ/ohGBBMRAgAGBQI+1r14AAoJECTxPj/mjACSpH8AoIxJsUJr3iUoYCzZ
+0220/CbBO9kyAJ49dc3iiBArycmnGjbkSLnktpiqwYhGBBIRAgAGBQI+1itWAAoJ
+ECn45GVniJZfNQoAoIfjMXEsjsnaHI83sNdrBmN4knQDAJ9NAsEIxzaGFfQmc33E
+MhxRpo2cE4hGBBMRAgAGBQI+2BXeAAoJEFlRJ0yBj+NA09QAoLN99g2hp7h7onqW
+MIc4T9WYWKgiAKCD/4r1f7vrqR/sSYKHrahI5PbysYhGBBARAgAGBQI+3GFvAAoJ
+EGcvIifCwHAobfQAn01wzHEdfIeWy1X44PF3EDA1izBAAJ9oLQ3ES3Tv7R1rPPlo
+rSCPocO324hGBBARAgAGBQI+4mQfAAoJEHFzfab4xNFPBgUAoOk8J3Xe3ko2SPjM
+d84JfXbIijW+AKD5b7NfeVAkz9mNhYePqU53SpQvQYhGBBMRAgAGBQI+2U6qAAoJ
+EFHGMyB5fcdf47MAnAprssI6tke7MQuiqf+xOPO2XxS7AKDOn8YOQDEJtlm8qRW/
+SGG0DJ5MBIhGBBMRAgAGBQI+44T5AAoJEN5HUcxjjSIaSpcAn0pWC2xioUmTdjnJ
+OVJPV6m3h7TYAKCm3tIRQ/n1e3+SjrZ6B6u9arKRiYhGBBMRAgAGBQI/ASS3AAoJ
+EDC3rnBH3fqFUAoAnA5k7w/Y6FCkgSBDqjC8Mz9e+DQ7AJ4npy3wXIW6gk9UD484
+6CFTk7rwHIhGBBMRAgAGBQI/ATFzAAoJEF1s/WZ+hdAzbf4AoMjm6YMw4TfxTASb
+85EfShS6LdH5AJ4zDq7QAvGkPjTY2JLlwHph3afyHIhGBBMRAgAGBQI/K8oEAAoJ
+EHx6uUUZG8DsJYQAn0VwLcYxa3f7Ovk6m+xJUzcpcMMIAJ9F0JRKAS2w//tkMPab
+8YZ+sYECv7QhTmVpbCBCb290aCA8a3l1dXBpY2hhbkBnbWFpbC5jb20+iGIEExEC
+ACIFAk/485MCGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHV+VfRE0xIn
+mTYAnid43IhUSJp4f35yw7V48uWI/PHiAJ9O0+8kcnVukXQOLpKgQekH8GIevbkB
+DQQ+wq2lEAQAlZjNXxfrUYxJ8ewd0LIGmig3HSKjREHc7vuN76P56KzH7pdEENaa
+rhY7XggGQ826MyFgkpRp7LLNR8LNJb6JR4tPeeUFpS6Xyz2tj3UHIkLTbLeub3em
+W3/oinoO3bbzuZ2hVx2GyR2yVlM5hbjUFayne0D5KfSHzCEJ5SLMn0cABAsD/i6c
+62u9tAkMRsrWAjcd8z5PLf+Gp5MuviE396gP0CCdcwo3K7RAYcUyiJRObv/zktbN
+cE+ZwYclB5zJT7kiJlaDmMfwyHBDYond4bV5VKeveGcr8Xy/cEh+cN7CceWoPC8P
+wjcnylxM5UZHMrTBlaoLqKvNq/JZk1sDAau/9g9kiEYEGBECAAYFAj7CraUACgkQ
+dX5V9ETTEidutgCdERuNxOlRNFORwpSVJcvOgeItgMsAn3RblJxgwKC8S+z1NpV/
+q9K8C944
+=MRTd
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/pubkeys/sombernight.asc b/pubkeys/sombernight.asc
new file mode 100644
index 000000000..336606856
--- /dev/null
+++ b/pubkeys/sombernight.asc
@@ -0,0 +1,92 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFq/xaYBEADLjN4eABFUTJH0OGRE6mtfPz4J2WYU8B3VI0KSKnqSNwGKm6MP
+dB394HIxpxO2aKW2IbxULQvbsaMYmqYYXzndlGgQIcqX6FSNfoOfmsTW9zzLI7CU
+Ezsh4lvV5PIxCMaJdG1qwM4vEEQh2AZkqdGLCNjS1nDywDFUqC11R+XmbcpDh6pn
+/T8f7oWBEBpfYKCPWSy35oRIxse9o6pc84JxGRv57eVMofApkZkhDVf+r+Z3mjbj
+iDVmRo/cGTaEPod3332z5dh+Ee/7Tg8RiVpqrJXkDlL+VhHH3ARECw/PeQEVd7B7
+Rxv9Ss/At2Js0Ah6cQSc67+S2Fuk/c7CahItAEJO9Us8T5LhhBNkc8QD9VoFkHBz
+T7S6Xhvq2MbRdbpIWLg32IqilARYUOSDOfa1GR//5lRW6Z9XX9GEjkKNAHbeQVTQ
+kwTVjN34cHMv03PhNy3a3FVaHzcIW+NTS5d7yuyinmTR+a3DTH3Uoo2d1TEItb6J
+NJsMaq4mC7XoXSg0JflqNU3sCjC22V/fFdGbdtLGJmv1pDX8g10XZ0REIZUX3vfl
+zsUsS3P0MD8aHst6nQzsDiUSjouuMdyO66rSKUZSM54rUH4DIj9EKq+yA8qfq6jJ
+UUvOo4AXpBsdQmNBj5FLksFKIEatC0ov+/U/Nj1G884UkWXJv9GqmcGvjQARAQAB
+tClTb21iZXJOaWdodCA8c29tYmVyLm5pZ2h0QHByb3Rvbm1haWwuY29tPokCTgQT
+AQgAOBYhBErWQznfoF4gs/atUee3SM2vXl7ZBQJav8WmAhsDBQsJCAcCBhUKCQgL
+AgQWAgMBAh4BAheAAAoJEOe3SM2vXl7ZPp0P/3yPNOgatCImwQq3eoXBNOonXazI
+7y4E/mcCNz0PJQmpxrzrbM/JEKx0ARXQuRw/ai22+tPM9XNkD1ewySs6iNMmeT4T
+YDQajNCI7V+aXlvLdbRnSwHaXC1gT/LA65vraTRuzkiCGkPFEmvUFO8ax1pdMwz2
+jxpXLRo04NtQf1BHAi3iM0BAd0+MY5xd7Dcqzdp96Kg/KY89g09UscQ7O6HKfM5k
+DQyOKfUgGJJ/rbON7OuFEJCrAbIPp+xOysIz+7ay602lpPnbNnmjgDcg3V4U3O7T
+vZQqXD7AHUUA03PDUUtAfpgNzSFqbh0T3UDjQI/Yv/LOQmHqvD6XxEiIb42Ttj0C
+hmWbamXV/Q2Fkt5QSvmXeTAEI8R9IW/Ui8+WvnHwxck51P5PnH/U2raBgU8nYpCv
+L78m9GFsScgrf6MMWRfh4L53E7eS3Mb4YTUgIgCdb5PYlDLq9zlyQiDFf9tW9V6A
+b7db/BVUWQ4wu9lulumnbooUY98DaodWL7TmAbOgqmMPX0KBOhf1vNXIuF8puIoK
+jowV9FIYD0muVICHY4U0uMO9hqnvdRjQXssTY1u4io3bfG4l49wnCNup2rRMS172
+uef+xIl5rle0MsGBlcjf48Fuxi1FnFNzeRrVP8s8NEof4/men+3a0bnB0GywHtSr
+D9HurGkl0mOJOt/piQEiBBABAgAMBQJav8wABQMAEnUAAAoJEJcQuJvKV618UgUI
+AKi/tyFRSXqef3qUpdLG+2l6TTw8TMejQGtifKAKZ/sBWtIY3UruKVaKlHqb9vXR
+/ZFMezQZoW+Z4mA79NS+T6yIH8AnaN6lIIbXEQ8lzJHV744Wp+vWlluqLD30534m
+bfGRFmPj8jCCV5k/GHsSUL3GTFlfxOOI8wQu/xENqRX6HjhH1pKOQfYqOBbxhaOv
+IrRYlL3Ia6CVJpHzJbQVKOwu8reij8MZ5hE1xr+Dj9i7GGYVlz2ZPRfOIzM7mIN8
+v8nMXsrb7sEMNga9tDD4/YV93rDodBk2bEk+yVR11/uX8BC1bpHoAncd7qbd42Ie
+E1ZZ2scYrMPz7YBkTEh6Yum5Ag0EWr/FpgEQAKzVKZzXQ0lTXt+B9Y+wPiBfGlSu
+NPFEf+jgMEiwjWFG/G47pDqUejTHovg/L6WA2MWW5sS9NfcSsq6FusnSRYoHH1Bj
+er+HlIGJp102m1mdvMjV/2CwG7Vv/emzzguy153GFpmsP70aGqno2vXil2Z1/jKx
+8eSEbHoW/n5nJGq9E3p50ZyRDy0ZQHlZgpoU0+0qbMQawo4DKTgfDEGwPLt+8+pR
+4dF2T5Yxomf6k2NN6l2xuJrIl57tpanPBkwC9/qLLZpww/leX1g4GkQx7f7kic1F
+jCPDpSYGXITkkT253lHXiy3ttJGT2zPwgvKxuktsj9Wb5vpc55NH9inKCnPXKbFm
+34mOfmFGlZ2UJn4wG3OQgbMDtfUnJFo0L+thjg7SDDXra7NHshmslp0+YvBfVttn
+0C33bFCUc26hUUrkcemAsZgphEOJ6aYMk2A3dcFjylt/wID4jHVhpdhdkbqBhA/2
+BJL6o9fpttE7+oBiM5ihSClkAppRk51oLis+Eev28KMllvXsDa7HRSkrp6AFYaHy
+U2pnOAmTvOxN6+oEJGS1vKfCu4Oy64TQ3Cs7JXP4sgwF8pd5LoYPsO7hfbwGMN62
+fEil17cuA0Lz6xwFHBIdJFfSbJjv2AmZ7P/220bUW9jNh2Vol9Cm9JfTQsAY43mF
+Qa4yMLdd3F3dvfCFABEBAAGJAjYEGAEIACAWIQRK1kM536BeILP2rVHnt0jNr15e
+2QUCWr/FpgIbDAAKCRDnt0jNr15e2UR6EACR1qooneG5onL8sBhjVW7TRuMx5F02
+FJJ90KKcI+bOUpqSHFWAFLmjtMjqVIzFuz+Mfaer6KtCnLO7RAtCNxTvZ5N/qeG0
+RH2qQ/ETPAASEn6aCARDtXDG67T1GNaTo5QIYg6ipD1wb/ptkRqD0yK4Y99aKcy0
+uxMedc10XJDXfNeGMWQoHwn1PAlOGQTbt4JOQbH/VEC8/NDrPDAd2fw2oIC2gi0o
+TSbIY4pKeFOt5F0UIkDzctR3sTyvLXuH7o0azRTIdvVogouON6DtizQf/i2qi/5/
+tCce8JksRzUf53lNDBj4gLMttv79VBtTf5F5e5mny5N5JmaTdkpdZtDwrQN1fAMm
+sIFDND7fIdW/7uwA11yTeYoJGxcGHiDNVqdWcSzleL0uhOHZTvbsf6Hu743j3L/j
+JMqllf7W4rPHx3OjLC4dCgUxA3gg2pylYSOldQGE4I1JJLW9La5f8yNvjXOic36O
+rHKHZPO5aOGRqgLnJwDvPEyUSc4CClKDN0a/9gSgabSjoQZ12kPA73qTI45onMYI
+o2c1y9KzvGVTrDbfFMNzjgSGfiSBSS6WYs9iIy0KqFry3UZWi+P59ea+cVPcOrcN
+2p7zbJaPijJtvKeInT51inTjvJTx064nAB7dgFYklhzCumyOgVl4c+Zkn9Rl+h3e
+LtctJR6oxIvVDrkCDQRav8ckARAAp+3ZGLyft2kClLJNYy9NTTOUZqbT4FokAqpz
+39hRdt10GcV98TyWS+faOHf2rLXQ5OMMFlJdEnsvU4DDQNMY1qUmaThEbFupZza9
+qBuqafwecfQALIRssvDnSVmcRauQJOfVCkFiGjx9vYeRT7VgWycqHeh6NuHQ7wNB
+i7TW1Pb1X1qX2xfdZPXTIeym5D2RhWJnCLz215PyxZ6ypwUmrKKBuMY/YRqca9VE
+AExOcyYFbs8ujojqMHCHzidZYPQomCp4JoY6DgsXlp5Y7TxFQDjC411vrWEgXQl8
+sR2icRb/fWQpn7cn8EbNJgJvfAH7EH2957FbpZar2ql329GoNZQAQv6+NNKgEdzj
+f1Bm0X+uVya9u3ZBshrnq/wzI2HHZ3UW43ZKdt544us+KjouRRLaFPDvKLhoFa+i
+VFBe61+eebEkhWBtKXWvrRtRtgdnbWnUu7b1ooJYE/s9O1HtPkRNav6v6p0nWJCL
+b/jhOiM3TraQ6D0nZxwNRaiplCjincXlP87laNjhmkiiyUYcepmM4X2Lj1gsmbWm
+KTt7TIvj4iVLhzNj7YCPH69tACnN8D5GxvnYgbnWTj3ipB236i1tAITJBkbf91fM
+xWI9Gul2zIf0cvelhoVE72zHRRNln5ytUvCNlBw8M5xUIx5+xsOpbp8xnxdfa2ba
+27GLZfEAEQEAAYkEbAQYAQgAIBYhBErWQznfoF4gs/atUee3SM2vXl7ZBQJav8ck
+AhsCAkAJEOe3SM2vXl7ZwXQgBBkBCAAdFiEEbXohFtqQngCsIRCLsztfIyxicekF
+Alq/xyQACgkQsztfIyxicekF9Q//SvZcFhowYfQSBgXNl1Q1CZQwlapDmI2xmudC
+hCmg3w4uRLMhLemoHae6OWair9kYGwhC1GXnOwK7qiYLpGKjCleNuvcErCZs6IUW
+3FRoLaVaRDAMw5FXsvw83+oHm5duXLiCFSIIAdIgfcWrnkEGG8Oz6GwgZ9cgGRUK
+satDtGapzUlNErMgmTJclmHHxwLwbD+quLiF1iZXHGk6aDtfMtLssPFa3XBiRkHu
+6GZMio4z0FQVaW4XSAEMBWO9ch7mdOGM3AMQ8yPELxmWQBxrusuCSY1hMMXjEjU8
+DBleeBduHZrsphcDoAlE2wY8cIiZgl41K5yBCX5N++9qpwSCsuOK3suidzqCPLCU
+y7eY0/4UorbmNHoCIPl5oOZCIL+zv81DTKGFGd9bB77ggVEGzT+sDNvrmeuvwGYw
+e8V7TA9gaxh4P1+qLwN5USqSeu27AhrXw0Hgym3vLUBURLLPVxX6HPapJhYsPwPR
+yFlyJLYVoBp4Y4k4YIeqiWPCc+a1nlTfvK/OJDvvCzNFZh5K26csvZnBrortmUau
+2k4zIg/DMJn4FWwveQ1HNKmyCKbQWCehCr2qa+QOG6R+FO/TxFRCsWNabb7blkRV
+1QNBOhVGp6g5vrJQLzc2kA9kEZRlHIepWucpxOBsOPuOQR5/gcM9RhQ80HuYGY+k
+fkBcCiTqShAAr18KpfDmSMBng4DX2WJVr1aTZ3ntJeCY8F0T9nEN/rPPZ2R/4cEB
+8obaoyKAYYzaFtRujJy+IvKfDE9t9qarGbvEauy97dU6cbhEyCGqfwwDo6WJvwx0
+kdl8IfNqYJ7EJiNtox1Z7OgidH0wmOHaxRmX4t5Pnny3FR5zCfAr8bf5e+v94OLW
+SBV336PhXhZttoCzCpZTV55ZNV8xPy8AD0sTpOJ59w1FjZyGKOmtjzBqJFrFy+EN
+JXW0k+fSb9ZLORlgJR3bxI2Q3LVo+C6Hl2vW3A9BgqXwT9cYPW+5Lpq1iS0WuGz3
+B3+jZSFkXYFYFpNPmi8u1exItKl331qxu8g5R4RCReQhmL0of+kMB90sKjg2gn/A
+2dVORjVK2IbHy3ftFCiku5SiqK1Qs9U/ZawsBxfuJpE2bxMWSZ9qSr5Ef622wTfK
+EeJqSBO4MH04JFD5ckiimg+4Kad1Hieg5LOgavonVV9xvVMgsxQNpfoPFYUTakN0
+abDMKxA1b6R2gxIvvp50M9J7t/BnqPqM148Q20Cgm1G1AK8z8N+2cX22hYcmyt2x
+LEwfWaojVFp518+6GT8hmwgjROUGC3OWM6Daq/F/jej/sI7+Md40vI4Bn8t4gdEA
+k4b8JKqEhlT/nyvlS5ynegosnLIMNlqO09El0wpRHTJoxb951/GPMM8=
+=+v+2
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/pubkeys/wozz.asc b/pubkeys/wozz.asc
new file mode 100644
index 000000000..d82eb2157
--- /dev/null
+++ b/pubkeys/wozz.asc
@@ -0,0 +1,199 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: GPGTools - http://gpgtools.org
+
+mQINBFFI4SABEAC7U8hKaLjTBQb05k067LzsT+CTfEVvCA675fxp2413SKCLJUL5
+sfBZF+2fsXGqZA3SoA79/y83414YYBogOUxM8OFhIQknSSbukpELcbJ2f6w4GQze
+xVDak0FH58mLepu4dx27cP+txvC0JCSXFETe4yvXd0nM0MPj/bbHyhWDZ8qp11qW
+p1GtOyFL7Xtkt9Wcbhx2/1pA92H1TYnnfsEqXWgH+Og294Vsjp6novaNerjJsRYD
+UQucpG6scNw7DNQgRJE/tdWvj8n9Q/RSy60ZekZlLpFd3reNN67nV0ojI25Y5zOu
+H0yz0XSFx4ZZakkP3d8oDsMdWPDycFhQjJGUTj4TWfBKo0cn4DzeoPBlFxPoyO8g
+ruemw01fawlOA9YXLe6y3J50l3aZOvME0JzCGh5M32MvWmAywVlc47cjr6YA0uOS
+uNuQzO4XGIPwVbfIHUz1+9BBffgpeBA97mm60e+UtuAM50fTWx5ddiGlkERXCdps
+NCBfptvsM5b0Efa7v4fhRuAj0lOBE5KbgWaOUGLOAnGJ6ZxkCjzmCcGKPICufxLU
+2errH6xk9z1poRsJvVH3l5SsIBbFmuntJwiKCHRy/tPzdnGuFHcQLYihcrjSXaer
+siiPcOEHjgAezUvNvAy6MnIW7Erl+zYYUS1k0QqAslul6iPZ61JW2hjwrQARAQAB
+tBtNaWNoYWVsIFdvem5pYWsgPG13QGtvaC5tcz6JAkAEEwEKACoCGy8FCQeGH4AF
+CwkIBwMFFQoJCAsFFgIDAQACHgECF4AFAlFI4tkCGQEACgkQA4wJ9GLCT8c5XxAA
+tWNVR1pQhpsBq8J2H0xkCxfeRtuNb5gEq9fmusPW+7Dad/CL+KVIfpyfUSii1+Te
+zE9cNCb6I7xf7jGWPUrKohl5IPLHo+bYDWIL27EjoDYIW05v0RYyTHA53fsHNX37
+mWd39xyu6DZLV2PDfAqILra3VLH/luDI7kpYfvuHAr/AqFAGByKgMr/ZLLOoDcfV
+sI9k9jiyp77VHbPjvTIHan9hhTT7Ee8oCSmxTsZ/ySJcMf1ROKUeCq4hGTXchHSX
+FXQNveCKChEL862nVuCQ0betcXYKPolQG3DJVfihMaEpQYP3bkyerSTpE1TRbZTt
+doeNvwFSvhXfzhOC8UcuwuD4ET9dS+brq/r2IvM8FrcZt8dEfReiu4NbIXGb+V0A
+/fqKatYs5cIyy7hY0U/aILUI7WEaxA1EQ4wOcLudgM6Y4hF/df17RMzofCrXrh/2
+eYLVVdVMj9pfj/MHdlwJX7K9OCmzsN2e7PNjwu/wgqY//aG2tyRdhjmeI/ybwLYj
+y5sJ8GJkekCrnZbkiSgfBxfO0wNPzq+JGF95qVB4RrhMPiOzLOHyU1RUyP8cJqFr
+St1fyHtfNa9XN++r7nRTIMFWQL5nyQ9czJEOSKnBMbADtcSs9kxlFYTAMnfpETGu
+XLAONS3wufi9qEN4vIwG/xRqc7I4f6CUPMtSwj7AzQaJASIEEAECAAwFAlF1UFQF
+AwASdQAACgkQlxC4m8pXrXyCHggAp8qekPNR3N78WrCiEVqama9iqoeHZxhzoNaB
+9ELyH2Y8PP8FAcpFSQK+OmoAOwCPBX9+b+jTXwZYhjoMI7yrSdxcUDvE0PUpO3q2
+VV5A2fKQuljSmzYuhnjUgA5ZKbj1lwPjbWpXUR6q8schnu4zDzasuz53bqYdDVlb
+qO2IBaPhss5Hc98vTIRMHp9rxbET3Ict9a6XtMYhTKye5jf4O5JUOsNPLZWZeSn1
+QGxaxbJ4ko+/NXvHN2ZdNoFyZEm4Am2v2m/tKq2JLMVF5SXUtS1c8MCEOO9byjsH
+CE/X7GHQoC3IVxR32wdkwzZ2uZcVG7Zm+RvLLzy14WSPjjRNPIkCHAQSAQIABgUC
+UYc4zwAKCRAyidZ6Uw5GxjE2EACNU7LHVUpAQFKjxKN03IM0cd/C6coHObvGmpXY
+sMFMta5iyXafmo+MGrR52kEr8xrQ5xz7Ozw2RMC83PyBN3P9nEirQT7gm+gA4m8I
+3a/jg/py7iwRh49XXY5Xh7eMAlkvy12DMsmkCHx9yQHVv+Rdt5/N+ELvgeL4aZR9
+N8/J+6Lf/MV5ZapxXyxessdOQPF7Ik/EMi8iAPZYkjXlA5qsfGONqS/hs8sJJ4sF
+rwndgqMTtsqmYbqwKnZgvSPpMj1CclbXorSVjzBn49/9zsCiv2twsVFr2kS7HnsX
+a/Xu+4OLdOCVQ7w4kHkrIIuRQ9Xuz5uFNOTADvpjvwbxeu7q1+SORd+kEbPIoT17
+ecQoM6/POXpUv7Xcja67PCL1O8xeLLjsxco68uFCtKGSmnztOGtw95+KrLgzLC2a
+ron9D6BcXSOrWwlyrxLP+0CWBnXBDRY39yfXF5RZ1tIlKPRBJEyBPJgIPoS5Fxlq
+rN+J3TzvVK8bK5QXTi7rGnSxrTM6teE88jOESBuUfT3KS7Lof3k8n+GWUOdqbf/k
+K2fb3YG49w0grQq2cBCs5gaaJLao37xQMPAb9ub0dWQMImEvwJmVn5m9ekcM2KcY
+Kb+qpRPZcwJEZOPW7nyl4DbqCKpuiL8BSl5vg2T/+8BdlRIVqj8oH5Fwmbz7OR4f
++LEchokBIgQQAQIADAUCUYeHuQUDABJ1AAAKCRCXELibyletfMv9CACcBOWrLaV7
+cXnb3NcBt9EBUchTH8J3JCI2zCE4oFNw3VSVzBA/ocLAm51llvmjdyR1A3/6RmOZ
+mF9pLKU0opOXBLjZ9paD3D05xlaOesuTNm++lPgolnMjFXBiPX3t5ulUMW998OHP
+guQYaxXIOBIGqqZ3SP8p0OMZcpZCNKwfexELmFknyLW2RcHnax5GcCIB+yDl39JU
+EbWx85jGcIwGgwSXtxINnhYYjz5zJMjGZfb4h7MdnV3fQxt3+nNVvA5ePTeS7v9E
+sQmnjYd6BFekr8crb6z7GQNrJk8DUjipe+PgA93EskHDMjiYrHAhIS+VV6MVGn5F
+MJrKjx9homCliQIcBBIBCAAGBQJRlYDaAAoJEO+dUAE5sB2JlIcP/2KY5EDXlXwo
+iSe6OhGZNIyC7qU8SIz06WWAPj9xlhjyjl54sWce1yOrwv1ZeytAEsVyXEQKvNGZ
+IcTto/RJqiSag0f7J7mZDcw1s48y2EPYHDk5yV96AT5uSaRUkmMpWPMK8Key0hH6
+EMfhZp4H/ziEn9VnKgpIpS9nQ/K5a6JjDcwZiM0TsEtfaDEC1/SFtrUA6Rf+yzK3
+xLHXhbPnLRlphM/fUoXqS/Zr0ft2eDwUlhbB6dP1ZfrxCbndNq97dOnxnmgPIxZ2
+RVX1pek7XOyi+qgWC+TpJwfzGX5d91do9tPjuC6+l8tR+CGrslHW7Bd3i8AcekdX
+L74JbQr5CXUUTEEFPZuKY6iOWGoPHyEy/o8VYztuOeWcZ4dBgrvQMBFGbu0wciPe
+Q/d6mZw57KuYfBBs8JpBmzi8MfCCh37VAPBMkYMfWk0FuaelTW0eL7prjEn+4Zlg
++iXDOWFp4OtY06aPMc+h0GXK64HBX3YJsYTS7iZNtQxSJK7Li3toREPy5kkhsYxE
+3dRTUdM5px5ekZWEGrRORsxOenQ+TStDGqfQwbCj5noUTy3WLBNwNeC7vxpb2NYI
+VkNPv/GS/29NBIkBbwZGIS5j82+SaeEo2/9ZfXiYXV2Va2lbH8OTIyhXLFXS6FQo
+BzkCFbsCyLICe5PLtwASnYKa4po+nKcWiQEcBBABAgAGBQJRrggZAAoJENtpSKdF
+kwASi9gH/3CIAuxg+oBYUDly4tM+oeFiWknDEUcroY6i2BdHq6n0O2mhvmd0+BuM
+OGHi2ilYZ9SeKazgFvGBIlD0wdpApW0EWnS6b4CNa87+ckTpBDqXTXNTyWhKuGS/
+bWnUDkAxAT5TxfX0jmTDVsjUT5XjL0acgL7TVxJZXbDFRMS4Qt8z6V2Q8JUY7wwD
+ArWri5QaYF9OnMm8rj0a+XnAdLvgTIbAy3w12aq0MEiI45++r47iyL0kXxqAFpRo
+YywSthtwvbeMDlGsz0HQfNLUszxWaroAyqARBtHvUlYWXmO9Vhr8ycFuZwrZ/GLm
+F56CHTUCQUXN37qq6jN0tkBAzN8Z+EqIRgQQEQIABgUCUbytgAAKCRAD0kzvlgTT
+5Mv9AJ0dknWViyDyho0xBvdU8otET/DlIwCfZYMlpofCbtxM3k9NY0FClstdBg2I
+RgQTEQIABgUCUbywoQAKCRAoUZ/piO1lEM/sAJwO4ULlzNfIKurfByPELQq5kEzU
+uQCeJmBPZJZouZgJIZxWtRKwku+ORKqJARwEEAECAAYFAlG8tIkACgkQS6iSTApa
+KQMkSQf8DKe+KKk5H0X4r3c5FSsaU4FchP+5BhiE1zhB//US80kJGOcdxLOgY6Wx
+QID0csneIhz/RrPwmHBoamlKlBEwMJKVPHeTlEJ1tBS62DyBsfTMZdPLxts9Z3da
+Py/sKSuG1bHuDOlm6quzm3h4gEmjCvMnJmWnBGBq/rONz/r8AY2qsAvVWba9lLVw
+9Ih5wVLTtUwG7O7D/TYNRPpVNQpm0VOuNImAiAwj8A3iHtYLN0Zq1c8hrlJQ5eF4
+aWaX1F/kSNIL4DL1bQMojVd/wmMp41gzI0WcqodFuqLalWC/hxMxKjJ1hP/CGUA1
+9jjJYKbZPryIaQzzlqHXe5EaZmvSP4kCIgQTAQIADAUCUbywVAWDB4YfgAAKCRC5
+nwhOJxzwubV0EACgd3vRfbH+QCDeB6y2JS6UldPahx4XGCtH24SAhqGwBNSrLKnu
+J/8gl9UXc9xRoa5as3/dVR3wvalmQ9If0q2CzzpvhDjr+xFH3DI7oVr31EjzIrs2
+U558QBhKL0SnUre0Tzg0gSYGhBeVcaN3hc/iaYdn+qjDQKIbm1MPqion6zPt0EhP
+NRjXR6ttHJmr4os0bCG4Dl/GMKYTdfQRiKlhy1R4d1XvaoK4VrFXggt4gpw+YHxx
+YW5Mxk4M8+bxBHbLF63v6x1U/Op9v7xHJvUfysCHaZyi4QYy4H93fbbzeYeYEFS5
+rNX+/Y0pIbhvLDdhe+mtMSCpFJrssXSISmxkmPYSKZ6BwF8l9UYWBWF5Xwv+d2m5
+gyuaqi3QPQRhZKraMpomUbZjr8TZspDdQG3PCxFuu/H23QxijM7it9LOBzxUXe88
+jTOKMtqwAHYZWDfvuGQSlkU0O0yz8OJH+sM1+FyAsjp9RM8nCJJNRFzTyxQmad7U
+nclErOWOKeV3wTpPS6iMOSv3wOqFIjSvF7xPvHNtL2Qfr8j3zxCMt62X6k8WJVok
+/2CyrbN3dXRuUiZZymIez9R4tF3Y+xf1NhqvEh2sew1WkMjtzhBsHWCMjePgqwod
+QtdQvP6c08H9tpXrKagph2WVnPFj4hQfGNI9Hc8bH166hDDrEwo/LWDQo4kCIgQT
+AQIADAUCUbywVAWDB4YfgAAKCRC5nwhOJxzwubV0EACgd3vRfbH+QCDeB6y2JS6U
+ldPahx4XGCtH24SAhqGwBNSrLKnuJ/8gl9UXc9xRoa5as3/dVR3wvalmQ9If0q2C
+zzpvhDjr+xFH3DI7oVr31EjzIrs2U558QBhKL0SnUre0Tzg0gSYGhBeVcaN3hc/i
+aYdn+qjDQKIbm1MPqion6zPt0EhPNRjXR6ttHJmr4os0bCG4Dl/GMKYTdfQRiKlh
+y1R4d1XvaoK4VrFXggt4gpw+YHxxYW5Mxk4M8+bxBHbLF63v6x1U/Op9v7xHJvUf
+ysCHaZz/////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+/////////////////////4kCHAQQAQIABgUCUcM1sAAKCRDofrZNT3C3NcoFD/9O
+LaxOr5SjwOEdEWGcfnIYrM9W3JTnPv55Sz5zyKkgDd6p3fONnrSVz6UYMEFnkMPF
+jn8KodOnyFvqVrkwko8FP8BctI4ztyNG6i9xv3xu9iOqt2n49ANRZM/tUtwTyb7q
+BXVQwcMmhVJ/i8MR1yYMraFA8YM6ovMHUszY3gx/x2Yr97S/xAtuauaRO0sZasA1
+UyxaZxWHGrek+/tinuuWp0Y1b/iSyW7tC0WV/tFO0C8Iv5tRcel/4b1EsV8he1EB
+8qOLBrAXcrJy3eC8H11h9rkfwL85qUf81oHYYg/vfmR4Y151VglclOZM6Y9kLC/X
+8yIhraa6dUG2prLxi2xEnl9eovs8A5xhcxPy2CHcJLRwR5xHA1IsrPK4aZsRC6cw
+Rt+oLFxLCvGK8MjW8O2qMjn7u/xeBTnkEEOJpe3NQH+1jql3Tt/3ZkPtANR/Oly0
+/7tj/CyR37GUWxM6pIXIElhQ19u+5bGwgxdhD9Htwbe6j2LNqD0e3MUrQphojzIk
+pjbmvPU8Xpk6upYFAu04+X/DfANWv61MZdiEHhEiySxIlrg2sthRooOURmmXld0L
+gzOCwJzMOjcXkaVDt3nubJGhfetHT4K36cAWBfRURMuC0hUQw+WgCBRzNT5nXBDi
+6Ng2l79fXlrY3/AQXDf9bH2EYpHBYXLySqBnxwBWS4hGBBARAgAGBQJRzJUrAAoJ
+EHbCDUAgGgwVAHgAn2Lm4q4fsoPwvy56i5NBWItY0ReGAJ4jpm+rsXdK+mW8gp4V
+r4RQD4bjO4kCHAQQAQIABgUCUcyVjQAKCRAoOKHZD9LVht9TEACeUgvjTWf/C9j3
+8fHj8zd5OrTy9PwXXfGvAZ17THgnzZNy1/U6zIhLqdUUwvyqtRU9EYkZ4YFquCPS
+qqZbyNBzpjdFARpoxL41xXSATjaaitHrywYYeWTb7USaUkAjOz/5krsGTuVsM2YG
+CLrYn2OVFobLZ0alO7D4mrAwLzTyat+WXuJA8V6wJW11r8E7awf3qdoI/VWGbQch
+7d3pYgfp6Q6ZRwt0dZDLkKNUYx41x5saM2wo8d/TgcHWuACnVV5eX9v80GToXUsm
+12ruI1ctRqEqsgiTLYpBdrevkzZgHTrY/VLTg5MZxWayUvSLn6abKalYNTvs6HFH
+2FuqgY0dSS88ozFbt/WzVG8u/2LPJ5loqYBREawJmdYjZCS7TI0gjq0rpL9Ppwhz
+3kkNGYKOlXpSiJVMBL2yiBkqLG7KYnzaJrFpG/aupxnxGPHjzdoLOd+Kpi/K+tnK
+FoTngfVBJxPdNG8z2a9TqxeLtXNPkJmwKQUQPibVFzKvvVkbSp7wQ0dfyuxPcHXu
+O0Jf3D+/AXl/V0BqrjIsoJBWhONavt3RnyiX1a2UDhTE3HFqblctg0E8wpG649dv
+vnZW57/a2PAqV6B7ztZ33tDUtLHnMPOT/kWQRdU+x3WKUi+j1GM4dWksLr2FeTUf
+C/5Tekmf0w7TjH32rHU3l4Pty8hhpIhGBBARAgAGBQJRzJW+AAoJEAqchymq1vv2
+/hsAoKBSCdynoKY1+RY8WF76WJJO0GxgAJ9p+/nu5kaId8j/mcVQfSULBl5MdYhr
+BBARAgArBQJR0eauBYMB4oUAHhpodHRwOi8vd3d3LmNhY2VydC5vcmcvY3BzLnBo
+cAAKCRDSuw0BZdD9WIHTAJ972DSGqdJ+MlRGxjhWtteRFdrJcgCeLLzVFbi6C380
+Ob0T1kEaPnloUrGJAo0EEwEIAHcFAlIz079wGmh0dHA6Ly91bmRlcmdyaWQubmV0
+L2xlZ2FsL2dwZy9wb2xpY3kvMjAxMTEyMjQvNjdkYmUxM2I1YThkNDIwNDFmMGIw
+OWUyYjdkMjQ0Zjg0MmZjY2I4Y2EzYmZjNzBiNzkzZWVlM2U2NTI4NmRjMQAKCRAV
+0KYu0B4ZDKl1EACgPo7uNemY3DIuOpUfuR1gyxRLOy93/dyHZYyrjCA6qqFEfU7g
+i/2i2ZGqualW+ot/AtyHztY32e4KQFJ1msnLk9RKwrQUHJCF7HgNo1Hn3dSrmYxH
+vAaMUJ6MRSQF2kKJjgbTa754FzRO5BzMqKcgffn1Dl6JVhjNpVk9RYBx0sRGP8tf
+qggsSbEYTtf/s08IhZlSnBriUBFU96eabYzbuGdLTGDQusf4OgtkTcu0mhmNv+hV
+FgvB8/sMToZzhLC2LyklA9t3W4S+syFnhI7qutLdzgVZfdXe7ZyYT/SN0PZhZQwR
+Ehci/hczbE2mO8k10HvUiOo6TDrOBxWGg4EAAXsRTKLvAwcbLuDDSdfALnPx4agq
+dX7pNYFMG11ct218p5AdwO8mUxIIvioN9TAs3ZFw2/OMA/I2L5Wm3etvmPHFWUgV
+Z++cxXGV/i0rD5VWUxdqtuw+l3Dx5MddzROlCyggzrTE7G8lkxGv/NoxE257/dJh
+oeyCnvkdEfZeQJQweeUP14EHcm322AZpgQIqQMYcsrlVJJVEWXitocbfUuGbhIKo
+yNi+7y6zbixcNV+OCxhqeEVXJrC8ZVTaIOPuQiPYJCDWQEuClnLQ46lpBVfwPOu1
+VVgg2tBiI12jYlOTL6XaZt+Gkm2JRg8kX9CkwbUhtWh7GmYp9jFhUeZQV4kCjQQT
+AQgAdwUCUjPT1XAaaHR0cDovL3VuZGVyZ3JpZC5uZXQvbGVnYWwvZ3BnL3BvbGlj
+eS8yMDExMTIyNC82N2RiZTEzYjVhOGQ0MjA0MWYwYjA5ZTJiN2QyNDRmODQyZmNj
+YjhjYTNiZmM3MGI3OTNlZWUzZTY1Mjg2ZGMxAAoJEP/OHJpPrfGXAr0P/3fJas1n
+EGTYJQDSyvVRREklecwqlEsP5tVru+yW7eswSeFPZnZMab+4Bl+D5pLmp9zZ3oFc
+2x6ARb/e9uUNd9SkpcOovvw+TxKRFHeRbIokJz82aLEPK8ascYdbHLG+0LMPFyfA
+xFYLQD7i18QX1HGN8FdoVuWUVp7/UO7qMcBc436RZhbRMfmSjsLaB9HDny7DOlIq
+ScQYtHbQ/qraBUOrGgI0K/APN/17E/7tt0Amzg944YlfOXi54OwekB/i8g0+fIEU
+FQ4b8sjSWbOZwUiE6WC2g6TwYTzSmnKunBjuMXGH5se96VkRPdFi5yHYjt77TzEd
+b9xIFztpLwRlOEFD+fzzJ2zqqvd319RBn3IG0rwVOC9lr+pRHEYMpd8R7yAd/v1/
+kOQppzgHEqzcfNMQgbUlIyoXqnhFbMH+NIkOTH2Lda3GkFC93p+TOqADbwYOBo8g
+YfsUnEU/dASA18D/hshRg/Wjlq3sdJkLQfgrqk0CxVOgaIy6iDz4MerXxiLJG4uH
+chgmGJLuhEvle9QB6uGNEa48DOETMUDGwPeSPqgUsk5zxgdCq/qOGH2+76S/Tc1K
+DwI8MTf+/04dUbbj1whDuH7tfKdEHrCl3UoPV4gwKbmsoMoZG/sfaHqehkW1XlZy
+FtHEVf0XDmOEbruyBMjhRLkQc9apAIqqeVxPiLcEExEIAHcFAlIz099wGmh0dHA6
+Ly91bmRlcmdyaWQubmV0L2xlZ2FsL2dwZy9wb2xpY3kvMjAxMTEyMjQvNjdkYmUx
+M2I1YThkNDIwNDFmMGIwOWUyYjdkMjQ0Zjg0MmZjY2I4Y2EzYmZjNzBiNzkzZWVl
+M2U2NTI4NmRjMQAKCRDVc9WxKatM3fF0AJ9FSrSWq9OUE8LRcA+QxbtoZB+DFQCf
+V2NIf0wyR8HWLjwLXiG8beEbj2mItwQTEQgAdwUCUjPT6XAaaHR0cDovL3VuZGVy
+Z3JpZC5uZXQvbGVnYWwvZ3BnL3BvbGljeS8yMDExMTIyNC82N2RiZTEzYjVhOGQ0
+MjA0MWYwYjA5ZTJiN2QyNDRmODQyZmNjYjhjYTNiZmM3MGI3OTNlZWUzZTY1Mjg2
+ZGMxAAoJEFRMSGhi299iPBYAn3F7fsXWvWPXwQFM7/BBxU/PUvayAJ42XPHSSVWf
+ZEXDT/gkpYG0xhYJ6bkCDQRRSOEgARAAyWjtpoXnq/G8veD10U0ZSG3OMLsHxLsk
+okLKigPxWMHSgFwRrdT+t7OQ+T5f4ETerfPBeripsYqtlcajSVhXEciUJRqXMCXN
+l1tyawCOvE4Vs+cQN780XzfxfiwUD/NeC5YzEcUBVMfdqNoVJVYtte/niv24PZaV
+dRKRWICX1J9wZj3oA8WS0JdKnvYjzQSzldrJ6iIrY7Hyb/Y4a1hCpEelJ9LombG3
+5O1f8bi1RNv5fHHLzJLU2Ngwj9ghb9XGD4n0NJAEqOrLpUm4VZ4UyJOj6kvEsOa1
+GlKzzizWblSelFtXvwXwtVgOK3kbpJ3rCYN8omckqk7T23rEAYy+4AO2Yqq+xdYL
+1qgloAEFtUCUFunnVAx1j5gqqd7TcjBPDqNOgHkfgeVhZ3GmbB/uUsK2PGocGLd/
+YTlXf3EGaJpuwYts4mHOF2Vovhww1+LswX5fKKnJ5CzbfC52y+7HMpO4kLUQYkF9
+sTXElNaUSh+3qNwsuUevI4QeFsyt21AEjUj+gcLSrbI7xqsJWG1gyfnfVuOj1KDC
+YL6hkq+/XmZSXOHbI/7TeCyEsgl6JGtfV6bhZ3Lgd5nEctaou8byDDL+yL9/aie4
+jEtC3tWdHWWZxNqROyQEdCXtgEoRXC95MyrcWA0QADtv7KHBh7lekcLh7uF1gLEN
+gOUnLVB1r10AEQEAAYkERAQYAQoADwUCUUjhIAIbLgUJB4YfgAIpCRADjAn0YsJP
+x8FdIAQZAQoABgUCUUjhIAAKCRC9XExgS3u4A8pXEACd+Q6zyerRstasztj11Jyi
+NbAvaGJ/jIk6CEPttBQOrH4/OgeeaaabaVrQuzYtuYJJ9FwemHiRHKQ7xxg9L67k
+a5tHwXuxpA0tjilZABsF0/VuENMH6yf4Z2vPrtmJtuL+ZzeXANijWwuzNfOsUMQS
+KK20XeHxUXI5WpboZzRBB9ZjYVgEYg2Yy4RuIV903wy90TFrfkCiNyxEctTbr5PB
+uq/Sbvv2cfa2n5K6UUWjWAMAJfeePyrEPjDZusUNonIFyQ+tUtpQCzFDSeLKi9MR
+HOplTa77BjWSkdcT58EtOJLYYRkJEHjw/Tv8MDMOjMOHAeo4toaWNNOadYV8Ml0a
+O7rULVQuTDeJJqZ5YAtcCl/TxAFv4mIVBKs7919KQobPPHUa68F6MQ/N65l27ly0
+Vdc1V+O923NcP37Y59f960FmECKDHwrBwMtCKzLtmXDtP+F63s0Nh1S+Oxe47t7N
+g4W8qgILwzSqXyST0RzOafYpJWfvj3bhW7h0Sax7tCeDWltTagaxNKhi7Tzjqph7
+JAAauIeRzBSisPTdEJw1ww9mI7LlXz4HGp1rV+ynvraDyhC81tZ+xhIng9/Hj/7v
+C4oFOuZABohYXjg5sahFYuR2l+kYRz9hA0fKSFNUo07xqL+rDt0Nah0uhEhJG3MS
+8HL5UYRXD6gCqUiwimOF1jj5EACiIL1tjlgvftkcaCyLOFbPegUnbwrgcXNoGo0T
+D6D2ir5swV6GL2cLxPTGTwXjQ74zNna3vrnRb0p6cgjk0fVUuGvI2gBWewJ55mDO
+tTMZv5u06zN3VfnUVlKp+aiNpEhOwoMBhufOMCRk7U0xzV755BxJs2XjO9Fwv3fq
+7jnqNlK/sLPOd6KANJyHLNAtkpkUlF3KiMwHCdNZxzila8x7CBfzlfjJz9tMb1Ug
+7MSMZ3A4fOp9Uz7vWEie5gu8gPm4rr+ksAylWeNLd6K+ZlkK5Z+jQ3qRU9YBr3BJ
+dXePk5WqGuzav5Tmh2S1avEDfD1V4RzW8bdwDowsfsjqXz1dNeD8ahoZN32PwFiN
+7dgHKQtUs4NIbXCqeTaD14oNjmR/lCBjP/Vxdahmh7WuQcnKEx1/ZUa8a2JYWxFW
+eol1U/rrXuX/CiTs2U4vYDQzpcDJPZCVs6+731q2qWo4HBG9jkQKjuNyxTGhBoTn
+kiBEwGug84/fXHq/+kcdYhsyekU8tR45ILH3FVuRuV89JhPGYorC7ThJOu72dYSv
+YDvc+GHc7v/f2cqHGUc/ZBWmuD41lZq3hbZMzypCYTBQglnqGj3ttGM6ZhlIW7KR
+t/zwAbMPLlFLnrMb7FkvSvHWH3BNAcuhqr4lC0sUd30jtaVsLt0Mwvi9sv+wZ1Gg
+rSxUQQ==
+=B9ld
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/run_electrum b/run_electrum
new file mode 100755
index 000000000..a1fc32c31
--- /dev/null
+++ b/run_electrum
@@ -0,0 +1,481 @@
+#!/usr/bin/env python3
+# -*- mode: python -*-
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2011 thomasv@gitorious
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+import sys
+
+script_dir = os.path.dirname(os.path.realpath(__file__))
+is_bundle = getattr(sys, 'frozen', False)
+is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop"))
+is_android = 'ANDROID_DATA' in os.environ
+
+# move this back to gui/kivy/__init.py once plugins are moved
+os.environ['KIVY_DATA_DIR'] = os.path.abspath(os.path.dirname(__file__)) + '/electrum/gui/kivy/data/'
+
+if is_local or is_android:
+ sys.path.insert(0, os.path.join(script_dir, 'packages'))
+
+
+def check_imports():
+ # pure-python dependencies need to be imported here for pyinstaller
+ try:
+ import dns
+ import pyaes
+ import ecdsa
+ import requests
+ import qrcode
+ import google.protobuf
+ import jsonrpclib
+ except ImportError as e:
+ sys.exit("Error: %s. Try 'sudo pip install '"%str(e))
+ # the following imports are for pyinstaller
+ from google.protobuf import descriptor
+ from google.protobuf import message
+ from google.protobuf import reflection
+ from google.protobuf import descriptor_pb2
+ from jsonrpclib import SimpleJSONRPCServer
+ # make sure that certificates are here
+ assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)
+
+
+if not is_android:
+ check_imports()
+
+
+from electrum import bitcoin, util
+from electrum import constants
+from electrum import SimpleConfig, Network
+from electrum.wallet import Wallet, Imported_Wallet
+from electrum import bitcoin, util, constants
+from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption
+from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled
+from electrum.util import set_verbosity, InvalidPassword
+from electrum.commands import get_parser, known_commands, Commands, config_variables
+from electrum import daemon
+from electrum import keystore
+from electrum.mnemonic import Mnemonic
+
+# get password routine
+def prompt_password(prompt, confirm=True):
+ import getpass
+ password = getpass.getpass(prompt, stream=None)
+ if password and confirm:
+ password2 = getpass.getpass("Confirm: ")
+ if password != password2:
+ sys.exit("Error: Passwords do not match.")
+ if not password:
+ password = None
+ return password
+
+
+
+def run_non_RPC(config):
+ cmdname = config.get('cmd')
+
+ storage = WalletStorage(config.get_wallet_path())
+ if storage.file_exists():
+ sys.exit("Error: Remove the existing wallet first!")
+
+ def password_dialog():
+ return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
+
+ if cmdname == 'restore':
+ text = config.get('text').strip()
+ passphrase = config.get('passphrase', '')
+ password = password_dialog() if keystore.is_private(text) else None
+ if keystore.is_address_list(text):
+ wallet = Imported_Wallet(storage)
+ for x in text.split():
+ wallet.import_address(x)
+ elif keystore.is_private_key_list(text):
+ k = keystore.Imported_KeyStore({})
+ storage.put('keystore', k.dump())
+ storage.put('use_encryption', bool(password))
+ wallet = Imported_Wallet(storage)
+ for x in text.split():
+ wallet.import_private_key(x, password)
+ storage.write()
+ else:
+ if keystore.is_seed(text):
+ k = keystore.from_seed(text, passphrase, False)
+ elif keystore.is_master_key(text):
+ k = keystore.from_master_key(text)
+ else:
+ sys.exit("Error: Seed or key not recognized")
+ if password:
+ k.update_password(None, password)
+ storage.put('keystore', k.dump())
+ storage.put('wallet_type', 'standard')
+ storage.put('use_encryption', bool(password))
+ storage.write()
+ wallet = Wallet(storage)
+ if not config.get('offline'):
+ network = Network(config)
+ network.start()
+ wallet.start_threads(network)
+ print_msg("Recovering wallet...")
+ wallet.synchronize()
+ wallet.wait_until_synchronized()
+ wallet.stop_threads()
+ # note: we don't wait for SPV
+ msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
+ else:
+ msg = "This wallet was restored offline. It may contain more addresses than displayed."
+ print_msg(msg)
+
+ elif cmdname == 'create':
+ password = password_dialog()
+ passphrase = config.get('passphrase', '')
+ seed_type = 'segwit' if config.get('segwit') else 'standard'
+ seed = Mnemonic('en').make_seed(seed_type)
+ k = keystore.from_seed(seed, passphrase, False)
+ storage.put('keystore', k.dump())
+ storage.put('wallet_type', 'standard')
+ wallet = Wallet(storage)
+ wallet.update_password(None, password, True)
+ wallet.synchronize()
+ print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
+ print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
+
+ wallet.storage.write()
+ print_msg("Wallet saved in '%s'" % wallet.storage.path)
+ sys.exit(0)
+
+
+def init_daemon(config_options):
+ config = SimpleConfig(config_options)
+ storage = WalletStorage(config.get_wallet_path())
+ if not storage.file_exists():
+ print_msg("Error: Wallet file not found.")
+ print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
+ sys.exit(0)
+ if storage.is_encrypted():
+ if storage.is_encrypted_with_hw_device():
+ plugins = init_plugins(config, 'cmdline')
+ password = get_password_for_hw_device_encrypted_storage(plugins)
+ elif config.get('password'):
+ password = config.get('password')
+ else:
+ password = prompt_password('Password:', False)
+ if not password:
+ print_msg("Error: Password required")
+ sys.exit(1)
+ else:
+ password = None
+ config_options['password'] = password
+
+
+def init_cmdline(config_options, server):
+ config = SimpleConfig(config_options)
+ cmdname = config.get('cmd')
+ cmd = known_commands[cmdname]
+
+ if cmdname == 'signtransaction' and config.get('privkey'):
+ cmd.requires_wallet = False
+ cmd.requires_password = False
+
+ if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
+ cmd.requires_password = False
+
+ if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
+ cmd.requires_network = True
+
+ # instantiate wallet for command-line
+ storage = WalletStorage(config.get_wallet_path())
+
+ if cmd.requires_wallet and not storage.file_exists():
+ print_msg("Error: Wallet file not found.")
+ print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
+ sys.exit(0)
+
+ # important warning
+ if cmd.name in ['getprivatekeys']:
+ print_stderr("WARNING: ALL your private keys are secret.")
+ print_stderr("Exposing a single private key can compromise your entire wallet!")
+ print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
+
+ # commands needing password
+ if (cmd.requires_wallet and storage.is_encrypted() and server is None)\
+ or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):
+ if storage.is_encrypted_with_hw_device():
+ # this case is handled later in the control flow
+ password = None
+ elif config.get('password'):
+ password = config.get('password')
+ else:
+ password = prompt_password('Password:', False)
+ if not password:
+ print_msg("Error: Password required")
+ sys.exit(1)
+ else:
+ password = None
+
+ config_options['password'] = password
+
+ if cmd.name == 'password':
+ new_password = prompt_password('New password:')
+ config_options['new_password'] = new_password
+
+ return cmd, password
+
+
+def get_connected_hw_devices(plugins):
+ support = plugins.get_hardware_support()
+ if not support:
+ print_msg('No hardware wallet support found on your system.')
+ sys.exit(1)
+ # scan devices
+ devices = []
+ devmgr = plugins.device_manager
+ for name, description, plugin in support:
+ try:
+ u = devmgr.unpaired_device_infos(None, plugin)
+ except:
+ devmgr.print_error("error", name)
+ continue
+ devices += list(map(lambda x: (name, x), u))
+ return devices
+
+
+def get_password_for_hw_device_encrypted_storage(plugins):
+ devices = get_connected_hw_devices(plugins)
+ if len(devices) == 0:
+ print_msg("Error: No connected hw device found. Cannot decrypt this wallet.")
+ sys.exit(1)
+ elif len(devices) > 1:
+ print_msg("Warning: multiple hardware devices detected. "
+ "The first one will be used to decrypt the wallet.")
+ # FIXME we use the "first" device, in case of multiple ones
+ name, device_info = devices[0]
+ plugin = plugins.get_plugin(name)
+ derivation = get_derivation_used_for_hw_device_encryption()
+ try:
+ xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler)
+ except UserCancelled:
+ sys.exit(0)
+ password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
+ return password
+
+
+def run_offline_command(config, config_options, plugins):
+ cmdname = config.get('cmd')
+ cmd = known_commands[cmdname]
+ password = config_options.get('password')
+ if cmd.requires_wallet:
+ storage = WalletStorage(config.get_wallet_path())
+ if storage.is_encrypted():
+ if storage.is_encrypted_with_hw_device():
+ password = get_password_for_hw_device_encrypted_storage(plugins)
+ config_options['password'] = password
+ storage.decrypt(password)
+ wallet = Wallet(storage)
+ else:
+ wallet = None
+ # check password
+ if cmd.requires_password and wallet.has_password():
+ try:
+ seed = wallet.check_password(password)
+ except InvalidPassword:
+ print_msg("Error: This password does not decode this wallet.")
+ sys.exit(1)
+ if cmd.requires_network:
+ print_msg("Warning: running command offline")
+ # arguments passed to function
+ args = [config.get(x) for x in cmd.params]
+ # decode json arguments
+ if cmdname not in ('setconfig',):
+ args = list(map(json_decode, args))
+ # options
+ kwargs = {}
+ for x in cmd.options:
+ kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
+ cmd_runner = Commands(config, wallet, None)
+ func = getattr(cmd_runner, cmd.name)
+ result = func(*args, **kwargs)
+ # save wallet
+ if wallet:
+ wallet.storage.write()
+ return result
+
+def init_plugins(config, gui_name):
+ from electrum.plugin import Plugins
+ return Plugins(config, is_local or is_android, gui_name)
+
+
+if __name__ == '__main__':
+ # The hook will only be used in the Qt GUI right now
+ util.setup_thread_excepthook()
+ # on macOS, delete Process Serial Number arg generated for apps launched in Finder
+ sys.argv = list(filter(lambda x: not x.startswith('-psn'), sys.argv))
+
+ # old 'help' syntax
+ if len(sys.argv) > 1 and sys.argv[1] == 'help':
+ sys.argv.remove('help')
+ sys.argv.append('-h')
+
+ # old '-v' syntax
+ try:
+ i = sys.argv.index('-v')
+ except ValueError:
+ pass
+ else:
+ sys.argv[i] = '-v*'
+
+ # read arguments from stdin pipe and prompt
+ for i, arg in enumerate(sys.argv):
+ if arg == '-':
+ if not sys.stdin.isatty():
+ sys.argv[i] = sys.stdin.read()
+ break
+ else:
+ raise Exception('Cannot get argument from stdin')
+ elif arg == '?':
+ sys.argv[i] = input("Enter argument:")
+ elif arg == ':':
+ sys.argv[i] = prompt_password('Enter argument (will not echo):', False)
+
+ # parse command line
+ parser = get_parser()
+ args = parser.parse_args()
+
+ # config is an object passed to the various constructors (wallet, interface, gui)
+ if is_android:
+ config_options = {
+ 'verbosity': '',
+ 'cmd': 'gui',
+ 'gui': 'kivy',
+ }
+ else:
+ config_options = args.__dict__
+ f = lambda key: config_options[key] is not None and key not in config_variables.get(args.cmd, {}).keys()
+ config_options = {key: config_options[key] for key in filter(f, config_options.keys())}
+ if config_options.get('server'):
+ config_options['auto_connect'] = False
+
+ config_options['cwd'] = os.getcwd()
+
+ # fixme: this can probably be achieved with a runtime hook (pyinstaller)
+ if is_bundle and os.path.exists(os.path.join(sys._MEIPASS, 'is_portable')):
+ config_options['portable'] = True
+
+ if config_options.get('portable'):
+ config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum-btx_data')
+
+ # kivy sometimes freezes when we write to sys.stderr
+ set_verbosity(config_options.get('verbosity') if config_options.get('gui') != 'kivy' else '')
+
+ # check uri
+ uri = config_options.get('url')
+ if uri:
+ if not uri.startswith('bitcore:'):
+ print_stderr('unknown command:', uri)
+ sys.exit(1)
+ config_options['url'] = uri
+
+ # todo: defer this to gui
+ config = SimpleConfig(config_options)
+ cmdname = config.get('cmd')
+
+ if config.get('testnet'):
+ constants.set_testnet()
+ elif config.get('regtest'):
+ constants.set_regtest()
+ elif config.get('simnet'):
+ constants.set_simnet()
+
+ # run non-RPC commands separately
+ if cmdname in ['create', 'restore']:
+ run_non_RPC(config)
+ sys.exit(0)
+
+ if cmdname == 'gui':
+ fd, server = daemon.get_fd_or_server(config)
+ if fd is not None:
+ plugins = init_plugins(config, config.get('gui', 'qt'))
+ d = daemon.Daemon(config, fd, True)
+ d.start()
+ d.init_gui(config, plugins)
+ sys.exit(0)
+ else:
+ result = server.gui(config_options)
+
+ elif cmdname == 'daemon':
+ subcommand = config.get('subcommand')
+ if subcommand in ['load_wallet']:
+ init_daemon(config_options)
+
+ if subcommand in [None, 'start']:
+ fd, server = daemon.get_fd_or_server(config)
+ if fd is not None:
+ if subcommand == 'start':
+ pid = os.fork()
+ if pid:
+ print_stderr("starting daemon (PID %d)" % pid)
+ sys.exit(0)
+ init_plugins(config, 'cmdline')
+ d = daemon.Daemon(config, fd, False)
+ d.start()
+ if config.get('websocket_server'):
+ from electrum import websockets
+ websockets.WebSocketServer(config, d.network).start()
+ if config.get('requests_dir'):
+ path = os.path.join(config.get('requests_dir'), 'index.html')
+ if not os.path.exists(path):
+ print("Requests directory not configured.")
+ print("You can configure it using https://github.com/spesmilo/electrum-merchant")
+ sys.exit(1)
+ d.join()
+ sys.exit(0)
+ else:
+ result = server.daemon(config_options)
+ else:
+ server = daemon.get_server(config)
+ if server is not None:
+ result = server.daemon(config_options)
+ else:
+ print_msg("Daemon not running")
+ sys.exit(1)
+ else:
+ # command line
+ server = daemon.get_server(config)
+ init_cmdline(config_options, server)
+ if server is not None:
+ result = server.run_cmdline(config_options)
+ else:
+ cmd = known_commands[cmdname]
+ if cmd.requires_network:
+ print_msg("Daemon not running; try 'electrum daemon start'")
+ sys.exit(1)
+ else:
+ plugins = init_plugins(config, 'cmdline')
+ result = run_offline_command(config, config_options, plugins)
+ # print result
+ if isinstance(result, str):
+ print_msg(result)
+ elif type(result) is dict and result.get('error'):
+ print_stderr(result.get('error'))
+ elif result is not None:
+ print_msg(json_encode(result))
+ sys.exit(0)
diff --git a/setup.py b/setup.py
new file mode 100755
index 000000000..0616b9975
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# python setup.py sdist --format=zip,gztar
+
+import os
+import sys
+import platform
+import imp
+import argparse
+import subprocess
+
+from setuptools import setup, find_packages
+from setuptools.command.install import install
+
+with open('contrib/requirements/requirements.txt') as f:
+ requirements = f.read().splitlines()
+
+with open('contrib/requirements/requirements-hw.txt') as f:
+ requirements_hw = f.read().splitlines()
+
+version = imp.load_source('version', 'electrum/version.py')
+
+if sys.version_info[:3] < (3, 4, 0):
+ sys.exit("Error: Electrum requires Python version >= 3.4.0...")
+
+data_files = []
+
+if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']:
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--root=', dest='root_path', metavar='dir', default='/')
+ opts, _ = parser.parse_known_args(sys.argv[1:])
+ usr_share = os.path.join(sys.prefix, "share")
+ icons_dirname = 'pixmaps'
+ if not os.access(opts.root_path + usr_share, os.W_OK) and \
+ not os.access(opts.root_path, os.W_OK):
+ icons_dirname = 'icons'
+ if 'XDG_DATA_HOME' in os.environ.keys():
+ usr_share = os.environ['XDG_DATA_HOME']
+ else:
+ usr_share = os.path.expanduser('~/.local/share')
+ data_files += [
+ (os.path.join(usr_share, 'applications/'), ['electrum.desktop']),
+ (os.path.join(usr_share, icons_dirname), ['icons/electrumBTX.png'])
+ ]
+
+extras_require = {
+ 'hardware': requirements_hw,
+ 'fast': ['pycryptodomex'],
+ 'gui': ['pyqt5'],
+}
+extras_require['full'] = [pkg for sublist in list(extras_require.values()) for pkg in sublist]
+
+
+class CustomInstallCommand(install):
+ def run(self):
+ install.run(self)
+ # potentially build Qt icons file
+ try:
+ import PyQt5
+ except ImportError:
+ pass
+ else:
+ try:
+ path = os.path.join(self.install_lib, "electrum/gui/qt/icons_rc.py")
+ if not os.path.exists(path):
+ subprocess.call(["pyrcc5", "icons.qrc", "-o", path])
+ except Exception as e:
+ print('Warning: building icons file failed with {}'.format(e))
+
+
+setup(
+ name="Electrum",
+ version=version.ELECTRUM_VERSION,
+ install_requires=requirements,
+ extras_require=extras_require,
+ packages=[
+ 'electrum',
+ 'electrum.gui',
+ 'electrum.gui.qt',
+ 'electrum.plugins',
+ ] + [('electrum.plugins.'+pkg) for pkg in find_packages('electrum/plugins')],
+ package_dir={
+ 'electrum': 'electrum'
+ },
+ package_data={
+ '': ['*.txt', '*.json', '*.ttf', '*.otf'],
+ 'electrum': [
+ 'wordlist/*.txt',
+ 'locale/*/LC_MESSAGES/electrum.mo',
+ ],
+ },
+ scripts=['electrum/electrum'],
+ data_files=data_files,
+ description="Lightweight Bitcore Wallet",
+ author="Thomas Voegtlin",
+ author_email="thomasv@electrum.org",
+ license="MIT Licence",
+ url="https://bitcore.cc",
+ long_description="""Lightweight Bitcore Wallet""",
+ cmdclass={
+ 'install': CustomInstallCommand,
+ },
+)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
new file mode 100644
index 000000000..20f2b63de
--- /dev/null
+++ b/snap/snapcraft.yaml
@@ -0,0 +1,23 @@
+name: electrum
+version: master
+summary: Bitcoin thin client
+description: |
+ Lightweight Bitcoin client
+
+grade: devel # must be 'stable' to release into candidate/stable channels
+confinement: strict
+
+apps:
+ electrum:
+ command: desktop-launch electrum
+ plugs: [network, network-bind, x11, unity7]
+
+parts:
+ electrum:
+ source: .
+ plugin: python
+ python-version: python3
+ stage-packages: [python3-pyqt5]
+ build-packages: [pyqt5-dev-tools]
+ install: pyrcc5 icons.qrc -o $SNAPCRAFT_PART_INSTALL/lib/python3.5/site-packages/electrum/gui/qt/icons_rc.py
+ after: [desktop-qt5]
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 000000000..b00d29238
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,12 @@
+[tox]
+envlist = py35, py36
+
+[testenv]
+deps=
+ pytest
+ coverage
+commands=
+ coverage run --source=electrum '--omit=electrum/gui/*,electrum/plugins/*,electrum/scripts/*,electrum/tests/*' -m py.test -v
+ coverage report
+extras=
+ fast