diff --git a/.gitignore b/.gitignore index dae35e75f..804917e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ env/ .tox/ .buildozer/ bin/ +/app.fil # tox files .cache/ diff --git a/MANIFEST.in b/MANIFEST.in index 4fa5491a6..b028b14c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,12 +4,13 @@ include electrum.conf.sample include electrum.desktop include *.py include electrum +include contrib/requirements/requirements.txt +include contrib/requirements/requirements-hw.txt recursive-include lib *.py recursive-include gui *.py recursive-include plugins *.py recursive-include packages *.py recursive-include packages cacert.pem -include app.fil include icons.qrc recursive-include icons * recursive-include scripts * diff --git a/README.rst b/README.rst index 9b1832354..d7a11d7b3 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,9 @@ Electrum - Lightweight Bitcoin client .. 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://img.shields.io/badge/help-translating-blue.svg + :target: https://crowdin.com/project/electrum + :alt: Help translating Electrum online @@ -39,10 +41,12 @@ directory. To run Electrum from its root directory, just do:: You can also install Electrum on your system, by running this command:: sudo apt-get install python3-setuptools - python3 setup.py install + pip3 install .[full] This will download and install the Python dependencies used by Electrum, instead of using the 'packages' directory. +The 'full' 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 @@ -60,7 +64,7 @@ Check out the code from Github:: Run install (this should install dependencies):: - python3 setup.py install + pip3 install .[full] Compile the icons file for Qt:: diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 258efb5d2..db3337900 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,5 +1,25 @@ +# Release 3.1.2 - (March 28, 2018) -# Release 3.1 - (March 5, 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 diff --git a/app.fil b/app.fil deleted file mode 100644 index 835b6748f..000000000 --- a/app.fil +++ /dev/null @@ -1,29 +0,0 @@ -gui/qt/__init__.py -gui/qt/main_window.py -gui/qt/history_list.py -gui/qt/contact_list.py -gui/qt/invoice_list.py -gui/qt/request_list.py -gui/qt/installwizard.py -gui/qt/network_dialog.py -gui/qt/password_dialog.py -gui/qt/util.py -gui/qt/seed_dialog.py -gui/qt/transaction_dialog.py -gui/qt/address_dialog.py -gui/qt/qrcodewidget.py -gui/qt/qrtextedit.py -gui/qt/qrwindow.py -gui/kivy/main.kv -gui/kivy/main_window.py -gui/kivy/uix/dialogs/__init__.py -gui/kivy/uix/dialogs/fee_dialog.py -gui/kivy/uix/dialogs/installwizard.py -gui/kivy/uix/dialogs/settings.py -gui/kivy/uix/dialogs/wallets.py -gui/kivy/uix/ui_screens/history.kv -gui/kivy/uix/ui_screens/receive.kv -gui/kivy/uix/ui_screens/send.kv -plugins/labels/qt.py -plugins/trezor/qt.py -plugins/virtualkeyboard/qt.py diff --git a/contrib/build-osx/README.md b/contrib/build-osx/README.md index af43e05ce..c1e96d90b 100644 --- a/contrib/build-osx/README.md +++ b/contrib/build-osx/README.md @@ -2,16 +2,35 @@ Building Mac OS binaries ======================== This guide explains how to build Electrum binaries for macOS systems. -We build our binaries on El Capitan (10.11.6) as building it on High Sierra -makes the binaries incompatible with older versions. -This assumes that the Xcode command line tools (and thus git) are already installed. +The build process consists of two steps: +## 1. Building the binary -## 1. Run the script +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`). - ./make_osx + cd electrum + ./contrib/build-osx/make_osx + +This creates a folder named Electrum.app. -## 2. Done +## 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 index e5a656049..60b160afa 100755 --- a/contrib/build-osx/make_osx +++ b/contrib/build-osx/make_osx @@ -1,28 +1,18 @@ -#!/bin/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 -} - -build_dir=$(dirname "$0") -test -n "$build_dir" -a -d "$build_dir" || exit -cd $build_dir/../.. +#!/usr/bin/env bash -export PYTHONHASHSEED=22 -VERSION=`git describe --tags` - -# Paramterize +# Parameterize PYTHON_VERSION=3.6.4 BUILDDIR=/tmp/electrum-build PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum +. $(dirname "$0")/base.sh + +src_dir=$(dirname "$0") +cd $src_dir/../.. + +export PYTHONHASHSEED=22 +VERSION=`git describe --tags` info "Installing Python $PYTHON_VERSION" export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH" @@ -61,9 +51,9 @@ cp $BUILDDIR/electrum-icons/icons_rc.py ./gui/qt/ info "Downloading libusb..." -curl https://homebrew.bintray.com/bottles/libusb-1.0.21.el_capitan.bottle.tar.gz | \ +curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \ tar xz --directory $BUILDDIR -cp $BUILDDIR/libusb/1.0.21/lib/libusb-1.0.dylib contrib/build-osx +cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx info "Installing requirements..." python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ @@ -77,6 +67,13 @@ 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 --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary" diff --git a/contrib/build-osx/osx.spec b/contrib/build-osx/osx.spec index a2c02f39b..2e399ede0 100644 --- a/contrib/build-osx/osx.spec +++ b/contrib/build-osx/osx.spec @@ -93,7 +93,8 @@ app = BUNDLE(exe, name=PACKAGE + '.app', icon=electrum+ICONS_FILE, bundle_identifier=None, - info_plist = { - 'NSHighResolutionCapable':'True' + 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/build.sh b/contrib/build-wine/build.sh index 8bf650626..9870ec7e3 100755 --- a/contrib/build-wine/build.sh +++ b/contrib/build-wine/build.sh @@ -13,6 +13,10 @@ 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/prepare-wine.sh || exit 1 echo "Resetting modification time in C:\Python..." diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 33dfd60ba..caaec5088 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -10,6 +10,8 @@ for i, x in enumerate(sys.argv): else: raise BaseException('no name') +PYTHON_VERSION = '3.5.4' +PYHOME = 'c:/python' + PYTHON_VERSION home = 'C:\\electrum\\' @@ -21,7 +23,7 @@ hiddenimports += collect_submodules('keepkeylib') hiddenimports += collect_submodules('websocket') # Add libusb binary -binaries = [("c:/python3.5.4/libusb-1.0.dll", ".")] +binaries = [(PYHOME+"/libusb-1.0.dll", ".")] # Workaround for "Retro Look": binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]] diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh index c1bed9beb..7c3802818 100755 --- a/contrib/build-wine/prepare-wine.sh +++ b/contrib/build-wine/prepare-wine.sh @@ -1,17 +1,17 @@ #!/bin/bash # Please update these carefully, some versions won't work under Wine -NSIS_FILENAME=nsis-3.02.1-setup.exe +NSIS_FILENAME=nsis-3.03-setup.exe NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download -NSIS_SHA256=736c9062a02e297e335f82252e648a883171c98e0d5120439f538c81d429552e +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.21.7z -LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.21/$LIBUSB_FILENAME?download -LIBUSB_SHA256=acdde63a40b1477898aee6153f9d91d1a2e8a5d93f832ca8ab876498f3a6d2b8 +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.5.4 @@ -54,6 +54,27 @@ download_if_not_exist() { 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 @@ -65,8 +86,6 @@ echo "done" wine 'wineboot' -mkdir -p /tmp/electrum-build - cd /tmp/electrum-build # Install Python @@ -74,12 +93,12 @@ cd /tmp/electrum-build # 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" -KEYSERVER_PYTHON_DEV="hkp://keys.gnupg.net" -gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV +KEYSERVER_PYTHON_DEV="hkp://pool.sks-keyservers.net" +retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV for msifile in core dev exe lib pip tools; do echo "Installing $msifile..." - wget -nc "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi" - wget -nc "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc" + 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 @@ -117,11 +136,6 @@ verify_hash $LIBUSB_FILENAME "$LIBUSB_SHA256" cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/python$PYTHON_VERSION/ -# Install UPX -#wget -O upx.zip "https://downloads.sourceforge.net/project/upx/upx/3.08/upx308w.zip" -#unzip -o upx.zip -#cp upx*/upx.exe . - # add dlls needed for pyinstaller: cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/ diff --git a/contrib/deterministic-build/find_restricted_dependencies.py b/contrib/deterministic-build/find_restricted_dependencies.py new file mode 100755 index 000000000..846a0c75d --- /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 BaseException("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 index 381b4378f..2516789ab 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -1,5 +1,57 @@ -pycryptodomex==3.4.12 -PyQt5==5.10 -sip==4.19.7 -six==1.11.0 -websocket-client==0.46.0 +pip==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 +pycryptodomex==3.5.1 \ + --hash=sha256:16ab612ca9164e971dc00f8fe895ac835e8bfe64c3174b368f80172ff5a98300 \ + --hash=sha256:299a79efba6152ea438cc37f7349161e7bbd914f918342cad6316a4a5f29f2d7 \ + --hash=sha256:2a55e8fd69c84287b44e2c9c07eaad314e76680b86e873774314c27266728670 \ + --hash=sha256:2f71dc2b91288cf4a164287858eaccdc7053bf5765ebc47c5188f94eccc35e80 \ + --hash=sha256:2f81caf3ee08f00a957fd074c33430e8781958c616e864c5a1e709fb954750bc \ + --hash=sha256:3d4c77f1d4273ae753e49dac5c916f2278b0dd354a0c5f2a29fcf88bbae4efa9 \ + --hash=sha256:563512542dcab3e95d8cef70e45cc5a43ef35ff84bc040c388b305015343e51e \ + --hash=sha256:5d058decae88f86833a430afc0517df815d9efa4255b3a6d576c7fb305cb56d4 \ + --hash=sha256:5ec5903197d256b4559ff5c6a4756c34219ec81aff92be1174681623ba1e6383 \ + --hash=sha256:67f6573ff84ce7f7ea8ffc01ba5821c15dc85bf43291e4f8e11d7b6e2d5f504e \ + --hash=sha256:729da9aa2b8ea0bd8e35bc89ecd1ff4e482e6e9c2275e2e19de8b68dd8156fb5 \ + --hash=sha256:82df0a7cda5c94e9e4c62fb8d6507d5418f6593c8ed1b40b538a771ca003b597 \ + --hash=sha256:91b87c3abb24da1a980cb0f05e150eb0525235129bc5cb59277ea96860677f0a \ + --hash=sha256:a02b1b17d7c86b12bc1d4ede75846a7971e7df6d75508cd0696e383c18cad4ce \ + --hash=sha256:a36d5a5b73e51d66e3f1da53ce00e56de860a9c529f2811bb8d95374d9da06df \ + --hash=sha256:a7d836d6284c4734841c7c9d851be546650302ebca281de851129c22f1298ad5 \ + --hash=sha256:bb60d38111ebc383a5a1c909545562926c66c846d03fc65ba7b8a3487cb23078 \ + --hash=sha256:bf2e6cf6e78c8e6d63eeaa9641cad5008a382af98f2dc25cb7c6444f13133df9 \ + --hash=sha256:e303a4a1b242d3277e8dea07ab4e3737d0d1ed122990c713d6f88b0dda10c378 \ + --hash=sha256:e378bd7a09257a7a9a58f7f04b088991cf23a99847e9f42d6f996b4e52a11c33 \ + --hash=sha256:e75e7fe725dd5989e89da25a2fe7e3d35ed8123ac30eaae2f2340d0ba0431a88 \ + --hash=sha256:eac46844350302c93f3fd3eaa37353ee9e25cffcd1c574dfffb22de157ddce17 \ + --hash=sha256:f0ca00abf69827e78415050780cf838c7af9f378e591611210e25a03d6d0ea90 +PyQt5==5.10.1 \ + --hash=sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac \ + --hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \ + --hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \ + --hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61 +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 +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 +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +websocket-client==0.47.0 \ + --hash=sha256:188b68b14fdb2d8eb1a111f21b9ffd2dbf1dbc4e4c1d28cf2c37cdbf1dd1cae6 \ + --hash=sha256:a453dc4dfa6e0db3d8fd7738a308a88effe6240c59f3226eb93e8f020c216149 +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 8e0ba52f0..88cd12e75 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -1,18 +1,119 @@ -btchip-python==0.1.24 -certifi==2018.1.18 -chardet==3.0.4 -click==6.7 -Cython==0.27.3 -ecdsa==0.13 -hidapi==0.7.99.post21 -idna==2.6 -keepkey==4.0.2 -libusb1==1.6.4 -mnemonic==0.18 -pbkdf2==1.3 -protobuf==3.5.1 -pyblake2==1.1.0 -requests==2.18.4 -six==1.11.0 -trezor==0.9.0 -urllib3==1.22 +btchip-python==0.1.26 \ + --hash=sha256:427d67c5b4f4709605c51dd91d5d44a2ad8f541693673817765271e4b3a1461e +certifi==2018.1.18 \ + --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \ + --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +click==6.7 \ + --hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \ + --hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b +Cython==0.28.1 \ + --hash=sha256:0a44c3645a84724d4f7f7c1126f3807cad14271f210bb1a9a15bfd330c8d20b9 \ + --hash=sha256:152ee5f345012ca3bb7cc71da2d3736ee20f52cd8476e4d49e5e25c5a4102b12 \ + --hash=sha256:15cbde95cdf6a346c63c0b38b895aa3186d0a49adcaf42b9e19c4cb0473cb921 \ + --hash=sha256:175273eb6a90af364b9b2aea74e3c3eebcde9caec02d617cd8886c0933ec3304 \ + --hash=sha256:1a434e7621ca6671ce949893fef94b13a580853ba976e729f68dda5ab270ee8a \ + --hash=sha256:1da199a5be7c486ee89b4a8bda7f00f9fd98b800d9af3a1fe3fc63e68dadea85 \ + --hash=sha256:26196ff8fe00e7c4c5815a310c368edfc1a1c8a3c90ac9220435d1b01e795cf7 \ + --hash=sha256:2b73b062658511167dde37a51acb80ae6ddea1ffa284ebdbc47a900f21e63c70 \ + --hash=sha256:2b9aa64473fefbe988e36a30915732a0e2a75ffe0f3e81a70e3f8d367c2e4119 \ + --hash=sha256:32638aefc164404ac70e5f86558cd3aece34df17db16da905abf5e664073bae4 \ + --hash=sha256:330c95c03e39835976d1410f5fa877b75fcc5c50dc146d4c02377fc719b1f8c9 \ + --hash=sha256:38b499fa317cf6939e46317457240553b13e974f08ed387523f10af26e269f6c \ + --hash=sha256:3c60caa0075aa1f1c5e10b5352d2e8bb9a18361ce9fca50fa7d4baea67804ade \ + --hash=sha256:3fc9b945541cadb5a10316e48b5e73f4b6f635b7d30156f502b66f3766545b23 \ + --hash=sha256:427299c47cfe04d97f9f09ea55570c05898a87b64caf6ddebb529b6f64e5d228 \ + --hash=sha256:4e07e619af85e7c1ec2344ecab558929bb4acbca25f8f170d07dc677e8ee413f \ + --hash=sha256:5010e048fb9791522fe626bd40137b8a847ba77a6e656bb64d6d7acdc45ece32 \ + --hash=sha256:70bc2806fc9e5affcf74c0d4fa12cba57ffb94cdfc28b975692c8df56ea86402 \ + --hash=sha256:7cdd5303121024269610236876d9f4beb6a909a1ea5d7bc48e7bbf5d8774a3f2 \ + --hash=sha256:80856aa45004514a3ff5e102bd18fbd5230d234311de1f83d4e5b97ef42f6c93 \ + --hash=sha256:996ae959ab2600b8ad4c80981afc32e89b42d0abe3234c48e960e40180459cb2 \ + --hash=sha256:b5c2e31be55bc61d3c758889d06b16d84f4fda944832fbed63c113ec2dbc5f97 \ + --hash=sha256:c39b3a042bf5ded7c8336c82b1fa817e1f46a7ef16d41d66b3d3340e7a3b60ed \ + --hash=sha256:d08f5dd2fbf7d1506c9d986a8352b2423170002ddb635b184d2a916d2b5df0d6 \ + --hash=sha256:d2b636c16931663aeb8ffb91cf871a912e66e7200755ce68aa8c502c16eb366f \ + --hash=sha256:e27e12834ac315c7d67ca697a325d42ff8395e9b82fb62c8665bb583eb0df589 \ + --hash=sha256:e312dd688b97e9f97199a8e4ba18b65db2747157630761d27193a18761b23fed \ + --hash=sha256:e47bbe74d6c87fab2e22f1580aa3e4a8e7482b4265b1fc76685fc49f90e18f99 \ + --hash=sha256:ea113ff58e96152738e646b8ee77b41d3994735df77bee0a26cd413a67e03c0f \ + --hash=sha256:ea22d79133583b5b0f856dbfda097363228ae0a3d77431deaba90634e5d4853a +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.6 \ + --hash=sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f \ + --hash=sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4 +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==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 +protobuf==3.5.2.post1 \ + --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ + --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ + --hash=sha256:242e4c7ae565267a8bc8b92d707177f915607ea4bd73244bec6cbf4a49b96661 \ + --hash=sha256:3b60685732bd0cbdc802dfcb6071efbcf5d927ce3127c13c33ea1a8efae3aa76 \ + --hash=sha256:3f655e1f99c3e14d56ca900af1b9a4715b691319a295cc38939d7f77eabd5e7c \ + --hash=sha256:560a38e692a69957a70ba0e5839aa67430efd63072bf91b0539dac19055694cd \ + --hash=sha256:5c1c8f6a0a68a874e3beff89255959dd80fad45870e96c88944a1b81a22dd5f5 \ + --hash=sha256:628a3bf0794a8b3cabb18db11eb67cc10e0cc6e5525d557ae7b682bb73fa2018 \ + --hash=sha256:7222d6616108b33ad6cbeff8117062a73c43cdc8fa8f64f6a322ebeb663e710e \ + --hash=sha256:76ef6ca3c50e4cfd044861586d5f1b352e0fe7f17f883df6c165bad5b4d0e10a \ + --hash=sha256:7c193e6964e752bd056735594826c5b03274ceb8f07349d3ae47d9766250ba96 \ + --hash=sha256:869e12bcfb5759e683f53ec1dd6155b7be034065431da289f0cb4510040a0799 \ + --hash=sha256:905414e5ea6cdb78d8730f66335755152b46685fcb9fc2f2134024e3ea9e8dcc \ + --hash=sha256:ac0067e3c60737865ed72bb7416e02297d229d960902802d874c0e167128c809 \ + --hash=sha256:adf716a89c9cc1891ead79a861c427071ef59172f0e11967b00565a9547b3bd0 \ + --hash=sha256:bcfa99f5a82f5eaaf6e5cee5bfdca5a1670f5740aec1d93dae170645ed1a16b0 \ + --hash=sha256:cc94079ae6cbcea5ae194464a30f3223f075e06a0446f52bca9ddbeb6e9f412a \ + --hash=sha256:d5d9edfdc5a3a01d06062d677b121081629782edf0e05ca1be14f15bb947eeee \ + --hash=sha256:e269ab7a50bf0fa6fe6a88ea7dcc7a1079ae9450d9ab9b7730ac32916d55508b \ + --hash=sha256:e7fd33a3474cbe18fd5b5620784a0fa21fcae3e402b1806e29c6b450c7f61706 +pyblake2==1.1.1 \ + --hash=sha256:11c1d9d94cbaf5a4834aadf7f57bcb29eae1d174721269f242ca891f62cd6502 \ + --hash=sha256:427e7e91d644c3b9952e84145e211e4e3197fc4a3a0dbbd87b6da6b6cfa0a0df \ + --hash=sha256:4903d64e1a24f0cf2f8b8a1e0aaab12898951112b370ab9600651a4be4387c99 \ + --hash=sha256:6886b050521aed0293b2f67a3e1da74ea6080e4be19b57d9e1ae3d6ff10e223a \ + --hash=sha256:8cc4198ce61dddd33c9e66a216fc70be04fab66d02baa79e6bdebd83f16af57e \ + --hash=sha256:8ec8e9087d13c99b354ab6d8b4cadb1758633db5946ff95a6bc7ac538b6d7b3d \ + --hash=sha256:a785faf939810dca4aef525b6f59890fdcabdef09228cb30f4d77c3021707846 \ + --hash=sha256:e51b86e685045e2f8896d581b230effb1cc69f1134e11318f3607d98fa5ba95c \ + --hash=sha256:f51051de4eb27dc63c525a562daf9ead14e3e3583f096b9b90d3a360b5ca4995 +requests==2.18.4 \ + --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b \ + --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e +rlp==0.6.0 \ + --hash=sha256:87879a0ba1479b760cee98af165de2eee95258b261faa293199f60742be96f34 +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +trezor==0.9.1 \ + --hash=sha256:a481191011bade98f1e9f1201e7c72a83945050657bbc90dc4ac32dc8b8b46a4 +urllib3==1.22 \ + --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ + --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index e7d8925b8..8ec2501cf 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -1,14 +1,69 @@ -certifi==2018.1.18 -chardet==3.0.4 -dnspython==1.15.0 -ecdsa==0.13 -idna==2.6 -jsonrpclib-pelix==0.3.1 -pbkdf2==1.3 -protobuf==3.5.1 -pyaes==1.6.1 -PySocks==1.6.8 -qrcode==5.3 -requests==2.18.4 -six==1.11.0 -urllib3==1.22 +certifi==2018.1.18 \ + --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \ + --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d +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.6 \ + --hash=sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f \ + --hash=sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4 +jsonrpclib-pelix==0.3.1 \ + --hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \ + --hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4 +pbkdf2==1.3 \ + --hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979 +pip==9.0.3 \ + --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \ + --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5 +protobuf==3.5.2.post1 \ + --hash=sha256:01ccd6d03449ae75b779fb5bf4ed62177d61afe3c5e6465ccf3f8b2e1a84afbe \ + --hash=sha256:1d92cc30b0b46cced33adde5853d920179eb5ea8eecdee9552502a7f29cc3f21 \ + --hash=sha256:242e4c7ae565267a8bc8b92d707177f915607ea4bd73244bec6cbf4a49b96661 \ + --hash=sha256:3b60685732bd0cbdc802dfcb6071efbcf5d927ce3127c13c33ea1a8efae3aa76 \ + --hash=sha256:3f655e1f99c3e14d56ca900af1b9a4715b691319a295cc38939d7f77eabd5e7c \ + --hash=sha256:560a38e692a69957a70ba0e5839aa67430efd63072bf91b0539dac19055694cd \ + --hash=sha256:5c1c8f6a0a68a874e3beff89255959dd80fad45870e96c88944a1b81a22dd5f5 \ + --hash=sha256:628a3bf0794a8b3cabb18db11eb67cc10e0cc6e5525d557ae7b682bb73fa2018 \ + --hash=sha256:7222d6616108b33ad6cbeff8117062a73c43cdc8fa8f64f6a322ebeb663e710e \ + --hash=sha256:76ef6ca3c50e4cfd044861586d5f1b352e0fe7f17f883df6c165bad5b4d0e10a \ + --hash=sha256:7c193e6964e752bd056735594826c5b03274ceb8f07349d3ae47d9766250ba96 \ + --hash=sha256:869e12bcfb5759e683f53ec1dd6155b7be034065431da289f0cb4510040a0799 \ + --hash=sha256:905414e5ea6cdb78d8730f66335755152b46685fcb9fc2f2134024e3ea9e8dcc \ + --hash=sha256:ac0067e3c60737865ed72bb7416e02297d229d960902802d874c0e167128c809 \ + --hash=sha256:adf716a89c9cc1891ead79a861c427071ef59172f0e11967b00565a9547b3bd0 \ + --hash=sha256:bcfa99f5a82f5eaaf6e5cee5bfdca5a1670f5740aec1d93dae170645ed1a16b0 \ + --hash=sha256:cc94079ae6cbcea5ae194464a30f3223f075e06a0446f52bca9ddbeb6e9f412a \ + --hash=sha256:d5d9edfdc5a3a01d06062d677b121081629782edf0e05ca1be14f15bb947eeee \ + --hash=sha256:e269ab7a50bf0fa6fe6a88ea7dcc7a1079ae9450d9ab9b7730ac32916d55508b \ + --hash=sha256:e7fd33a3474cbe18fd5b5620784a0fa21fcae3e402b1806e29c6b450c7f61706 +pyaes==1.6.1 \ + --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f +PySocks==1.6.8 \ + --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672 +qrcode==6.0 \ + --hash=sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf \ + --hash=sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3 +requests==2.18.4 \ + --hash=sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b \ + --hash=sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e +setuptools==39.0.1 \ + --hash=sha256:8010754433e3211b9cdbbf784b50f30e80bf40fc6b05eb5f865fab83300599b8 \ + --hash=sha256:bec7badf0f60e7fc8153fac47836edc41b74e5d541d7692e614e635720d6a7c7 +six==1.11.0 \ + --hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \ + --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb +urllib3==1.22 \ + --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ + --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +wheel==0.31.0 \ + --hash=sha256:1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce \ + --hash=sha256:9cdc8ab2cc9c3c2e2727a4b67c22881dbb0e1c503d592992594c5e131c867107 +colorama==0.3.9 \ + --hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \ + --hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1 diff --git a/contrib/freeze_packages.sh b/contrib/freeze_packages.sh index 3471e528b..19d6b5fcc 100755 --- a/contrib/freeze_packages.sh +++ b/contrib/freeze_packages.sh @@ -5,18 +5,35 @@ 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 "$venv_dir" -rf + rm -rf "$venv_dir" virtualenv -p $(which python3) $venv_dir source $venv_dir/bin/activate - echo "Installing $i dependencies" + echo "Installing $m dependencies" python -m pip install -r $contrib/requirements/requirements${i}.txt --upgrade - pip freeze | sed '/^Electrum/ d' > $contrib/deterministic-build/requirements${i}.txt + 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_locale b/contrib/make_locale index 9243a8b4f..3ab4b21cf 100755 --- a/contrib/make_locale +++ b/contrib/make_locale @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +import subprocess import io import zipfile import requests @@ -7,21 +8,31 @@ import requests os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir('..') +code_directories = 'gui plugins lib' +cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories) + +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('lib/locale'): os.mkdir('lib/locale') -cmd = 'xgettext -s --no-wrap -f app.fil --output=lib/locale/messages.pot' +cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=lib/locale/messages.pot' print('Generate template') os.system(cmd) os.chdir('lib') crowdin_identifier = 'electrum' -crowdin_file_name = 'electrum-client/messages.pot' +crowdin_file_name = 'files[electrum-client/messages.pot]' locale_file_name = 'locale/messages.pot' crowdin_api_key = None -filename = '~/.crowdin_api_key' +filename = os.path.expanduser('~/.crowdin_api_key') if os.path.exists(filename): with open(filename) as f: crowdin_api_key = f.read().strip() @@ -33,13 +44,14 @@ 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: + with open(locale_file_name, 'rb') as f: files = {crowdin_file_name: f} - requests.request('POST', url, files=files) + 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).content - print(response) + 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') diff --git a/electrum b/electrum index 78924631e..4c4bf27cc 100755 --- a/electrum +++ b/electrum @@ -26,21 +26,6 @@ import os import sys -# from https://gist.github.com/tito/09c42fb4767721dc323d -import threading -try: - import jnius -except: - jnius = None -if jnius: - orig_thread_run = threading.Thread.run - def thread_check_run(*args, **kwargs): - try: - return orig_thread_run(*args, **kwargs) - finally: - jnius.detach() - threading.Thread.run = thread_check_run - 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")) @@ -93,7 +78,7 @@ from electrum import constants from electrum import SimpleConfig, Network from electrum.wallet import Wallet, Imported_Wallet from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption -from electrum.util import print_msg, print_stderr, json_encode, json_decode +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 @@ -295,7 +280,10 @@ def get_password_for_hw_device_encrypted_storage(plugins): name, device_info = devices[0] plugin = plugins.get_plugin(name) derivation = get_derivation_used_for_hw_device_encryption() - xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + 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 diff --git a/electrum-env b/electrum-env index c05b2d1ab..1c2e6a4e3 100755 --- a/electrum-env +++ b/electrum-env @@ -11,6 +11,7 @@ PYTHON_VER="$(python3 -c 'import sys; print(sys.version[:3])')" +cd $(dirname $0) if [ -e ./env/bin/activate ]; then source ./env/bin/activate else diff --git a/electrum.desktop b/electrum.desktop index 92540ea41..5e57c8dda 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -3,7 +3,7 @@ [Desktop Entry] Comment=Lightweight Bitcoin Client -Exec=electrum %u +Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum %u" GenericName[en_US]=Bitcoin Wallet GenericName=Bitcoin Wallet Icon=electrum @@ -14,4 +14,8 @@ StartupNotify=false Terminal=false Type=Application MimeType=x-scheme-handler/bitcoin; +Actions=Testnet; +[Desktop Action Testnet] +Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum --testnet %u" +Name=Testnet mode diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv index 95cfe8def..6db14c8f0 100644 --- a/gui/kivy/main.kv +++ b/gui/kivy/main.kv @@ -285,7 +285,8 @@ : size_hint: 1, None - height: '48dp' + height: '60dp' + font_size: '30dp' on_release: self.parent.update_amount(self.text) @@ -372,9 +373,6 @@ tab_height: '48dp' tab_width: panel.width/3 strip_border: 0, 0, 0, 0 - InvoicesScreen: - id: invoices_screen - tab: invoices_tab SendScreen: id: send_screen tab: send_tab @@ -384,29 +382,18 @@ ReceiveScreen: id: receive_screen tab: receive_tab - AddressScreen: - id: address_screen - tab: address_tab - CleanHeader: - id: invoices_tab - text: _('Invoices') - slide: 0 CleanHeader: id: send_tab text: _('Send') - slide: 1 + slide: 0 CleanHeader: id: history_tab text: _('Balance') - slide: 2 + slide: 1 CleanHeader: id: receive_tab text: _('Receive') - slide: 3 - CleanHeader: - id: address_tab - text: _('Addresses') - slide: 4 + slide: 2 diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index 7ca40bf8d..1d702f425 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -244,6 +244,7 @@ class ElectrumWindow(App): self.tabs = None self.is_exit = False self.wallet = None + self.pause_time = 0 App.__init__(self)#, **kwargs) @@ -445,7 +446,6 @@ class ElectrumWindow(App): #win.softinput_mode = 'below_target' self.on_size(win, win.size) self.init_ui() - self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # init plugins run_hook('init_kivy', self) # fiat currency @@ -467,6 +467,8 @@ class ElectrumWindow(App): self.network.register_callback(self.on_fee, ['fee']) 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: @@ -484,17 +486,18 @@ class ElectrumWindow(App): wallet.start_threads(self.daemon.network) self.daemon.add_wallet(wallet) self.load_wallet(wallet) - self.on_resume() def load_wallet_by_name(self, path): 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 != self.wallet: - self.stop_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) - self.on_resume() else: Logger.debug('Electrum: Wallet not found. Launching install wizard') storage = WalletStorage(path) @@ -504,6 +507,7 @@ class ElectrumWindow(App): wizard.run(action) def on_stop(self): + Logger.info('on_stop') self.stop_wallet() def stop_wallet(self): @@ -617,6 +621,8 @@ class ElectrumWindow(App): @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 @@ -684,12 +690,16 @@ class ElectrumWindow(App): 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.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() @@ -824,7 +834,6 @@ class ElectrumWindow(App): d = LabelDialog(_('Enter description'), text, callback) d.open() - @profiler def amount_dialog(self, screen, show_max): from .uix.dialogs.amount_dialog import AmountDialog amount = screen.amount @@ -836,6 +845,34 @@ class ElectrumWindow(App): 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(): @@ -848,7 +885,8 @@ class ElectrumWindow(App): def protected(self, msg, f, args): if self.wallet.has_password(): - self.password_dialog(msg, f, args) + on_success = lambda pw: f(*(args + (pw,))) + self.password_dialog(self.wallet, msg, on_success, lambda: None) else: f(*(args + (None,))) @@ -860,7 +898,7 @@ class ElectrumWindow(App): def _delete_wallet(self, b): if b: - basename = os.path.basename(self.wallet.storage.path) + basename = self.wallet.basename() self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ()) def __delete_wallet(self, pw): @@ -898,40 +936,23 @@ class ElectrumWindow(App): if passphrase: label.text += '\n\n' + _('Passphrase') + ': ' + passphrase - def change_password(self, cb): - if self.wallet.has_password(): - self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,)) - else: - self._change_password(cb, None) - - def _change_password(self, cb, old_password): - if self.wallet.has_password(): - if old_password is None: - return - try: - self.wallet.check_password(old_password) - except InvalidPassword: - self.show_error("Invalid PIN") - return - self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,)) - - def _change_password2(self, cb, old_password, new_password): - self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password)) - - def _change_password3(self, cb, old_password, new_password, confirmed_password): - if new_password == confirmed_password: - self.wallet.update_password(old_password, new_password) - cb() - else: - self.show_error("PIN numbers do not match") + 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 password_dialog(self, msg, f, args): + def change_password(self, cb): from .uix.dialogs.password_dialog import PasswordDialog - def callback(pw): - Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1) if self._password_dialog is None: self._password_dialog = PasswordDialog() - self._password_dialog.init(msg, callback) + 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): diff --git a/gui/kivy/theming/light/share.png b/gui/kivy/theming/light/share.png new file mode 100644 index 000000000..d0dc761d4 Binary files /dev/null and b/gui/kivy/theming/light/share.png differ diff --git a/gui/kivy/uix/context_menu.py b/gui/kivy/uix/context_menu.py index dee0212af..884a2098e 100644 --- a/gui/kivy/uix/context_menu.py +++ b/gui/kivy/uix/context_menu.py @@ -47,7 +47,6 @@ class ContextMenu(Bubble): l = MenuItem() l.text = _(k) def func(f=v): - Clock.schedule_once(lambda dt: self.hide(), 0.1) Clock.schedule_once(lambda dt: f(obj), 0.15) l.on_release = func self.ids.buttons.add_widget(l) diff --git a/gui/kivy/uix/dialogs/addresses.py b/gui/kivy/uix/dialogs/addresses.py new file mode 100644 index 000000000..8f4b3fa62 --- /dev/null +++ b/gui/kivy/uix/dialogs/addresses.py @@ -0,0 +1,216 @@ +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)) + ScrollView: + GridLayout: + cols: 1 + id: search_container + size_hint_y: None + height: self.minimum_height +''') + + +from electrum_gui.kivy.i18n import _ +from electrum_gui.kivy.uix.context_menu import ContextMenu + + +class EmptyLabel(Factory.Label): + pass + + +class AddressesDialog(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, addr, balance, is_used, label): + ci = self.cards.get(addr) + if ci is None: + ci = Factory.AddressItem() + ci.screen = self + ci.address = addr + self.cards[addr] = ci + + ci.memo = label + ci.amount = self.app.format_amount_and_units(balance) + request = self.app.wallet.get_payment_request(addr, self.app.electrum_config) + if is_used: + ci.status = _('Used') + else: + ci.status = _('Funded') if balance > 0 else _('Unused') + return ci + + + def update(self): + self.menu_actions = [(_('Use'), self.do_show), (_('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 + container.clear_widgets() + n = 0 + for address in _list: + label = wallet.labels.get(address, '') + balance = sum(wallet.get_addr_balance(address)) + is_used = wallet.is_used(address) + if self.show_used == 1 and (balance or is_used): + continue + if self.show_used == 2 and balance == 0: + continue + if self.show_used == 3 and not is_used: + continue + card = self.get_card(address, balance, is_used, label) + if search and not self.ext_search(card, search): + continue + container.add_widget(card) + n += 1 + if not n: + msg = _('No address matching your search') + container.add_widget(EmptyLabel(text=msg)) + + def do_show(self, obj): + self.hide_menu() + self.dismiss() + self.app.show_request(obj.address) + + def do_view(self, obj): + req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config) + if req: + c, u, x = self.app.wallet.get_addr_balance(obj.address) + balance = c + u + x + if balance > 0: + req['fund'] = balance + status = req.get('status') + amount = req.get('amount') + address = req['address'] + if amount: + status = req.get('status') + status = request_text[status] + else: + received_amount = self.app.wallet.get_addr_received(address) + status = self.app.format_amount_and_units(received_amount) + self.app.show_pr_details(req, status, False) + + else: + 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 do_delete(self, obj): + from .dialogs.question import Question + def cb(result): + if result: + self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config) + self.update() + d = Question(_('Delete request?'), cb) + d.open() + + 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/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py index da76ef75b..849263e6f 100644 --- a/gui/kivy/uix/dialogs/installwizard.py +++ b/gui/kivy/uix/dialogs/installwizard.py @@ -802,28 +802,18 @@ class InstallWizard(BaseWizard, Widget): app = App.get_running_app() Clock.schedule_once(lambda dt: app.show_error(msg)) - def password_dialog(self, message, callback): + 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() - popup.init(message, callback) + app = App.get_running_app() + popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2) popup.open() - def request_password(self, run_next, force_disable_encrypt_cb=False): - def callback(pin): - if pin: - self.run('confirm_password', pin, run_next) - else: - run_next(None, None) - self.password_dialog('Choose a PIN code', callback) - - def confirm_password(self, pin, run_next): - def callback(conf): - if conf == pin: - run_next(pin, False) - else: - self.show_error(_('PIN mismatch')) - self.run('request_password', run_next) - self.password_dialog('Confirm your PIN code', callback) - def action_dialog(self, action, run_next): f = getattr(self, action) f() diff --git a/gui/kivy/uix/dialogs/invoices.py b/gui/kivy/uix/dialogs/invoices.py new file mode 100644 index 000000000..d8e4f884d --- /dev/null +++ b/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://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://gui/kivy/theming/light/important', + PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', + PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', + PR_EXPIRED: 'atlas://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/gui/kivy/uix/dialogs/password_dialog.py b/gui/kivy/uix/dialogs/password_dialog.py index 274b8a185..670179928 100644 --- a/gui/kivy/uix/dialogs/password_dialog.py +++ b/gui/kivy/uix/dialogs/password_dialog.py @@ -5,35 +5,42 @@ 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: _('PIN Code') + title: 'Electrum' message: '' - size_hint: 0.9, 0.9 BoxLayout: + size_hint: 1, 1 orientation: 'vertical' Widget: - size_hint: 1, 1 + 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, 1 + size_hint: 1, 0.05 Label: id: a - text: ' * '*len(kb.password) + ' o '*(6-len(kb.password)) + font_size: '50dp' + text: '*'*len(kb.password) + '-'*(6-len(kb.password)) + size: self.texture_size Widget: - size_hint: 1, 1 + 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) - size_hint: 1, None - height: '200dp' + spacing: '2dp' cols: 3 KButton: text: '1' @@ -59,30 +66,44 @@ Builder.load_string(''' text: '0' KButton: text: '<' - BoxLayout: - size_hint: 1, None - height: '48dp' - Widget: - size_hint: 0.5, None - Button: - size_hint: 0.5, None - height: '48dp' - text: _('Cancel') - on_release: - popup.dismiss() - popup.callback(None) ''') class PasswordDialog(Factory.Popup): - #def __init__(self, message, callback): - # Factory.Popup.__init__(self) - - def init(self, message, callback): + def init(self, app, wallet, message, on_success, on_failure, is_change=0): + self.app = app + self.wallet = wallet self.message = message - self.callback = callback + 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 @@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup): def on_password(self, pw): if len(pw) == 6: - self.dismiss() - Clock.schedule_once(lambda dt: self.callback(pw), 0.1) + 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/gui/kivy/uix/dialogs/requests.py b/gui/kivy/uix/dialogs/requests.py new file mode 100644 index 000000000..266a40436 --- /dev/null +++ b/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://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://gui/kivy/theming/light/important', + PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', + PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', + PR_EXPIRED: 'atlas://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/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py index a3b3a5788..3a93a4f09 100644 --- a/gui/kivy/uix/dialogs/settings.py +++ b/gui/kivy/uix/dialogs/settings.py @@ -36,9 +36,8 @@ Builder.load_string(''' action: partial(root.language_dialog, self) CardSeparator SettingsItem: - status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF') disabled: root.disable_pin - title: _('PIN code') + ': ' + self.status + title: _('PIN code') description: _("Change your PIN code.") action: partial(root.change_password, self) CardSeparator diff --git a/gui/kivy/uix/menus.py b/gui/kivy/uix/menus.py index 6b6efa9a7..17490054e 100644 --- a/gui/kivy/uix/menus.py +++ b/gui/kivy/uix/menus.py @@ -7,7 +7,7 @@ from kivy.uix.bubble import Bubble, BubbleButton from kivy.properties import ListProperty from kivy.uix.widget import Widget -from electrum_gui.i18n import _ +from electrum_gui.kivy.i18n import _ class ContextMenuItem(Widget): '''abstract class diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py index 5664d439d..6b040dc15 100644 --- a/gui/kivy/uix/screens.py +++ b/gui/kivy/uix/screens.py @@ -27,8 +27,6 @@ from .context_menu import ContextMenu from electrum_gui.kivy.i18n import _ -class EmptyLabel(Factory.Label): - pass class CScreen(Factory.Screen): __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave') @@ -219,7 +217,6 @@ class SendScreen(CScreen): pr = make_unsigned_request(req).SerializeToString() pr = PaymentRequest(pr) self.app.wallet.invoices.add(pr) - self.app.update_tab('invoices') self.app.show_info(_("Invoice saved")) if pr.is_pr(): self.screen.is_pr = True @@ -374,221 +371,32 @@ class ReceiveScreen(CScreen): def save_request(self): addr = self.screen.address if not addr: - return + 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) - self.app.wallet.add_payment_request(req, self.app.electrum_config) - self.app.update_tab('requests') + 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): - self.save_request() 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.')) - else: - self.save_request() - self.app.show_info(_('New request added to your list.')) - - -invoice_text = { - PR_UNPAID:_('Pending'), - PR_UNKNOWN:_('Unknown'), - PR_PAID:_('Paid'), - PR_EXPIRED:_('Expired') -} -request_text = { - PR_UNPAID: _('Pending'), - PR_UNKNOWN: _('Unknown'), - PR_PAID: _('Received'), - PR_EXPIRED: _('Expired') -} -pr_icon = { - PR_UNPAID: 'atlas://gui/kivy/theming/light/important', - PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important', - PR_PAID: 'atlas://gui/kivy/theming/light/confirmed', - PR_EXPIRED: 'atlas://gui/kivy/theming/light/close' -} - - -class InvoicesScreen(CScreen): - kvname = 'invoices' - cards = {} - - 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.screen.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) - if not _list: - msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.') - invoices_list.add_widget(EmptyLabel(text=msg)) - - def do_pay(self, obj): - 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 .dialogs.question import Question - def cb(result): - if result: - self.app.wallet.invoices.remove(obj.key) - self.app.update_tab('invoices') - d = Question(_('Delete invoice?'), cb) - d.open() - - -address_icon = { - 'Pending' : 'atlas://gui/kivy/theming/light/important', - 'Paid' : 'atlas://gui/kivy/theming/light/confirmed' -} - -class AddressScreen(CScreen): - kvname = 'address' - cards = {} - - def get_card(self, addr, balance, is_used, label): - ci = self.cards.get(addr) - if ci is None: - ci = Factory.AddressItem() - ci.screen = self - ci.address = addr - self.cards[addr] = ci - - ci.memo = label - ci.amount = self.app.format_amount_and_units(balance) - request = self.app.wallet.get_payment_request(addr, self.app.electrum_config) - if is_used: - ci.status = _('Used') - elif request: - status, conf = self.app.wallet.get_request_status(addr) - requested_amount = request.get('amount') - # make sure that requested amount is > 0 - if status == PR_PAID: - s = _('Request paid') - elif status == PR_UNPAID: - s = _('Request pending') - elif status == PR_EXPIRED: - s = _('Request expired') - else: - s = '' - ci.status = s + ': ' + self.app.format_amount_and_units(requested_amount) - else: - ci.status = _('Funded') if balance>0 else _('Unused') - return ci - - - def update(self): - self.menu_actions = [('Receive', self.do_show), ('Details', self.do_view)] - wallet = self.app.wallet - if self.screen.show_change == 0: - _list = wallet.get_receiving_addresses() - elif self.screen.show_change == 1: - _list = wallet.get_change_addresses() - else: - _list = wallet.get_addresses() - search = self.screen.message - container = self.screen.ids.search_container - container.clear_widgets() - n = 0 - for address in _list: - label = wallet.labels.get(address, '') - balance = sum(wallet.get_addr_balance(address)) - is_used = wallet.is_used(address) - if self.screen.show_used == 1 and (balance or is_used): - continue - if self.screen.show_used == 2 and balance == 0: - continue - if self.screen.show_used == 3 and not is_used: - continue - card = self.get_card(address, balance, is_used, label) - if search and not self.ext_search(card, search): - continue - container.add_widget(card) - n += 1 - if not n: - msg = _('No address matching your search') - container.add_widget(EmptyLabel(text=msg)) - - def do_show(self, obj): - self.app.show_request(obj.address) - - def do_view(self, obj): - req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config) - if req: - c, u, x = self.app.wallet.get_addr_balance(obj.address) - balance = c + u + x - if balance > 0: - req['fund'] = balance - status = req.get('status') - amount = req.get('amount') - address = req['address'] - if amount: - status = req.get('status') - status = request_text[status] - else: - received_amount = self.app.wallet.get_addr_received(address) - status = self.app.format_amount_and_units(received_amount) - self.app.show_pr_details(req, status, False) - - else: - 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 do_delete(self, obj): - from .dialogs.question import Question - def cb(result): - if result: - self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config) - self.update() - d = Question(_('Delete request?'), cb) - d.open() - - def ext_search(self, card, search): - return card.memo.find(search) >= 0 or card.amount.find(search) >= 0 - + def do_save(self): + if self.save_request(): + self.app.show_info(_('Request was saved.')) class TabbedCarousel(Factory.TabbedPanel): diff --git a/gui/kivy/uix/ui_screens/address.kv b/gui/kivy/uix/ui_screens/address.kv deleted file mode 100644 index 07bb43677..000000000 --- a/gui/kivy/uix/ui_screens/address.kv +++ /dev/null @@ -1,90 +0,0 @@ -#:import _ electrum_gui.kivy.i18n._ -#:import Decimal decimal.Decimal -#:set btc_symbol chr(171) -#:set mbtc_symbol chr(187) -#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf' - - - 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 - -AddressScreen: - id: addr_screen - name: 'address' - message: '' - pr_status: 'Pending' - show_change: 0 - show_used: 0 - on_message: - self.parent.update() - BoxLayout - 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: app.address_screen.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: app.address_screen.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(addr_screen)) - ScrollView: - GridLayout: - cols: 1 - id: search_container - size_hint_y: None - height: self.minimum_height diff --git a/gui/kivy/uix/ui_screens/invoices.kv b/gui/kivy/uix/ui_screens/invoices.kv deleted file mode 100644 index 9621a8af6..000000000 --- a/gui/kivy/uix/ui_screens/invoices.kv +++ /dev/null @@ -1,66 +0,0 @@ - - #color: .305, .309, .309, 1 - text_size: self.width, None - halign: 'left' - valign: 'top' - - - requestor: '' - memo: '' - amount: '' - status: '' - date: '' - icon: 'atlas://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 - - -InvoicesScreen: - name: 'invoices' - BoxLayout: - orientation: 'vertical' - spacing: '1dp' - ScrollView: - GridLayout: - cols: 1 - id: invoices_container - size_hint: 1, None - height: self.minimum_height - spacing: '2dp' - padding: '12dp' diff --git a/gui/kivy/uix/ui_screens/receive.kv b/gui/kivy/uix/ui_screens/receive.kv index c58b77ec7..650be40ff 100644 --- a/gui/kivy/uix/ui_screens/receive.kv +++ b/gui/kivy/uix/ui_screens/receive.kv @@ -70,7 +70,7 @@ ReceiveScreen: id: address_label text: s.address if s.address else _('Bitcoin Address') shorten: True - disabled: True + on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s)) CardSeparator: opacity: message_selection.opacity color: blue_bottom.foreground_color @@ -110,16 +110,31 @@ ReceiveScreen: BoxLayout: size_hint: 1, None height: '48dp' + IconButton: + icon: 'atlas://gui/kivy/theming/light/save' + size_hint: 0.6, None + height: '48dp' + on_release: s.parent.do_save() Button: - text: _('Copy') + text: _('Requests') size_hint: 1, None height: '48dp' - on_release: s.parent.do_copy() + on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s)) Button: - text: _('Share') + text: _('Copy') size_hint: 1, None height: '48dp' + on_release: s.parent.do_copy() + IconButton: + icon: 'atlas://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 diff --git a/gui/kivy/uix/ui_screens/requests.kv b/gui/kivy/uix/ui_screens/requests.kv deleted file mode 100644 index 1e39ec7db..000000000 --- a/gui/kivy/uix/ui_screens/requests.kv +++ /dev/null @@ -1,66 +0,0 @@ - - #color: .305, .309, .309, 1 - text_size: self.width, None - halign: 'left' - valign: 'top' - - - address: '' - memo: '' - amount: '' - status: '' - date: '' - icon: 'atlas://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 - - - -RequestsScreen: - name: 'requests' - BoxLayout: - orientation: 'vertical' - spacing: '1dp' - ScrollView: - GridLayout: - cols: 1 - id: requests_container - size_hint_y: None - height: self.minimum_height - spacing: '2dp' - padding: '12dp' diff --git a/gui/kivy/uix/ui_screens/send.kv b/gui/kivy/uix/ui_screens/send.kv index f2a361e37..7269cb9b6 100644 --- a/gui/kivy/uix/ui_screens/send.kv +++ b/gui/kivy/uix/ui_screens/send.kv @@ -34,6 +34,7 @@ SendScreen: 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 @@ -93,25 +94,29 @@ SendScreen: size_hint: 1, None height: '48dp' 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://gui/kivy/theming/light/camera' + on_release: s.parent.do_save() + icon: 'atlas://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() - Button: - text: _('Clear') - on_release: s.parent.do_clear() IconButton: + id: qr size_hint: 0.6, 1 - on_release: s.parent.do_save() - icon: 'atlas://gui/kivy/theming/light/save' + on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr)) + icon: 'atlas://gui/kivy/theming/light/camera' BoxLayout: size_hint: 1, None height: '48dp' + Button: + text: _('Clear') + on_release: s.parent.do_clear() Widget: - size_hint: 2, 1 + size_hint: 1, 1 Button: text: _('Pay') size_hint: 1, 1 diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py index 0879208f9..426dfd2c6 100644 --- a/gui/qt/__init__.py +++ b/gui/qt/__init__.py @@ -44,7 +44,8 @@ from electrum import WalletStorage # from electrum.synchronizer import Synchronizer # from electrum.verifier import SPV # from electrum.util import DebugMem -from electrum.util import UserCancelled, print_error +from electrum.util import (UserCancelled, print_error, + WalletFileException, BitcoinException) # from electrum.wallet import Abstract_Wallet from .installwizard import InstallWizard, GoBack @@ -185,42 +186,51 @@ class ElectrumGui: def start_new_window(self, path, uri): '''Raises the window for the wallet if it is open. Otherwise - opens the wallet and creates a new window for it.''' - for w in self.windows: - if w.wallet.storage.path == path: - w.bring_to_top() - break - else: + 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_() + return + if not wallet: + storage = WalletStorage(path, manual_upgrades=True) + wizard = InstallWizard(self.config, self.app, self.plugins, storage) try: - wallet = self.daemon.load_wallet(path, None) - except BaseException as e: - traceback.print_exc(file=sys.stdout) + 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:') + '\n' + str(e)) + _('Cannot load wallet') + ' (2):\n' + str(e)) d.exec_() 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() - except UserCancelled: - pass - except GoBack as e: - print_error('[start_new_window] Exception caught (GoBack)', e) + finally: wizard.terminate() - if not wallet: - return + 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: - 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 + 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() diff --git a/gui/qt/address_list.py b/gui/qt/address_list.py index 104f367a8..6eb0cb236 100644 --- a/gui/qt/address_list.py +++ b/gui/qt/address_list.py @@ -123,7 +123,7 @@ class AddressList(MyTreeWidget): address_item.setText(0, _('receiving')) address_item.setBackground(0, ColorScheme.GREEN.as_color(True)) address_item.setFont(1, QFont(MONOSPACE_FONT)) - address_item.setData(1, Qt.UserRole, address) + address_item.setData(0, Qt.UserRole, address) # column 0; independent from address column if self.wallet.is_frozen(address): address_item.setBackground(1, ColorScheme.BLUE.as_color(True)) if self.wallet.is_beyond_limit(address): diff --git a/gui/qt/console.py b/gui/qt/console.py index bde05a3df..2b49d93c6 100644 --- a/gui/qt/console.py +++ b/gui/qt/console.py @@ -223,7 +223,7 @@ class Console(QtWidgets.QPlainTextEdit): exec(command, self.namespace, self.namespace) except SystemExit: self.close() - except Exception: + except BaseException: traceback_lines = traceback.format_exc().split('\n') # Remove traceback mentioning this file, and a linebreak for i in (3,2,1,-1): diff --git a/gui/qt/exception_window.py b/gui/qt/exception_window.py index fa2912834..15242bf0c 100644 --- a/gui/qt/exception_window.py +++ b/gui/qt/exception_window.py @@ -38,6 +38,8 @@ from PyQt5.QtWidgets import * from electrum.i18n import _ from electrum import ELECTRUM_VERSION, bitcoin, constants +from .util import MessageBoxMixin + issue_template = """

Traceback

 {traceback}
@@ -54,7 +56,7 @@ issue_template = """

Traceback

report_server = "https://crashhub.electrum.org/crash" -class Exception_Window(QWidget): +class Exception_Window(QWidget, MessageBoxMixin): _active_window = None def __init__(self, main_window, exctype, value, tb): @@ -75,7 +77,9 @@ class Exception_Window(QWidget): 'information:'))) collapse_info = QPushButton(_("Show report contents")) - collapse_info.clicked.connect(lambda: QMessageBox.about(self, "Report contents", self.get_report_string())) + 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(_("Please briefly describe what led to the error (optional):"))) diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py index 3140af001..8b4baf575 100644 --- a/gui/qt/history_list.py +++ b/gui/qt/history_list.py @@ -161,6 +161,9 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): 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() @@ -232,7 +235,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): label = tx_item['label'] status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) has_invoice = self.wallet.invoices.paid.get(tx_hash) - icon = QIcon(":icons/" + TX_ICONS[status]) + icon = self.icon_cache.get(":icons/" + TX_ICONS[status]) v_str = self.parent.format_amount(value, True, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True) entry = ['', tx_hash, status_str, label, v_str, balance_str] @@ -250,10 +253,10 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): item.setToolTip(0, str(conf) + " confirmation" + ("s" if conf != 1 else "")) item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf)) if has_invoice: - item.setIcon(3, QIcon(":icons/seal")) + item.setIcon(3, self.icon_cache.get(":icons/seal")) for i in range(len(entry)): if i>3: - item.setTextAlignment(i, Qt.AlignRight) + item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter) if i!=2: item.setFont(i, QFont(MONOSPACE_FONT)) if value and value < 0: @@ -299,7 +302,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): def update_item(self, tx_hash, height, conf, timestamp): status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) - icon = QIcon(":icons/" + TX_ICONS[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] @@ -344,7 +347,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): if child_tx: menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx)) if pr_key: - menu.addAction(QIcon(":icons/seal"), _("View invoice"), lambda: self.parent.show_invoice(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)) @@ -408,7 +411,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): lines.append([item['txid'], item.get('label', ''), item['confirmations'], item['value'], item['date']]) else: lines.append(item) - with open(fileName, "w+") as f: + with open(fileName, "w+", encoding='utf-8') as f: if is_csv: import csv transaction = csv.writer(f, lineterminator='\n') diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index a6e335125..e1830ebfa 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -148,7 +148,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. - def run_and_get_wallet(self): + def run_and_get_wallet(self, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() @@ -181,10 +181,15 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def on_filename(filename): path = os.path.join(wallet_folder, filename) + wallet_from_memory = get_wallet_from_daemon(path) try: - self.storage = WalletStorage(path, manual_upgrades=True) + if wallet_from_memory: + self.storage = wallet_from_memory.storage + else: + self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) - except IOError: + except BaseException: + traceback.print_exc(file=sys.stderr) self.storage = None self.next_button.setEnabled(False) if self.storage: @@ -192,7 +197,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False - else: + elif not wallet_from_memory: if self.storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') @@ -204,6 +209,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): else: msg = _("Press 'Next' to open this wallet.") pw = False + else: + msg = _("This file is already open in memory.") + "\n" \ + + _("Press 'Next' to create/focus window.") + pw = False else: msg = _('Cannot read file') pw = False @@ -228,6 +237,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): return if not self.storage.file_exists(): break + wallet_from_memory = get_wallet_from_daemon(self.storage.path) + if wallet_from_memory: + return wallet_from_memory if self.storage.file_exists() and self.storage.is_encrypted(): if self.storage.is_encrypted_with_user_pw(): password = self.pw_e.text() @@ -245,14 +257,12 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) except InvalidPassword as e: - # FIXME if we get here because of mistyped passphrase - # then that passphrase gets "cached" QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.')) self.stack = [] - return self.run_and_get_wallet() + return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) @@ -302,8 +312,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): self.wallet = Wallet(self.storage) return self.wallet - - def finished(self): """Called in hardware client wrapper, in order to close popups.""" return @@ -340,7 +348,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): if not result and raise_on_cancel: raise UserCancelled if result == 1: - raise GoBack + raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) diff --git a/gui/qt/invoice_list.py b/gui/qt/invoice_list.py index 586dd71c5..462aadd85 100644 --- a/gui/qt/invoice_list.py +++ b/gui/qt/invoice_list.py @@ -48,7 +48,7 @@ class InvoiceList(MyTreeWidget): 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, QIcon(pr_icons.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)) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 33389619b..5e74ae3f4 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -47,7 +47,7 @@ from electrum.i18n import _ from electrum.util import (format_time, format_satoshis, PrintError, format_satoshis_plain, NotEnoughFunds, UserCancelled, NoDynamicFeeEstimates, profiler, - export_meta, import_meta, bh2u, bfh) + export_meta, import_meta, bh2u, bfh, InvalidPassword) from electrum import Transaction from electrum import util, bitcoin, commands, coinchooser from electrum import paymentrequest @@ -396,7 +396,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.show_warning(msg, title=_('Information')) def open_wallet(self): - wallet_folder = self.get_wallet_folder() + 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 @@ -409,13 +413,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): 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 (IOError, os.error) as reason: + 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): @@ -441,7 +444,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return os.path.dirname(os.path.abspath(self.config.get_wallet_path())) def new_wallet(self): - wallet_folder = self.get_wallet_folder() + 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 @@ -531,7 +538,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): help_menu = menubar.addMenu(_("&Help")) help_menu.addAction(_("&About"), self.show_about) - help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org")) + help_menu.addAction(_("&Official website"), lambda: webbrowser.open("https://electrum.org")) 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) @@ -663,8 +670,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): 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 None - if rate is None or amount is None: + 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: @@ -1182,7 +1189,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.fee_adv_controls.setVisible(False) self.preview_button = EnterButton(_("Preview"), self.do_preview) - self.preview_button.setToolTip(_('Display the details of your transactions before signing it.')) + 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() @@ -1329,8 +1336,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): # actual fees often differ somewhat. if freeze_feerate or self.fee_slider.is_active(): displayed_feerate = self.feerate_e.get_amount() - displayed_feerate = displayed_feerate // 1000 if displayed_feerate else 0 - displayed_fee = displayed_feerate * size + if displayed_feerate: + displayed_feerate = displayed_feerate // 1000 + else: + # fallback to actual fee + displayed_feerate = fee // size if fee is not None else None + self.feerate_e.setAmount(displayed_feerate) + displayed_fee = displayed_feerate * size if displayed_feerate is not None else None self.fee_e.setAmount(displayed_fee) else: if freeze_fee: @@ -1767,8 +1779,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def remove_address(self, addr): if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")): self.wallet.delete_address(addr) - self.address_list.update() - self.history_list.update() + self.need_update.set() # history, addresses, coins self.clear_receive_tab() def get_coins(self): @@ -1827,6 +1838,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): 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) @@ -1968,10 +1982,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return try: self.wallet.update_password(old_password, new_password, encrypt_file) - except BaseException as e: + except InvalidPassword as e: self.show_error(str(e)) return - except: + except BaseException: traceback.print_exc(file=sys.stdout) self.show_error(_('Failed to update password')) return @@ -2304,7 +2318,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.pay_to_URI(data) return # else if the user scanned an offline signed tx - data = bh2u(bitcoin.base_decode(data, length=None, base=43)) + 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 @@ -2621,13 +2639,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): msg = '\n'.join([ _('Time based: fee rate is based on average confirmation time estimates'), - _('Mempool based: fee rate is targetting a depth in the memory pool') + _('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(1 if self.config.use_mempool_fees() else 0) + 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) @@ -2648,7 +2666,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): use_rbf_cb.setChecked(self.config.get('use_rbf', True)) use_rbf_cb.setToolTip( _('If you check this box, your transactions will be marked as non-final,') + '\n' + \ - _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\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) @@ -2658,7 +2676,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): 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 http://openalias.org' + + 'For more information, see https://openalias.org' alias_label = HelpLabel(_('OpenAlias') + ':', msg) alias = self.config.get('alias','') alias_e = QLineEdit(alias) diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py index d1cc237c0..716a5f58d 100644 --- a/gui/qt/paytoedit.py +++ b/gui/qt/paytoedit.py @@ -28,6 +28,8 @@ import re from decimal import Decimal from electrum import bitcoin +from electrum.util import bfh + from .qrtextedit import ScanQRTextEdit from .completion_text_edit import CompletionTextEdit from . import util @@ -92,9 +94,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit): for word in x.split(): if word[0:3] == 'OP_': assert word in opcodes.lookup - script += chr(opcodes.lookup[word]) + opcode_int = opcodes.lookup[word] + assert opcode_int < 256 # opcode is single-byte + script += bitcoin.int_to_hex(opcode_int) else: - script += push_script(word).decode('hex') + bfh(word) # to test it is hex data + script += push_script(word) return script def parse_amount(self, x): diff --git a/gui/qt/qrtextedit.py b/gui/qt/qrtextedit.py index aef68f053..cc5c5cf0e 100644 --- a/gui/qt/qrtextedit.py +++ b/gui/qt/qrtextedit.py @@ -46,9 +46,13 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): fileName, __ = QFileDialog.getOpenFileName(self, 'select file') if not fileName: return - with open(fileName, "r") as f: - data = f.read() - self.setText(data) + 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 diff --git a/gui/qt/request_list.py b/gui/qt/request_list.py index 59b084ff9..dacae503e 100644 --- a/gui/qt/request_list.py +++ b/gui/qt/request_list.py @@ -98,10 +98,10 @@ class RequestList(MyTreeWidget): 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, QIcon(":icons/seal.png")) + 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, QIcon(pr_icons.get(status))) + item.setIcon(6, self.icon_cache.get(pr_icons.get(status))) self.addTopLevelItem(item) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 4fc589dd1..d53997baf 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -179,11 +179,12 @@ class TxDialog(QDialog, MessageBoxMixin): def sign(self): def sign_done(success): - if 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("") + self.save_button.setDisabled(False) + self.save_button.setToolTip("") self.update() self.main_window.pop_top_level_window(self) @@ -289,7 +290,7 @@ class TxDialog(QDialog, MessageBoxMixin): cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext) addr = x.get('address') if addr == "(pubkey)": - _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) + _addr = self.wallet.get_txin_address(x) if _addr: addr = _addr if addr is None: diff --git a/gui/qt/util.py b/gui/qt/util.py index 306c64a55..b8fc1cdae 100644 --- a/gui/qt/util.py +++ b/gui/qt/util.py @@ -393,6 +393,8 @@ class MyTreeWidget(QTreeWidget): self.addChild = self.addTopLevelItem self.insertChild = self.insertTopLevelItem + self.icon_cache = IconCache() + # Control which columns are editable self.editor = None self.pending_update = False @@ -779,6 +781,17 @@ class SortableTreeWidgetItem(QTreeWidgetItem): 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")) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 793a96fb7..ba29f1ff0 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -33,7 +33,7 @@ from .keystore import bip44_derivation from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types 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 +from .util import UserCancelled, InvalidPassword # hardware device setup purpose HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2) @@ -164,7 +164,7 @@ class BaseWizard(object): k = keystore.Imported_KeyStore({}) self.storage.put('keystore', k.dump()) w = Imported_Wallet(self.storage) - for x in text.split(): + for x in keystore.get_private_keys(text): w.import_private_key(x, None) self.keystores.append(w.keystore) else: @@ -202,20 +202,32 @@ class BaseWizard(object): # scan devices devices = [] devmgr = self.plugins.device_manager - for name, description, plugin in support: - try: - # FIXME: side-effect: unpaired_device_info sets client.handler - u = devmgr.unpaired_device_infos(None, plugin) - except: - devmgr.print_error("error", name) - continue - devices += list(map(lambda x: (name, x), u)) + 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)) + debug_msg += ' {}:\n {}\n'.format(plugin.name, e) + 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.'), + _('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 @@ -243,6 +255,9 @@ class BaseWizard(object): devmgr.unpair_id(device_info.device.id_) self.choose_hw_device(purpose) return + except UserCancelled: + self.choose_hw_device(purpose) + return except BaseException as e: self.show_error(str(e)) self.choose_hw_device(purpose) @@ -259,7 +274,15 @@ class BaseWizard(object): 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, ()) - self.storage.decrypt(password) + 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) @@ -459,10 +482,6 @@ class BaseWizard(object): def show_xpub_and_add_cosigners(self, xpub): self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) - def on_cosigner(self, text, password, i): - k = keystore.from_master_key(text, password) - self.on_keystore(k) - def choose_seed_type(self): title = _('Choose Seed type') message = ' '.join([ diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 18d2afd14..f43d1047b 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -32,7 +32,7 @@ import json import ecdsa import pyaes -from .util import bfh, bh2u, to_string +from .util import bfh, bh2u, to_string, BitcoinException from . import version from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict from . import segwit_addr @@ -44,7 +44,7 @@ from . import constants COINBASE_MATURITY = 100 COIN = 100000000 -# supported types of transction outputs +# supported types of transaction outputs TYPE_ADDRESS = 0 TYPE_PUBKEY = 1 TYPE_SCRIPT = 2 @@ -144,6 +144,9 @@ def rev_hex(s): def int_to_hex(i, length=1): assert isinstance(i, int) + if i < 0: + # two's complement + i = pow(256, length) + i s = hex(i)[2:].rstrip('L') s = "0"*(2*length - len(s)) + s return rev_hex(s) @@ -261,7 +264,7 @@ def hash_160(public_key): return md.digest() -def hash160_to_b58_address(h160, addrtype, witness_program_version=1): +def hash160_to_b58_address(h160, addrtype): s = bytes([addrtype]) s += h160 return base_encode(s+Hash(s)[0:4], base=58) @@ -273,23 +276,29 @@ def b58_address_to_hash160(addr): return _bytes[0], _bytes[1:21] -def hash160_to_p2pkh(h160): - return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2PKH) +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): - return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2SH) +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): return hash160_to_p2pkh(hash_160(public_key)) -def hash_to_segwit_addr(h): - return segwit_addr.encode(constants.net.SEGWIT_HRP, 0, h) +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)) + return hash_to_segwit_addr(hash_160(public_key), witver=0) def script_to_p2wsh(script): - return hash_to_segwit_addr(sha256(bfh(script))) + return hash_to_segwit_addr(sha256(bfh(script)), witver=0) def p2wpkh_nested_script(pubkey): pkh = bh2u(hash_160(bfh(pubkey))) @@ -303,7 +312,7 @@ def pubkey_to_address(txin_type, pubkey): if txin_type == 'p2pkh': return public_key_to_p2pkh(bfh(pubkey)) elif txin_type == 'p2wpkh': - return hash_to_segwit_addr(hash_160(bfh(pubkey))) + 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))) @@ -322,14 +331,16 @@ def redeem_script_to_address(txin_type, redeem_script): raise NotImplementedError(txin_type) -def script_to_address(script): +def script_to_address(script, *, net=None): from .transaction import get_address_from_output_script - t, addr = get_address_from_output_script(bfh(script)) + t, addr = get_address_from_output_script(bfh(script), net=net) assert t == TYPE_ADDRESS return addr -def address_to_script(addr): - witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, 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: assert (0 <= witver <= 16) OP_n = witver + 0x50 if witver > 0 else 0 @@ -337,16 +348,16 @@ def address_to_script(addr): script += push_script(bh2u(bytes(witprog))) return script addrtype, hash_160 = b58_address_to_hash160(addr) - if addrtype == constants.net.ADDRTYPE_P2PKH: + 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 == constants.net.ADDRTYPE_P2SH: + elif addrtype == net.ADDRTYPE_P2SH: script = 'a9' # op_hash_160 script += push_script(bh2u(hash_160)) script += '87' # op_equal else: - raise BaseException('unknown address type') + raise BitcoinException('unknown address type: {}'.format(addrtype)) return script def address_to_scripthash(addr): @@ -408,7 +419,10 @@ def base_decode(v, length, base): chars = __b43chars long_value = 0 for (i, c) in enumerate(v[::-1]): - long_value += chars.find(bytes([c])) * (base**i) + 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) @@ -428,6 +442,10 @@ def base_decode(v, length, base): return bytes(result) +class InvalidChecksum(Exception): + pass + + def EncodeBase58Check(vchIn): hash = Hash(vchIn) return base_encode(vchIn + hash[0:4], base=58) @@ -440,13 +458,14 @@ def DecodeBase58Check(psz): hash = Hash(key) cs32 = hash[0:4] if cs32 != csum: - return None + 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 SCRIPT_TYPES = { 'p2pkh':0, 'p2wpkh':1, @@ -479,9 +498,12 @@ def deserialize_privkey(key): if ':' in key: txin_type, key = key.split(sep=':', maxsplit=1) assert txin_type in SCRIPT_TYPES - vch = DecodeBase58Check(key) - if not vch: - raise BaseException("cannot deserialize", key) + 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 @@ -876,7 +898,8 @@ def deserialize_xkey(xkey, prv, *, net=None): net = constants.net xkey = DecodeBase58Check(xkey) if len(xkey) != 78: - raise BaseException('Invalid length') + raise BitcoinException('Invalid length for extended key: {}' + .format(len(xkey))) depth = xkey[4] fingerprint = xkey[5:9] child_number = xkey[9:13] @@ -884,7 +907,8 @@ def deserialize_xkey(xkey, prv, *, net=None): 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 BaseException('Invalid xpub format', hex(header)) + raise BitcoinException('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:] diff --git a/lib/blockchain.py b/lib/blockchain.py index f6fe31dcf..7ec9aa8af 100644 --- a/lib/blockchain.py +++ b/lib/blockchain.py @@ -255,6 +255,10 @@ class Blockchain(util.PrintError): with open(name, 'rb') as f: f.seek(delta * 80) h = f.read(80) + elif not os.path.exists(util.get_headers_dir(self.config)): + raise Exception('Electrum datadir does not exist. Was it deleted while running?') + else: + raise Exception('Cannot find headers file but datadir is there. Should be at {}'.format(name)) if h == bytes([0])*80: return None return deserialize_header(h, height) @@ -313,6 +317,8 @@ class Blockchain(util.PrintError): 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) diff --git a/lib/commands.py b/lib/commands.py index 23c35b896..0b507ff45 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -34,7 +34,7 @@ from functools import wraps from decimal import Decimal from .import util -from .util import bfh, bh2u, format_satoshis, json_decode, print_error +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 _ @@ -159,19 +159,13 @@ class Commands: return True @command('') - def make_seed(self, nbits=132, entropy=1, language=None, segwit=False): + 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, custom_entropy=entropy) + s = Mnemonic(language).make_seed(t, nbits) return s - @command('') - def check_seed(self, seed, entropy=1, language=None): - """Check that a seed was generated with given entropy""" - from .mnemonic import Mnemonic - return Mnemonic(language).check_seed(seed, entropy) - @command('n') def getaddresshistory(self, address): """Return the transaction history of any address. Note: This is a @@ -207,7 +201,7 @@ class Commands: keypairs = {} inputs = jsontx.get('inputs') outputs = jsontx.get('outputs') - locktime = jsontx.get('locktime', 0) + locktime = jsontx.get('lockTime', 0) for txin in inputs: if txin.get('output'): prevout_hash, prevout_n = txin['output'].split(':') @@ -418,6 +412,8 @@ class Commands: 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: @@ -426,7 +422,7 @@ class Commands: return tx @command('wp') - def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None): + 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 @@ -434,7 +430,7 @@ class Commands: return tx.as_dict() @command('wp') - def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None): + 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 @@ -455,7 +451,7 @@ class Commands: from .exchange_rate import FxThread fx = FxThread(self.config, None) kwargs['fx'] = fx - return self.wallet.get_full_history(**kwargs) + return json_encode(self.wallet.get_full_history(**kwargs)) @command('w') def setlabel(self, key, label): @@ -697,7 +693,6 @@ command_options = { '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"), - 'entropy': (None, "Custom entropy"), 'segwit': (None, "Create segwit seed"), 'language': ("-L", "Default language for wordlist"), 'privkey': (None, "Private key. Set to '?' to get a prompt."), @@ -726,7 +721,6 @@ arg_types = { 'nbits': int, 'imax': int, 'year': int, - 'entropy': int, 'tx': tx_from_str, 'pubkeys': json_loads, 'jsontx': json_loads, diff --git a/lib/constants.py b/lib/constants.py index ec35cbe3e..0eed179c0 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -87,7 +87,7 @@ class BitcoinTestnet: XPUB_HEADERS = { 'standard': 0x043587cf, # tpub 'p2wpkh-p2sh': 0x044a5262, # upub - 'p2wsh-p2sh': 0x024285ef, # Upub + 'p2wsh-p2sh': 0x024289ef, # Upub 'p2wpkh': 0x045f1cf6, # vpub 'p2wsh': 0x02575483, # Vpub } diff --git a/lib/contacts.py b/lib/contacts.py index 0015a8610..03b8d3ecc 100644 --- a/lib/contacts.py +++ b/lib/contacts.py @@ -22,13 +22,14 @@ # 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 +from .util import export_meta, import_meta, print_error, to_string class Contacts(dict): @@ -96,10 +97,14 @@ class Contacts(dict): def resolve_openalias(self, url): # support email-style addresses, per the OA standard url = url.replace('@', '.') - records, validated = dnssec.query(url, dns.rdatatype.TXT) + 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 = record.strings[0] + 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=([^;]+)') diff --git a/lib/daemon.py b/lib/daemon.py index b3242215e..2cf0d9847 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -173,7 +173,8 @@ class Daemon(DaemonThread): elif sub == 'load_wallet': path = config.get_wallet_path() wallet = self.load_wallet(path, config.get('password')) - self.cmd_runner.wallet = wallet + if wallet is not None: + self.cmd_runner.wallet = wallet response = wallet is not None elif sub == 'close_wallet': path = config.get_wallet_path() @@ -185,6 +186,9 @@ class Daemon(DaemonThread): 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], @@ -196,6 +200,7 @@ class Daemon(DaemonThread): '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: diff --git a/lib/exchange_rate.py b/lib/exchange_rate.py index b6a965fe7..b810c2022 100644 --- a/lib/exchange_rate.py +++ b/lib/exchange_rate.py @@ -66,7 +66,7 @@ class ExchangeBase(PrintError): if os.path.exists(filename): timestamp = os.stat(filename).st_mtime try: - with open(filename, 'r') as f: + with open(filename, 'r', encoding='utf-8') as f: h = json.loads(f.read()) h['timestamp'] = timestamp except: @@ -87,7 +87,7 @@ class ExchangeBase(PrintError): self.print_error("failed fx history:", e) return filename = os.path.join(cache_dir, self.name() + '_' + ccy) - with open(filename, 'w') as f: + with open(filename, 'w', encoding='utf-8') as f: f.write(json.dumps(h)) h['timestamp'] = time.time() self.history[ccy] = h @@ -382,7 +382,7 @@ def get_exchanges_and_currencies(): import os, json path = os.path.join(os.path.dirname(__file__), 'currencies.json') try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: return json.loads(f.read()) except: pass @@ -399,7 +399,7 @@ def get_exchanges_and_currencies(): except: print(name, "error") continue - with open(path, 'w') as f: + with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(d, indent=4, sort_keys=True)) return d diff --git a/lib/interface.py b/lib/interface.py index ac1495fbd..1847602b0 100644 --- a/lib/interface.py +++ b/lib/interface.py @@ -144,7 +144,7 @@ class TcpConnection(threading.Thread, util.PrintError): 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: - print_error(e) + self.print_error(e) s = None except: return @@ -172,8 +172,10 @@ class TcpConnection(threading.Thread, util.PrintError): # workaround android bug cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert) temporary_path = cert_path + '.temp' - with open(temporary_path,"w") as f: + with open(temporary_path, "w", encoding='utf-8') as f: f.write(cert) + f.flush() + os.fsync(f.fileno()) else: is_new = False @@ -199,7 +201,7 @@ class TcpConnection(threading.Thread, util.PrintError): os.unlink(rej) os.rename(temporary_path, rej) else: - with open(cert_path) as f: + with open(cert_path, encoding='utf-8') as f: cert = f.read() try: b = pem.dePem(cert, 'CERTIFICATE') @@ -295,8 +297,8 @@ class Interface(util.PrintError): wire_requests = self.unsent_requests[0:n] try: self.pipe.send_all([make_dict(*r) for r in wire_requests]) - except socket.error as e: - self.print_error("socket error:", e) + 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: @@ -396,7 +398,7 @@ def test_certificates(): certs = os.listdir(mydir) for c in certs: p = os.path.join(mydir,c) - with open(p) as f: + with open(p, encoding='utf-8') as f: cert = f.read() check_cert(c, cert) diff --git a/lib/keystore.py b/lib/keystore.py index 676db247d..968323310 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -29,7 +29,8 @@ from unicodedata import normalize from . import bitcoin from .bitcoin import * from . import constants -from .util import PrintError, InvalidPassword, hfu +from .util import (PrintError, InvalidPassword, hfu, WalletFileException, + BitcoinException) from .mnemonic import Mnemonic, load_wordlist from .plugins import run_hook @@ -75,6 +76,8 @@ class KeyStore(PrintError): return False return bool(self.get_tx_derivations(tx)) + def ready_to_sign(self): + return not self.is_watching_only() class Software_KeyStore(KeyStore): @@ -142,6 +145,10 @@ class Imported_KeyStore(Software_KeyStore): # 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 can not be trusted. + # Removing keys complicates this further. self.keypairs[pubkey] = pw_encode(serialized_privkey, password) return txin_type, pubkey @@ -531,6 +538,17 @@ class Hardware_KeyStore(KeyStore, Xpub): 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 '') @@ -615,7 +633,8 @@ def xpubkey_to_address(x_pubkey): mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) else: - raise BaseException("Cannot parse pubkey") + raise BitcoinException("Cannot parse pubkey. prefix: {}" + .format(x_pubkey[0:2])) if pubkey: address = public_key_to_p2pkh(bfh(pubkey)) return pubkey, address @@ -634,14 +653,15 @@ def hardware_keystore(d): if hw_type in hw_keystores: constructor = hw_keystores[hw_type] return constructor(d) - raise BaseException('unknown hardware type', hw_type) + raise WalletFileException('unknown hardware type: {}'.format(hw_type)) def load_keystore(storage, name): - w = storage.get('wallet_type', 'standard') d = storage.get(name, {}) t = d.get('type') if not t: - raise BaseException('wallet format requires update') + raise WalletFileException( + 'Wallet format requires update.\n' + 'Cannot find keystore for name {}'.format(name)) if t == 'old': k = Old_KeyStore(d) elif t == 'imported': @@ -651,7 +671,8 @@ def load_keystore(storage, name): elif t == 'hardware': k = hardware_keystore(d) else: - raise BaseException('unknown wallet type', t) + raise WalletFileException( + 'Unknown type {} for keystore named {}'.format(t, name)) return k @@ -709,7 +730,7 @@ def from_seed(seed, passphrase, is_p2sh): xtype = 'p2wsh' if is_p2sh else 'p2wpkh' keystore.add_xprv_from_seed(bip32_seed, xtype, der) else: - raise BaseException(t) + raise BitcoinException('Unexpected seed type {}'.format(t)) return keystore def from_private_key_list(text): @@ -743,5 +764,5 @@ def from_master_key(text): elif is_xpub(text): k = from_xpub(text) else: - raise BaseException('Invalid key') + raise BitcoinException('Invalid master key') return k diff --git a/lib/mnemonic.py b/lib/mnemonic.py index 7096e20f6..02038df05 100644 --- a/lib/mnemonic.py +++ b/lib/mnemonic.py @@ -91,7 +91,7 @@ def normalize_text(seed): def load_wordlist(filename): path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: s = f.read().strip() s = unicodedata.normalize('NFKD', s) lines = s.split('\n') @@ -157,28 +157,21 @@ class Mnemonic(object): i = i*n + k return i - def check_seed(self, seed, custom_entropy): - assert is_new_seed(seed) - i = self.mnemonic_decode(seed) - return i % custom_entropy == 0 - - def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1): + 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 distibution for the last word bpw = math.log(len(self.wordlist), 2) - num_bits = int(math.ceil(num_bits/bpw) * bpw) - # handle custom entropy; make sure we add at least 16 bits - n_custom = int(math.ceil(math.log(custom_entropy, 2))) - n = max(16, num_bits - n_custom) - print_error("make_seed", prefix, "adding %d bits"%n) - my_entropy = 1 - while my_entropy < pow(2, n - bpw): + # 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 - my_entropy = ecdsa.util.randrange(pow(2, n)) + entropy = ecdsa.util.randrange(pow(2, n)) nonce = 0 while True: nonce += 1 - i = custom_entropy * (my_entropy + nonce) + i = entropy + nonce seed = self.mnemonic_encode(i) assert i == self.mnemonic_decode(seed) if is_old_seed(seed): diff --git a/lib/network.py b/lib/network.py index b803a7820..ed952eec0 100644 --- a/lib/network.py +++ b/lib/network.py @@ -246,7 +246,7 @@ class Network(util.DaemonThread): return [] path = os.path.join(self.config.path, "recent_servers") try: - with open(path, "r") as f: + with open(path, "r", encoding='utf-8') as f: data = f.read() return json.loads(data) except: @@ -258,7 +258,7 @@ class Network(util.DaemonThread): 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") as f: + with open(path, "w", encoding='utf-8') as f: f.write(s) except: pass @@ -319,7 +319,7 @@ class Network(util.DaemonThread): self.queue_request('server.peers.subscribe', []) self.request_fee_estimates() self.queue_request('blockchain.relayfee', []) - for h in self.subscribed_addresses: + for h in list(self.subscribed_addresses): self.queue_request('blockchain.scripthash.subscribe', [h]) def request_fee_estimates(self): @@ -563,7 +563,7 @@ class Network(util.DaemonThread): self.notify('fee') elif method == 'blockchain.relayfee': if error is None: - self.relay_fee = int(result * COIN) + self.relay_fee = int(result * COIN) if result is not None else None self.print_error("relayfee", self.relay_fee) elif method == 'blockchain.block.get_chunk': self.on_get_chunk(interface, response) @@ -677,7 +677,7 @@ class Network(util.DaemonThread): # check cached response for subscriptions r = self.sub_cache.get(k) if r is not None: - util.print_error("cache hit", k) + self.print_error("cache hit", k) callback(r) else: message_id = self.queue_request(method, params) @@ -1089,7 +1089,7 @@ class Network(util.DaemonThread): def export_checkpoints(self, path): # run manually from the console to generate checkpoints cp = self.blockchain().get_checkpoints() - with open(path, 'w') as f: + with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(cp, indent=4)) def max_checkpoint(self): diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 471186705..d5a898375 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -89,7 +89,7 @@ def get_payment_request(url): error = "payment URL not pointing to a valid server" elif u.scheme == 'file': try: - with open(u.path, 'r') as f: + with open(u.path, 'r', encoding='utf-8') as f: data = f.read() except IOError: data = None @@ -385,9 +385,9 @@ 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') as f: + with open(key_path, 'r', encoding='utf-8') as f: params = pem.parse_private_key(f.read()) - with open(cert_path, 'r') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") # verify chain @@ -405,10 +405,10 @@ def check_ssl_config(config): def sign_request_with_x509(pr, key_path, cert_path): from . import pem - with open(key_path, 'r') as f: + 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') as f: + with open(cert_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") certificates = pb2.X509Certificates() @@ -453,7 +453,11 @@ class InvoiceStore(object): def set_paid(self, pr, txid): pr.tx = txid - self.paid[txid] = pr.get_id() + 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(): diff --git a/lib/plot.py b/lib/plot.py index 5bd6add64..3174d1b26 100644 --- a/lib/plot.py +++ b/lib/plot.py @@ -1,17 +1,13 @@ -from PyQt5.QtGui import * -from electrum.i18n import _ - - import datetime from collections import defaultdict -from electrum.bitcoin import COIN import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt import matplotlib.dates as md -from matplotlib.patches import Ellipse -from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker + +from .i18n import _ +from .bitcoin import COIN class NothingToPlotException(Exception): diff --git a/lib/plugins.py b/lib/plugins.py index 9f58611f0..b9c539948 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -461,12 +461,14 @@ class DeviceMgr(ThreadJob, PrintError): 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: + raise Exception('Missing libraries for {}'.format(plugin.name)) 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 not device.product_key in plugin.DEVICE_IDS: + if device.product_key not in plugin.DEVICE_IDS: continue client = self.create_client(device, handler, plugin) if not client: @@ -482,9 +484,14 @@ class DeviceMgr(ThreadJob, PrintError): infos = self.unpaired_device_infos(handler, plugin, devices) if infos: break - msg = _('Please insert your {}. Verify the cable is ' - 'connected and that no other application is using it.\n\n' - 'Try to connect again?').format(plugin.device) + 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 @@ -495,7 +502,7 @@ class DeviceMgr(ThreadJob, PrintError): if info.label == keystore.label: return info msg = _("Please select which {} device to use:").format(plugin.device) - descriptions = [info.label + ' (%s)'%(_("initialized") if info.initialized else _("wiped")) for info in infos] + 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() @@ -506,17 +513,15 @@ class DeviceMgr(ThreadJob, PrintError): handler.win.wallet.save_keystore() return info - def scan_devices(self): - # All currently supported hardware libraries use hid, so we - # assume it here. This can be easily abstracted if necessary. - # Note this import must be local so those without hardware - # wallet libraries are not affected. - import hid - self.print_error("scanning devices...") + def _scan_devices_with_hid(self): + try: + import hid + except ImportError: + return [] + with self.hid_lock: hid_list = hid.enumerate(0, 0) - # First see what's connected that we know about devices = [] for d in hid_list: product_key = (d['vendor_id'], d['product_id']) @@ -530,18 +535,31 @@ class DeviceMgr(ThreadJob, PrintError): 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: - devices.extend(f()) - - # Now find out what was disconnected + 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: + if pair in pairs and client.has_usable_connection_with_device(): connected[client] = pair else: disconnected_ids.append(pair[1]) diff --git a/lib/qrscanner.py b/lib/qrscanner.py index 6212b4e68..56e0a18af 100644 --- a/lib/qrscanner.py +++ b/lib/qrscanner.py @@ -36,7 +36,7 @@ else: try: libzbar = ctypes.cdll.LoadLibrary(name) -except OSError: +except BaseException: libzbar = None diff --git a/lib/simple_config.py b/lib/simple_config.py index 23bad22ad..34c62d3f3 100644 --- a/lib/simple_config.py +++ b/lib/simple_config.py @@ -211,9 +211,14 @@ class SimpleConfig(PrintError): return path = os.path.join(self.path, "config") s = json.dumps(self.user_config, indent=4, sort_keys=True) - with open(path, "w") as f: - f.write(s) - os.chmod(path, stat.S_IREAD | stat.S_IWRITE) + 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.""" @@ -228,6 +233,10 @@ class SimpleConfig(PrintError): return path # default path + if not os.path.exists(self.path): + raise FileNotFoundError( + _('Electrum datadir does not exist. Was it deleted while running?') + '\n' + + _('Should be at {}').format(self.path)) dirpath = os.path.join(self.path, "wallets") if not os.path.exists(dirpath): if os.path.islink(dirpath): @@ -489,7 +498,7 @@ def read_user_config(path): if not os.path.exists(config_path): return {} try: - with open(config_path, "r") as f: + with open(config_path, "r", encoding='utf-8') as f: data = f.read() result = json.loads(data) except: diff --git a/lib/storage.py b/lib/storage.py index d87a339a8..59673d423 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib import base64 import zlib -from .util import PrintError, profiler, InvalidPassword +from .util import PrintError, profiler, InvalidPassword, WalletFileException from .plugins import run_hook, plugin_loaders from .keystore import bip44_derivation from . import bitcoin @@ -51,6 +51,8 @@ FINAL_SEED_VERSION = 16 # electrum >= 2.7 will set this to prevent 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)] @@ -75,7 +77,7 @@ class WalletStorage(PrintError): self.modified = False self.pubkey = None if self.file_exists(): - with open(self.path, "r") as f: + 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(): @@ -111,7 +113,7 @@ class WalletStorage(PrintError): if not self.manual_upgrades: if self.requires_split(): - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") if self.requires_upgrade(): self.upgrade() @@ -172,7 +174,7 @@ class WalletStorage(PrintError): elif v == STO_EV_XPUB_PW: return b'BIE2' else: - raise Exception('no encryption magic for version: %s' % v) + raise WalletFileException('no encryption magic for version: %s' % v) def decrypt(self, password): ec_key = self.get_key(password) @@ -255,7 +257,7 @@ class WalletStorage(PrintError): s = s.decode('utf8') temp_path = "%s.tmp.%s" % (self.path, os.getpid()) - with open(temp_path, "w") as f: + with open(temp_path, "w", encoding='utf-8') as f: f.write(s) f.flush() os.fsync(f.fileno()) @@ -318,7 +320,7 @@ class WalletStorage(PrintError): storage2.write() result.append(new_path) else: - raise BaseException("This wallet has multiple accounts and must be split") + raise WalletFileException("This wallet has multiple accounts and must be split") return result def requires_upgrade(self): @@ -417,7 +419,7 @@ class WalletStorage(PrintError): d['seed'] = seed self.put(key, d) else: - raise + 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) @@ -541,7 +543,7 @@ class WalletStorage(PrintError): else: addresses.append(addr) if addresses and keypairs: - raise BaseException('mixed addresses and privkeys') + raise WalletFileException('mixed addresses and privkeys') elif addresses: self.put('addresses', addresses) self.put('accounts', None) @@ -551,7 +553,7 @@ class WalletStorage(PrintError): self.put('keypairs', keypairs) self.put('accounts', None) else: - raise BaseException('no addresses or privkeys') + raise WalletFileException('no addresses or privkeys') def convert_account(self): if not self._is_upgrade_method_needed(0, 13): @@ -564,9 +566,9 @@ class WalletStorage(PrintError): if cur_version > max_version: return False elif cur_version < min_version: - raise BaseException( - ('storage upgrade: unexpected version %d (should be %d-%d)' - % (cur_version, min_version, max_version))) + raise WalletFileException( + 'storage upgrade: unexpected version {} (should be {}-{})' + .format(cur_version, min_version, max_version)) else: return True @@ -582,7 +584,9 @@ class WalletStorage(PrintError): 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 BaseException('This version of Electrum is too old to open this wallet') + 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: @@ -605,4 +609,4 @@ class WalletStorage(PrintError): 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 BaseException(msg) + raise WalletFileException(msg) diff --git a/lib/synchronizer.py b/lib/synchronizer.py index 4b81810db..b43fd221e 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -50,6 +50,8 @@ class Synchronizer(ThreadJob): self.requested_histories = {} self.requested_addrs = set() self.lock = Lock() + + self.initialized = False self.initialize() def parse_response(self, response): @@ -84,7 +86,7 @@ class Synchronizer(ThreadJob): return bh2u(hashlib.sha256(status.encode('ascii')).digest()) def on_address_status(self, response): - if self.wallet.synchronizer is None: + 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: @@ -100,14 +102,17 @@ class Synchronizer(ThreadJob): self.requested_addrs.remove(addr) def on_address_history(self, response): - if self.wallet.synchronizer is None: + 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] + server_status = self.requested_histories.get(addr) + if server_status is None: + self.print_error("receiving history (unsolicited)", addr, len(result)) + return self.print_error("receiving history", addr, len(result)) - server_status = self.requested_histories[addr] hashes = set(map(lambda item: item['tx_hash'], result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) # tx_fees @@ -131,7 +136,7 @@ class Synchronizer(ThreadJob): self.requested_histories.pop(addr) def tx_response(self, response): - if self.wallet.synchronizer is None: + 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: @@ -183,6 +188,7 @@ class Synchronizer(ThreadJob): 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.''' diff --git a/lib/tests/__init__.py b/lib/tests/__init__.py index e69de29bb..c6e8240fc 100644 --- a/lib/tests/__init__.py +++ b/lib/tests/__init__.py @@ -0,0 +1,16 @@ +import unittest + +from lib import constants + + +class TestCaseForTestnet(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + constants.set_testnet() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + constants.set_mainnet() diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py index a5f734357..d19ea6697 100644 --- a/lib/tests/test_bitcoin.py +++ b/lib/tests/test_bitcoin.py @@ -11,10 +11,13 @@ from lib.bitcoin import ( var_int, op_push, address_to_script, regenerate_key, verify_message, 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) + xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check) from lib.util import bfh from lib import constants +from . import TestCaseForTestnet + + try: import ecdsa except ImportError: @@ -164,17 +167,7 @@ class Test_bitcoin(unittest.TestCase): self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') -class Test_bitcoin_testnet(unittest.TestCase): - - @classmethod - def setUpClass(cls): - super().setUpClass() - constants.set_testnet() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - constants.set_mainnet() +class Test_bitcoin_testnet(TestCaseForTestnet): def test_address_to_script(self): # bech32 native segwit @@ -267,6 +260,79 @@ class Test_xprv_xpub(unittest.TestCase): self.assertFalse(is_bip32_derivation("")) self.assertFalse(is_bip32_derivation("m/q8462")) + 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(unittest.TestCase): diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py index cc800454c..0b51561bb 100644 --- a/lib/tests/test_transaction.py +++ b/lib/tests/test_transaction.py @@ -1,10 +1,9 @@ import unittest + from lib import transaction from lib.bitcoin import TYPE_ADDRESS - from lib.keystore import xpubkey_to_address - -from lib.util import bh2u +from lib.util import bh2u, bfh unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' @@ -167,450 +166,593 @@ class TestTransaction(unittest.TestCase): 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): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000') - self.assertEqual('dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000' + txid = 'dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_coinbase_to_p2pkh(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000') - self.assertEqual('4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000' + txid = '4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_segwit_coinbase_to_p2pk(self): - tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') - self.assertEqual('fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301', tx.txid()) + raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' + txid = 'fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_segwit_coinbase_to_p2pkh(self): - tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') - self.assertEqual('ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e', tx.txid()) + raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000' + txid = 'ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2pkh(self): - tx = transaction.Transaction('010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000') - self.assertEqual('90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9', tx.txid()) + raw_tx = '010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000' + txid = '90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2sh(self): - tx = transaction.Transaction('0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000') - self.assertEqual('172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5', tx.txid()) + raw_tx = '0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000' + txid = '172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pk_to_p2wpkh(self): - tx = transaction.Transaction('01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000') - self.assertEqual('ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d', tx.txid()) + raw_tx = '01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000' + txid = 'ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2pkh(self): - tx = transaction.Transaction('0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000') - self.assertEqual('24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce', tx.txid()) + raw_tx = '0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000' + txid = '24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2sh(self): - tx = transaction.Transaction('010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000') - self.assertEqual('155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc', tx.txid()) + raw_tx = '010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000' + txid = '155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2pkh_to_p2wpkh(self): - tx = transaction.Transaction('0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000') - self.assertEqual('ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c', tx.txid()) + raw_tx = '0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000' + txid = 'ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2pkh(self): - tx = transaction.Transaction('01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000') - self.assertEqual('17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986', tx.txid()) + raw_tx = '01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000' + txid = '17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2sh(self): - tx = transaction.Transaction('01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000') - self.assertEqual('ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7', tx.txid()) + raw_tx = '01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000' + txid = 'ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2sh_to_p2wpkh(self): - tx = transaction.Transaction('010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000') - self.assertEqual('6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3', tx.txid()) + raw_tx = '010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000' + txid = '6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2pkh(self): - tx = transaction.Transaction('0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000') - self.assertEqual('c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc', tx.txid()) + raw_tx = '0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000' + txid = 'c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2sh(self): - tx = transaction.Transaction('010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000') - self.assertEqual('390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9', tx.txid()) + raw_tx = '010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000' + txid = '390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_p2wpkh_to_p2wpkh(self): - tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000') - self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid()) + raw_tx = '010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000' + txid = '51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_input_p2wsh_p2sh_not_multisig(self): - tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000') - self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid()) + 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): - tx = transaction.Transaction('0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000') - self.assertEqual('f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d', tx.txid()) + 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) # 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): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = '23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0002(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0003(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0004(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0005(self): - tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') - self.assertEqual('f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86', tx.txid()) + raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000' + txid = 'f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0006(self): - tx = transaction.Transaction('01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000') - self.assertEqual('c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73', tx.txid()) + raw_tx = '01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000' + txid = 'c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0007(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000') - self.assertEqual('e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000' + txid = 'e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0008(self): - tx = transaction.Transaction('01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000') - self.assertEqual('f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb', tx.txid()) + raw_tx = '01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000' + txid = 'f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0009(self): - tx = transaction.Transaction('01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000') - self.assertEqual('b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5', tx.txid()) + raw_tx = '01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000' + txid = 'b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0010(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000') - self.assertEqual('99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000' + txid = '99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0011(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000') - self.assertEqual('ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000' + txid = 'ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0012(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000') - self.assertEqual('4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000' + txid = '4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0013(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000') - self.assertEqual('9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000' + txid = '9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0014(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000') - self.assertEqual('99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000' + txid = '99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0015(self): - tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000') - self.assertEqual('c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84', tx.txid()) + raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000' + txid = 'c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0016(self): - tx = transaction.Transaction('010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') - self.assertEqual('c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666', tx.txid()) + raw_tx = '010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000' + txid = 'c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0017(self): - tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') - self.assertEqual('a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f', tx.txid()) + raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000' + txid = 'a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0018(self): - tx = transaction.Transaction('010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000') - self.assertEqual('afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae', tx.txid()) + raw_tx = '010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000' + txid = 'afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0019(self): - tx = transaction.Transaction('01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000') - self.assertEqual('f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d', tx.txid()) + raw_tx = '01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000' + txid = 'f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0020(self): - tx = transaction.Transaction('0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000') - self.assertEqual('cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984', tx.txid()) + raw_tx = '0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000' + txid = 'cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0021(self): - tx = transaction.Transaction('01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000') - self.assertEqual('1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667', tx.txid()) + raw_tx = '01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000' + txid = '1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0022(self): - tx = transaction.Transaction('0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000') - self.assertEqual('018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff', tx.txid()) + raw_tx = '0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000' + txid = '018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0023(self): - tx = transaction.Transaction('0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000') - self.assertEqual('1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2', tx.txid()) + raw_tx = '0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000' + txid = '1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0024(self): - tx = transaction.Transaction('010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000') - self.assertEqual('1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a', tx.txid()) + raw_tx = '010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000' + txid = '1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0025(self): - tx = transaction.Transaction('0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000') - self.assertEqual('24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab', tx.txid()) + raw_tx = '0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000' + txid = '24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0026(self): - tx = transaction.Transaction('0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000') - self.assertEqual('9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7', tx.txid()) + raw_tx = '0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000' + txid = '9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0027(self): - tx = transaction.Transaction('01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000') - self.assertEqual('46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa', tx.txid()) + raw_tx = '01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000' + txid = '46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0028(self): - tx = transaction.Transaction('01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000') - self.assertEqual('8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e', tx.txid()) + raw_tx = '01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000' + txid = '8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0029(self): - tx = transaction.Transaction('01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000') - self.assertEqual('aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8', tx.txid()) + raw_tx = '01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000' + txid = 'aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0030(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000') - self.assertEqual('6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000' + txid = '6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0031(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000') - self.assertEqual('892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000' + txid = '892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0032(self): - tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000') - self.assertEqual('578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc', tx.txid()) + raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000' + txid = '578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0033(self): - tx = transaction.Transaction('0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f', tx.txid()) + raw_tx = '0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = '974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0034(self): - tx = transaction.Transaction('01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b', tx.txid()) + raw_tx = '01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0035(self): - tx = transaction.Transaction('01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9', tx.txid()) + raw_tx = '01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0036(self): - tx = transaction.Transaction('01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') - self.assertEqual('a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e', tx.txid()) + raw_tx = '01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000' + txid = 'a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0037(self): - tx = transaction.Transaction('0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000') - self.assertEqual('5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f', tx.txid()) + raw_tx = '0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000' + txid = '5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0038(self): - tx = transaction.Transaction('0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000') - self.assertEqual('ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea', tx.txid()) + raw_tx = '0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000' + txid = 'ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0039(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0040(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d') - self.assertEqual('abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d' + txid = 'abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0041(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d') - self.assertEqual('58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d' + txid = '58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0042(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff') - self.assertEqual('5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff' + txid = '5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0043(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') - self.assertEqual('25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000' + txid = '25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0044(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff') - self.assertEqual('1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff' + txid = '1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0045(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0046(self): - tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000') - self.assertEqual('f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd', tx.txid()) + raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000' + txid = 'f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0047(self): - tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000') - self.assertEqual('d193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1', tx.txid()) + raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000' + txid = 'd193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0048(self): - tx = transaction.Transaction('010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000') - self.assertEqual('50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89', tx.txid()) + raw_tx = '010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000' + txid = '50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0049(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') - self.assertEqual('e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000' + txid = 'e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0050(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000') - self.assertEqual('f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000' + txid = 'f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0051(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') - self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000' + txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0052(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000') - self.assertEqual('3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000' + txid = '3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0053(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000') - self.assertEqual('bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000' + txid = 'bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0054(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') - self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000' + txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0055(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000') - self.assertEqual('f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000' + txid = 'f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0056(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') - self.assertEqual('19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000' + txid = '19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0057(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000') - self.assertEqual('c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000' + txid = 'c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0058(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') - self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000' + txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0059(self): - tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') - self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) + raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000' + txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0060(self): - tx = transaction.Transaction('02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000') - self.assertEqual('4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7', tx.txid()) + raw_tx = '02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000' + txid = '4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0061(self): - tx = transaction.Transaction('0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000') - self.assertEqual('5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5', tx.txid()) + raw_tx = '0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000' + txid = '5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0062(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0063(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') - self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0064(self): - tx = transaction.Transaction('01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece', tx.txid()) + raw_tx = '01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0065(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') - self.assertEqual('5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + txid = '5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0066(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000') - self.assertEqual('07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000' + txid = '07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0067(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0068(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0069(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0070(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0071(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0072(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0073(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0074(self): - tx = transaction.Transaction('01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8', tx.txid()) + raw_tx = '01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0075(self): - tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb', tx.txid()) + raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0076(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0077(self): - tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) + raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0078(self): - tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000') - self.assertEqual('d93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97', tx.txid()) + raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000' + txid = 'd93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0079(self): - tx = transaction.Transaction('0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') - self.assertEqual('b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91', tx.txid()) + raw_tx = '0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000' + txid = 'b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0080(self): - tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000') - self.assertEqual('2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874', tx.txid()) + raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000' + txid = '2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0081(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000') - self.assertEqual('60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000' + txid = '60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0082(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0083(self): - tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') - self.assertEqual('f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862', tx.txid()) + raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000' + txid = 'f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0084(self): - tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000') - self.assertEqual('98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654', tx.txid()) + raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000' + txid = '98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0085(self): - tx = transaction.Transaction('01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000') - self.assertEqual('570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab', tx.txid()) + raw_tx = '01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000' + txid = '570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0086(self): - tx = transaction.Transaction('01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') - self.assertEqual('e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e', tx.txid()) + raw_tx = '01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000' + txid = 'e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0087(self): - tx = transaction.Transaction('0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') - self.assertEqual('b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d', tx.txid()) + raw_tx = '0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000' + txid = 'b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0088(self): - tx = transaction.Transaction('0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000') - self.assertEqual('27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac', tx.txid()) + raw_tx = '0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000' + txid = '27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0089(self): - tx = transaction.Transaction('010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000') - self.assertEqual('22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641', tx.txid()) + raw_tx = '010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000' + txid = '22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0090(self): - tx = transaction.Transaction('0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000') - self.assertEqual('2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5', tx.txid()) + raw_tx = '0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000' + txid = '2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0091(self): - tx = transaction.Transaction('01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000') - self.assertEqual('1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c', tx.txid()) + raw_tx = '01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000' + txid = '1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c' + self._run_naive_tests_on_tx(raw_tx, txid) def test_txid_bitcoin_core_0092(self): - tx = transaction.Transaction('010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000') - self.assertEqual('45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3', tx.txid()) + raw_tx = '010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000' + txid = '45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3' + self._run_naive_tests_on_tx(raw_tx, txid) # txns from Bitcoin Core ends <--- diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index a29690ea9..355688b84 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -5,36 +5,42 @@ import lib.bitcoin as bitcoin import lib.keystore as keystore import lib.storage as storage import lib.wallet as wallet +from lib import constants from plugins.trustedcoin import trustedcoin +from . import TestCaseForTestnet -# TODO passphrase/seed_extension -class TestWalletKeystoreAddressIntegrity(unittest.TestCase): - - gap_limit = 1 # make tests run faster - def _check_seeded_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertFalse(ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertTrue (ks.has_seed()) +class WalletIntegrityHelper: - def _check_xpub_keystore_sanity(self, ks): - self.assertTrue (ks.is_deterministic()) - self.assertTrue (ks.is_watching_only()) - self.assertFalse(ks.can_import()) - self.assertFalse(ks.has_seed()) + gap_limit = 1 # make tests run faster - def _create_standard_wallet(self, ks): + @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): store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store.put('keystore', ks.dump()) - store.put('gap_limit', self.gap_limit) + store.put('gap_limit', cls.gap_limit) w = wallet.Standard_Wallet(store) w.synchronize() return w - def _create_multisig_wallet(self, ks1, ks2, ks3=None): + @classmethod + def create_multisig_wallet(cls, ks1, ks2, ks3=None): """Creates a 2-of-2 or 2-of-3 multisig wallet.""" store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store.put('x%d/' % 1, ks1.dump()) @@ -45,11 +51,15 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): multisig_type = "%dof%d" % (2, 3) store.put('x%d/' % 3, ks3.dump()) store.put('wallet_type', multisig_type) - store.put('gap_limit', self.gap_limit) + store.put('gap_limit', cls.gap_limit) w = wallet.Multisig_Wallet(store) w.synchronize() return w + +# TODO passphrase/seed_extension +class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase): + @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' @@ -57,13 +67,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + 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 = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf') @@ -76,13 +86,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + 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 = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af') @@ -95,12 +105,12 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): ks = keystore.from_seed(seed_words, '', False) - self._check_seeded_keystore_sanity(ks) + WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks) self.assertTrue(isinstance(ks, keystore.Old_KeyStore)) self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo') @@ -130,10 +140,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): 'x2/': {'xpub': xpub2}}) xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id) ks3 = keystore.from_xpub(xpub3) - self._check_xpub_keystore_sanity(ks3) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3) self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2, ks3) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2, ks3) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV') @@ -151,7 +161,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD') self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2pkh') self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo') @@ -169,7 +179,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7') self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh-p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') @@ -188,7 +198,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE') self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs') - w = self._create_standard_wallet(ks) + w = WalletIntegrityHelper.create_standard_wallet(ks) self.assertEqual(w.txin_type, 'p2wpkh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') @@ -200,17 +210,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(bitcoin.seed_type(seed_words), 'standard') ks1 = keystore.from_seed(seed_words, '', True) - self._check_seeded_keystore_sanity(ks1) + 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') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN') @@ -222,17 +232,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(bitcoin.seed_type(seed_words), 'segwit') ks1 = keystore.from_seed(seed_words, '', True) - self._check_seeded_keystore_sanity(ks1) + 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') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2wsh') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc') @@ -251,10 +261,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact # der: m/45'/0 ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2sh') self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN') @@ -272,11 +282,35 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): # bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost # der: m/49'/0'/0' ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau') - self._check_xpub_keystore_sanity(ks2) + WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2) self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore)) - w = self._create_multisig_wallet(ks1, ks2) + w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2) self.assertEqual(w.txin_type, 'p2wsh-p2sh') self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6') + + +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) + self.assertEqual(w.txin_type, 'p2wsh-p2sh') + + self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt') + self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7') diff --git a/lib/transaction.py b/lib/transaction.py index 4ee57d4e1..f534acd62 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -229,10 +229,10 @@ opcodes = Enumeration("Opcodes", [ "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", "OP_CHECKMULTISIGVERIFY", - ("OP_SINGLEBYTE_END", 0xF0), - ("OP_DOUBLEBYTE_BEGIN", 0xF000), - "OP_PUBKEY", "OP_PUBKEYHASH", - ("OP_INVALIDOPCODE", 0xFFFF), + ("OP_NOP1", 0xB0), + ("OP_CHECKLOCKTIMEVERIFY", 0xB1), ("OP_CHECKSEQUENCEVERIFY", 0xB2), + "OP_NOP4", "OP_NOP5", "OP_NOP6", "OP_NOP7", "OP_NOP8", "OP_NOP9", "OP_NOP10", + ("OP_INVALIDOPCODE", 0xFF), ]) @@ -242,10 +242,6 @@ def script_GetOp(_bytes): vch = None opcode = _bytes[i] i += 1 - if opcode >= opcodes.OP_SINGLEBYTE_END: - opcode <<= 8 - opcode |= _bytes[i] - i += 1 if opcode <= opcodes.OP_PUSHDATA4: nSize = opcode @@ -402,7 +398,8 @@ def parse_redeemScript(s): redeemScript = multisig_script(pubkeys, m) return m, n, x_pubkeys, pubkeys, redeemScript -def get_address_from_output_script(_bytes): + +def get_address_from_output_script(_bytes, *, net=None): decoded = [x for x in script_GetOp(_bytes)] # The Genesis Block, self-payments, and pay-by-IP-address payments look like: @@ -415,17 +412,19 @@ def get_address_from_output_script(_bytes): # 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]) + 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]) + return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net) # segwit address - match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4 ] - if match_decoded(decoded, match): - return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1]) + 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) @@ -439,16 +438,16 @@ def parse_input(vds): d['prevout_hash'] = prevout_hash d['prevout_n'] = prevout_n d['sequence'] = sequence + d['x_pubkeys'] = [] + d['pubkeys'] = [] + d['signatures'] = {} + d['address'] = None + d['num_sig'] = 0 if prevout_hash == '00'*32: d['type'] = 'coinbase' d['scriptSig'] = bh2u(scriptSig) else: - d['x_pubkeys'] = [] - d['pubkeys'] = [] - d['signatures'] = {} - d['address'] = None d['type'] = 'unknown' - d['num_sig'] = 0 if scriptSig: d['scriptSig'] = bh2u(scriptSig) try: @@ -607,6 +606,8 @@ class Transaction: @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: @@ -707,6 +708,8 @@ class Transaction: 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) @@ -728,10 +731,12 @@ class Transaction: @classmethod def serialize_witness(self, txin, estimate_size=False): - add_w = lambda x: var_int(len(x)//2) + x if not self.is_segwit_input(txin): return '00' + if txin['type'] == 'coinbase': + return txin['witness'] pubkeys, sig_list = self.get_siglist(txin, estimate_size) + add_w = lambda x: var_int(len(x) // 2) + x if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0]) elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']: @@ -790,7 +795,9 @@ class Transaction: return script @classmethod - def is_txin_complete(self, txin): + def is_txin_complete(cls, txin): + if txin['type'] == 'coinbase': + return True num_sig = txin.get('num_sig', 1) x_signatures = txin['signatures'] signatures = list(filter(None, x_signatures)) diff --git a/lib/util.py b/lib/util.py index 1714c78d4..68067c435 100644 --- a/lib/util.py +++ b/lib/util.py @@ -84,6 +84,12 @@ class TimeoutException(Exception): 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): @@ -412,7 +418,7 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa return 'unknown' x = int(x) # Some callers pass Decimal scale_factor = pow (10, decimal_point) - integer_part = "{:n}".format(int(abs(x) / scale_factor)) + integer_part = "{:d}".format(int(abs(x) / scale_factor)) if x < 0: integer_part = '-' + integer_part elif is_diff: @@ -730,10 +736,6 @@ class SocketPipe: print_error("SSLError:", e) time.sleep(0.1) continue - except OSError as e: - print_error("OSError", e) - time.sleep(0.1) - continue class QueuePipe: @@ -804,7 +806,7 @@ def versiontuple(v): def import_meta(path, validater, load_meta): try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: d = validater(json.loads(f.read())) load_meta(d) #backwards compatibility for JSONDecodeError @@ -818,7 +820,7 @@ def import_meta(path, validater, load_meta): def export_meta(meta, fileName): try: - with open(fileName, 'w+') as f: + 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) diff --git a/lib/version.py b/lib/version.py index 1d653e591..dbce6c2a5 100644 --- a/lib/version.py +++ b/lib/version.py @@ -1,4 +1,4 @@ -ELECTRUM_VERSION = '3.1' # version of the client package +ELECTRUM_VERSION = '3.1.2' # version of the client package PROTOCOL_VERSION = '1.2' # protocol version requested # The hash of the mnemonic seed must begin with this diff --git a/lib/wallet.py b/lib/wallet.py index 28597b851..9c4cda55f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -39,12 +39,14 @@ from functools import partial from collections import defaultdict from numbers import Number from decimal import Decimal +import itertools import sys from .i18n import _ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, - format_satoshis, NoDynamicFeeEstimates, TimeoutException) + format_satoshis, NoDynamicFeeEstimates, TimeoutException, + WalletFileException, BitcoinException) from .bitcoin import * from .version import * @@ -131,6 +133,7 @@ def sweep_preparations(privkeys, network, imax=100): find_utxos_for_privkey('p2pk', privkey, compressed) if not inputs: raise BaseException(_('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 @@ -186,7 +189,12 @@ class Abstract_Wallet(PrintError): self.synchronizer = None self.verifier = None - self.gap_limit_for_change = 6 # constant + self.gap_limit_for_change = 6 # constant + + # 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() + # saved fields self.use_change = storage.get('use_change', True) self.multiple_change = storage.get('multiple_change', False) @@ -200,6 +208,8 @@ class Abstract_Wallet(PrintError): self.load_transactions() self.build_spent_outpoints() + self.test_addresses_sanity() + # load requests self.receive_requests = self.storage.get('payment_requests', {}) @@ -215,10 +225,6 @@ class Abstract_Wallet(PrintError): # wallet.up_to_date is true when the wallet is synchronized (stronger requirement) self.up_to_date = False - # 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() - self.check_history() # save wallet type the first time @@ -229,6 +235,8 @@ class Abstract_Wallet(PrintError): self.invoices = InvoiceStore(self.storage) self.contacts = Contacts(self.storage) + self.coin_price_cache = {} + def diagnostic_name(self): return self.basename() @@ -247,6 +255,7 @@ class Abstract_Wallet(PrintError): self.pruned_txo = self.storage.get('pruned_txo', {}) tx_list = self.storage.get('transactions', {}) self.transactions = {} + self._history_local = {} # address -> set(txid) for tx_hash, raw in tx_list.items(): tx = Transaction(raw) self.transactions[tx_hash] = tx @@ -254,6 +263,8 @@ class Abstract_Wallet(PrintError): and (tx_hash not in self.pruned_txo.values()): self.print_error("removing unreferenced tx", tx_hash) self.transactions.pop(tx_hash) + else: + self._add_tx_to_local_history(tx_hash) @profiler def save_transactions(self, write=False): @@ -327,6 +338,12 @@ class Abstract_Wallet(PrintError): 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 bitcoin addresses.') + def synchronize(self): pass @@ -657,8 +674,9 @@ class Abstract_Wallet(PrintError): def get_addr_balance(self, address): 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 > self.get_local_height(): + if is_cb and tx_height + COINBASE_MATURITY > local_height: x += v elif tx_height > 0: c += v @@ -720,12 +738,30 @@ class Abstract_Wallet(PrintError): # 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: - for tx_hash in self.transactions: - if addr in self.txi.get(tx_hash, []) or addr in self.txo.get(tx_hash, []): - tx_height = self.get_tx_height(tx_hash)[0] - h.append((tx_hash, tx_height)) + related_txns = self._history_local.get(addr, set()) + for tx_hash in related_txns: + tx_height = self.get_tx_height(tx_hash)[0] + h.append((tx_hash, tx_height)) return h + 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 get_txin_address(self, txi): addr = txi.get('address') if addr != "(pubkey)": @@ -776,6 +812,9 @@ class Abstract_Wallet(PrintError): return conflicting_txns def add_transaction(self, tx_hash, tx): + 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: @@ -861,6 +900,9 @@ class Abstract_Wallet(PrintError): if dd.get(addr) is None: dd[addr] = [] dd[addr].append((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 @@ -877,9 +919,11 @@ class Abstract_Wallet(PrintError): # undo spent_outpoints that are in pruned_txo for ser, hh in list(self.pruned_txo.items()): if hh == tx_hash: - self.spent_outpoints.pop(ser) + self.spent_outpoints.pop(ser, None) self.pruned_txo.pop(ser) + self._remove_tx_from_local_history(tx_hash) + # add tx to pruned_txo, and undo the txi addition for next_tx, dd in self.txi.items(): for addr, l in list(dd.items()): @@ -987,11 +1031,11 @@ class Abstract_Wallet(PrintError): 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 = [] - capital_gains = 0 income = 0 expenditures = 0 - fiat_income = 0 - fiat_expenditures = 0 + capital_gains = Decimal(0) + fiat_income = Decimal(0) + fiat_expenditures = Decimal(0) h = self.get_history(domain) for tx_hash, height, conf, timestamp, value, balance in h: if from_timestamp and (timestamp or time.time()) < from_timestamp: @@ -1032,7 +1076,7 @@ class Abstract_Wallet(PrintError): else: income += value # fiat computations - if fx is not None: + if fx and fx.is_enabled(): date = timestamp_to_datetime(timestamp) fiat_value = self.get_fiat_value(tx_hash, fx.ccy) fiat_default = fiat_value is None @@ -1069,7 +1113,7 @@ class Abstract_Wallet(PrintError): 'income': Satoshis(income), 'expenditures': Satoshis(expenditures) } - if fx: + 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) @@ -1153,7 +1197,7 @@ class Abstract_Wallet(PrintError): _type, data, value = o if _type == TYPE_ADDRESS: if not is_address(data): - raise BaseException("Invalid bitcoin address:" + data) + raise BaseException("Invalid bitcoin address: {}".format(data)) if value == '!': if i_max is not None: raise BaseException("More than one output set to spend max") @@ -1326,7 +1370,7 @@ class Abstract_Wallet(PrintError): def bump_fee(self, tx, delta): if tx.is_final(): - raise BaseException(_("Cannot bump fee: transaction is final")) + raise BaseException(_('Cannot bump fee') + ': ' + _('transaction is final')) inputs = copy.deepcopy(tx.inputs()) outputs = copy.deepcopy(tx.outputs()) for txin in inputs: @@ -1357,7 +1401,7 @@ class Abstract_Wallet(PrintError): if delta > 0: continue if delta > 0: - raise BaseException(_('Cannot bump fee: could not find suitable outputs')) + raise BaseException(_('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() @@ -1429,7 +1473,7 @@ class Abstract_Wallet(PrintError): xpubs = self.get_master_public_keys() for txout in tx.outputs(): _type, addr, amount = txout - if self.is_change(addr): + if self.is_mine(addr): index = self.get_address_index(addr) pubkeys = self.get_public_keys(addr) # sort xpubs using the order of pubkeys @@ -1443,8 +1487,8 @@ class Abstract_Wallet(PrintError): # 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 - for k in self.get_keystores(): + # 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) @@ -1516,7 +1560,10 @@ class Abstract_Wallet(PrintError): baseurl = 'file://' + rdir rewrite = config.get('url_rewrite') if rewrite: - baseurl = baseurl.replace(*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 @@ -1575,6 +1622,11 @@ class Abstract_Wallet(PrintError): 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 @@ -1596,7 +1648,7 @@ class Abstract_Wallet(PrintError): f.write(pr.SerializeToString()) # reload req = self.get_payment_request(addr, config) - with open(os.path.join(path, key + '.json'), 'w') as f: + with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f: f.write(json.dumps(req)) return req @@ -1615,13 +1667,14 @@ class Abstract_Wallet(PrintError): return True def get_sorted_requests(self, config): - def f(x): + def f(addr): try: - addr = x.get('address') - return self.get_address_index(addr) or addr + return self.get_address_index(addr) except: - return addr - return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f) + 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() @@ -1724,11 +1777,12 @@ class Abstract_Wallet(PrintError): def txin_value(self, txin): txid = txin['prevout_hash'] prev_n = txin['prevout_n'] - for address, d in self.txo[txid].items(): + for address, d in self.txo.get(txid, {}).items(): for n, v, cb in d: if n == prev_n: return v - raise BaseException('unknown txin value') + # may occur if wallet is not synchronized + return None def price_at_timestamp(self, txid, price_func): height, conf, timestamp = self.get_tx_height(txid) @@ -1757,8 +1811,16 @@ class Abstract_Wallet(PrintError): 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, {}) != {}: - return self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) + 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: @@ -1767,6 +1829,7 @@ class Abstract_Wallet(PrintError): p = self.price_at_timestamp(txid, price_func) return p * txin_value/Decimal(COIN) + class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore @@ -1903,8 +1966,18 @@ class Imported_Wallet(Simple_Wallet): pubkey = self.get_public_key(address) self.addresses.pop(address) if pubkey: - self.keystore.delete_imported_key(pubkey) - self.save_keystore() + # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key) + for txin_type in bitcoin.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.storage.put('addresses', self.addresses) self.storage.write() @@ -1919,14 +1992,15 @@ class Imported_Wallet(Simple_Wallet): try: txin_type, pubkey = self.keystore.import_privkey(sec, pw) except Exception: - raise BaseException('Invalid private key', sec) + 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 BaseException('Cannot use redeem script with', txin_type, sec) + 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 BaseException('Redeem script required for', txin_type, sec) + 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) @@ -2280,5 +2354,5 @@ class Wallet(object): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] - raise RuntimeError("Unknown wallet type: " + wallet_type) + raise RuntimeError("Unknown wallet type: " + str(wallet_type)) diff --git a/lib/websockets.py b/lib/websockets.py index 415556b33..587119f97 100644 --- a/lib/websockets.py +++ b/lib/websockets.py @@ -64,7 +64,7 @@ class WsClientThread(util.DaemonThread): # 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) as f: + with open(n, encoding='utf-8') as f: s = f.read() d = json.loads(s) addr = d.get('address') diff --git a/lib/x509.py b/lib/x509.py index 52dd45f54..e4b94d404 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -284,7 +284,7 @@ class X509(object): return self.AKI if self.AKI else repr(self.issuer) def get_common_name(self): - return self.subject.get('2.5.4.3', 'unknown').decode() + return self.subject.get('2.5.4.3', b'unknown').decode() def get_signature(self): return self.cert_sig_algo, self.signature, self.data @@ -313,7 +313,7 @@ def load_certificates(ca_path): ca_list = {} ca_keyID = {} # ca_path = '/tmp/tmp.txt' - with open(ca_path, 'r') as f: + with open(ca_path, 'r', encoding='utf-8') as f: s = f.read() bList = pem.dePemList(s, "CERTIFICATE") for b in bList: diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py index 52d6a8f34..e00edd3ba 100644 --- a/plugins/cosigner_pool/qt.py +++ b/plugins/cosigner_pool/qt.py @@ -43,9 +43,7 @@ import sys import traceback -PORT = 12344 -HOST = 'cosigner.electrum.org' -server = ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) +server = ServerProxy('https://cosigner.electrum.org/', allow_none=True) class Listener(util.DaemonThread): @@ -175,7 +173,8 @@ class Plugin(BasePlugin): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue - message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') + raw_tx_bytes = bfh(str(tx)) + message = bitcoin.encrypt_message(raw_tx_bytes, bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index 623290543..334c2f679 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -82,6 +82,13 @@ class DigitalBitbox_Client(): 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(b'{"xpub": "%s"}' % bip32_path.encode('utf8')) @@ -106,7 +113,7 @@ class DigitalBitbox_Client(): 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.') + raise Exception(_('Device communication error. Please unplug and replug your Digital Bitbox.')) if reply['ping'] == 'password': return True return False @@ -124,9 +131,11 @@ class DigitalBitbox_Client(): if password is None: return None if len(password) < 4: - msg = _("Password must have at least 4 characters.\r\n\r\nEnter password:") + msg = _("Password must have at least 4 characters.") \ + + "\n\n" + _("Enter password:") elif len(password) > 64: - msg = _("Password must have less than 64 characters.\r\n\r\nEnter password:") + msg = _("Password must have less than 64 characters.") \ + + "\n\n" + _("Enter password:") else: return password.encode('utf8') @@ -137,9 +146,11 @@ class DigitalBitbox_Client(): if password is None: return False if len(password) < 4: - msg = _("Password must have at least 4 characters.\r\n\r\nEnter password:") + msg = _("Password must have at least 4 characters.") + \ + "\n\n" + _("Enter password:") elif len(password) > 64: - msg = _("Password must have less than 64 characters.\r\n\r\nEnter password:") + msg = _("Password must have less than 64 characters.") + \ + "\n\n" + _("Enter password:") else: self.password = password.encode('utf8') return True @@ -150,10 +161,11 @@ class DigitalBitbox_Client(): 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.\r\n\r\n REMEMBER THE PASSWORD!\r\n\r\n" \ - "You cannot access your coins or a backup without the password.\r\n" \ - "A backup is saved automatically when generating a new 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: @@ -163,19 +175,19 @@ class DigitalBitbox_Client(): msg = _("Enter your Digital Bitbox password:") while self.password is None: if not self.password_dialog(msg): - return False + 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.\r\n\r\n" \ - + reply['error']['message'] + "\r\n\r\n" \ - "Enter your Digital Bitbox password:") + msg = _("Incorrect password entered.") + "\n\n" + \ + reply['error']['message'] + "\n\n" + \ + _("Enter your Digital Bitbox password:") else: # Should never occur - msg = _("Unexpected error occurred.\r\n\r\n" \ - + reply['error']['message'] + "\r\n\r\n" \ - "Enter your Digital Bitbox password:") + 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: @@ -191,7 +203,7 @@ class DigitalBitbox_Client(): def recover_or_erase_dialog(self): - msg = _("The Digital Bitbox is already seeded. Choose an option:\n") + 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)")), @@ -208,13 +220,13 @@ class DigitalBitbox_Client(): return else: if self.hid_send_encrypt(b'{"device":"info"}')['device']['lock']: - raise Exception("Full 2FA enabled. This is not supported yet.") + 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") + msg = _("Choose how to initialize your Digital Bitbox:") + "\n" choices = [ (_("Generate a new random wallet")), (_("Load a wallet from the micro SD card")) @@ -280,9 +292,9 @@ class DigitalBitbox_Client(): def dbb_erase(self): - self.handler.show_message(_("Are you sure you want to erase the Digital Bitbox?\r\n\r\n" \ - "To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the light or wait for the timeout.")) + 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: @@ -305,9 +317,9 @@ class DigitalBitbox_Client(): raise Exception('Canceled by user') key = self.stretch_key(key) if show_msg: - self.handler.show_message(_("Loading backup...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the light or wait for the timeout.")) + 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 = b'{"seed":{"source": "backup", "key": "%s", "filename": "%s"}}' % (key, backups['backup'][f].encode('utf8')) hid_reply = self.hid_send_encrypt(msg) self.handler.finished() @@ -441,12 +453,12 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): - raise Exception("Could not sign message.") + raise Exception(_("Could not sign message.")) reply = dbb_client.hid_send_encrypt(msg) - self.handler.show_message(_("Signing message ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.")) + 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() @@ -454,7 +466,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): raise Exception(reply['error']['message']) if 'sign' not in reply: - raise Exception("Could not sign message.") + raise Exception(_("Could not sign message.")) if 'recid' in reply['sign'][0]: # firmware > v2.1.1 @@ -463,7 +475,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): pk = point_to_ser(pk.pubkey.point, compressed) addr = public_key_to_p2pkh(pk) if verify_message(addr, sig, message) is False: - raise Exception("Could not sign message") + raise Exception(_("Could not sign message")) elif 'pubkey' in reply['sign'][0]: # firmware <= v2.1.1 for i in range(4): @@ -475,7 +487,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): except Exception: continue else: - raise Exception("Could not sign message") + raise Exception(_("Could not sign message")) except BaseException as e: @@ -576,14 +588,14 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): self.plugin.comserver_post_notification(reply) if steps > 1: - self.handler.show_message(_("Signing large transaction. Please be patient ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds. " \ - "(Touch " + str(step + 1) + " of " + str(int(steps)) + ")\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.\r\n\r\n")) + 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 ...\r\n\r\n" \ - "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\n" \ - "To cancel, briefly touch the blinking light or wait for the timeout.")) + 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) @@ -719,3 +731,13 @@ class DigitalBitboxPlugin(HW_PluginBase): if client is not None: client.check_device_dialog() return client + + def show_address(self, wallet, keystore, address): + 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/plugins/digitalbitbox/qt.py b/plugins/digitalbitbox/qt.py index 8930d6244..1978ce8cc 100644 --- a/plugins/digitalbitbox/qt.py +++ b/plugins/digitalbitbox/qt.py @@ -1,3 +1,5 @@ +from functools import partial + from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from .digitalbitbox import DigitalBitboxPlugin @@ -30,14 +32,7 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase): if len(addrs) == 1: def show_address(): - change, index = wallet.get_address_index(addrs[0]) - 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) + keystore.thread.add(partial(self.show_address, wallet, keystore, addrs[0])) menu.addAction(_("Show on {}").format(self.device), show_address) diff --git a/plugins/email_requests/qt.py b/plugins/email_requests/qt.py index a4facccf3..ed9293c2b 100644 --- a/plugins/email_requests/qt.py +++ b/plugins/email_requests/qt.py @@ -22,7 +22,7 @@ # 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 @@ -45,11 +45,12 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit, from electrum.plugins import BasePlugin, hook from electrum.paymentrequest import PaymentRequest from electrum.i18n import _ +from electrum.util import PrintError from electrum_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, WindowModalDialog, get_parent_main_window) -class Processor(threading.Thread): +class Processor(threading.Thread, PrintError): polling_interval = 5*60 def __init__(self, imap_server, username, password, callback): @@ -59,6 +60,8 @@ class Processor(threading.Thread): self.password = password self.imap_server = imap_server self.on_receive = callback + self.M = None + self.connect_wait = 100 # ms, between failed connection attempts def poll(self): try: @@ -80,13 +83,18 @@ class Processor(threading.Thread): self.on_receive(pr_str) def run(self): - self.M = imaplib.IMAP4_SSL(self.imap_server) - self.M.login(self.username, self.password) while True: - self.poll() - time.sleep(self.polling_interval) - self.M.close() - self.M.logout() + try: + self.M = imaplib.IMAP4_SSL(self.imap_server) + self.M.login(self.username, self.password) + except BaseException as e: + self.print_error(e) + self.connect_wait *= 2 + # Reconnect when host changes + while self.M and self.M.host == self.imap_server: + self.poll() + time.sleep(self.polling_interval) + time.sleep(random.randint(0, self.connect_wait)) def send(self, recipient, message, payment_request): msg = MIMEMultipart() @@ -98,10 +106,13 @@ class Processor(threading.Thread): encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"') msg.attach(part) - 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() + 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): @@ -225,3 +236,27 @@ class Plugin(BasePlugin): 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/plugins/hw_wallet/cmdline.py b/plugins/hw_wallet/cmdline.py index 999f82994..6cd27a001 100644 --- a/plugins/hw_wallet/cmdline.py +++ b/plugins/hw_wallet/cmdline.py @@ -32,6 +32,9 @@ class CmdLineHandler: def show_message(self, msg, on_cancel=None): print_msg(msg) + def show_error(self, msg): + print_msg(msg) + def update_status(self, b): print_error('trezor status', b) diff --git a/plugins/keepkey/clientbase.py b/plugins/keepkey/clientbase.py index 6b33c9d43..9e1875573 100644 --- a/plugins/keepkey/clientbase.py +++ b/plugins/keepkey/clientbase.py @@ -50,6 +50,9 @@ class GuiMixin(object): 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) @@ -66,7 +69,13 @@ class GuiMixin(object): if passphrase is None: return self.proto.Cancel() passphrase = bip39_normalize_passphrase(passphrase) - return self.proto.PassphraseAck(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 @@ -110,6 +119,14 @@ class KeepKeyClientBase(GuiMixin, PrintError): 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() diff --git a/plugins/keepkey/plugin.py b/plugins/keepkey/plugin.py index a81a328bd..fa857c40d 100644 --- a/plugins/keepkey/plugin.py +++ b/plugins/keepkey/plugin.py @@ -1,5 +1,3 @@ -import threading - from binascii import hexlify, unhexlify from electrum.util import bfh, bh2u @@ -72,8 +70,6 @@ class KeepKeyCompatiblePlugin(HW_PluginBase): def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) - self.main_thread = threading.current_thread() - # FIXME: move to base class when Ledger is fixed if self.libraries_available: self.device_manager().register_devices(self.DEVICE_IDS) @@ -303,56 +299,83 @@ class KeepKeyCompatiblePlugin(HW_PluginBase): return inputs def tx_outputs(self, derivation, tx, segwit=False): + + def create_output_by_derivation(info): + index, xpubs, m = info + 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 = address[2:] + 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 BaseException('addrtype: ' + str(addrtype)) + txoutputtype.address = address + return txoutputtype + + def is_any_output_on_change_branch(): + for _type, address, amount in tx.outputs(): + info = tx.output_info.get(address) + if info is not None: + index, xpubs, m = info + if index[0] == 1: + return True + return False + outputs = [] has_change = False + any_output_on_change_branch = is_any_output_on_change_branch() for _type, address, amount in tx.outputs(): + use_create_by_derivation = False + info = tx.output_info.get(address) if info is not None and not has_change: - has_change = True # no more than one change address - addrtype, hash_160 = b58_address_to_hash160(address) index, xpubs, m = info - 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) + 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(info) else: - txoutputtype = self.types.TxOutputType() - txoutputtype.amount = amount - if _type == TYPE_SCRIPT: - txoutputtype.script_type = self.types.PAYTOOPRETURN - txoutputtype.op_return_data = address[2:] - 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 BaseException('addrtype: ' + str(addrtype)) - txoutputtype.address = address - + txoutputtype = create_output_by_address() outputs.append(txoutputtype) return outputs diff --git a/plugins/keepkey/qt_generic.py b/plugins/keepkey/qt_generic.py index a66e8f3d2..7cdc50769 100644 --- a/plugins/keepkey/qt_generic.py +++ b/plugins/keepkey/qt_generic.py @@ -250,7 +250,7 @@ class QtPlugin(QtPluginBase): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) diff --git a/plugins/labels/labels.py b/plugins/labels/labels.py index 3e778d544..b175e9e64 100644 --- a/plugins/labels/labels.py +++ b/plugins/labels/labels.py @@ -16,7 +16,7 @@ class LabelsPlugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) - self.target_host = 'labels.bauerj.eu' + self.target_host = 'labels.electrum.org' self.wallets = {} def encode(self, wallet, msg): @@ -45,7 +45,7 @@ class LabelsPlugin(BasePlugin): @hook def set_label(self, wallet, item, label): - if not wallet in self.wallets: + if wallet not in self.wallets: return if not item: return @@ -55,7 +55,7 @@ class LabelsPlugin(BasePlugin): "walletNonce": nonce, "externalId": self.encode(wallet, item), "encryptedLabel": self.encode(wallet, label)} - t = threading.Thread(target=self.do_request, + t = threading.Thread(target=self.do_request_safe, args=["POST", "/label", False, bundle]) t.setDaemon(True) t.start() @@ -78,8 +78,18 @@ class LabelsPlugin(BasePlugin): raise BaseException(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_id = self.wallets[wallet][2] + 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)} @@ -95,42 +105,47 @@ class LabelsPlugin(BasePlugin): self.do_request("POST", "/labels", True, bundle) def pull_thread(self, wallet, force): - wallet_id = self.wallets[wallet][2] + 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: - 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) - - except Exception as e: - traceback.print_exc(file=sys.stderr) - self.print_error("could not retrieve labels") + 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) @@ -144,7 +159,7 @@ class LabelsPlugin(BasePlugin): 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, args=(wallet, False)) + t = threading.Thread(target=self.pull_thread_safe, args=(wallet, False)) t.setDaemon(True) t.start() diff --git a/plugins/labels/qt.py b/plugins/labels/qt.py index 5fa9ee3a0..c608ccf78 100644 --- a/plugins/labels/qt.py +++ b/plugins/labels/qt.py @@ -1,4 +1,6 @@ from functools import partial +import traceback +import sys from PyQt5.QtGui import * from PyQt5.QtCore import * @@ -37,10 +39,12 @@ class Plugin(LabelsPlugin): hbox.addWidget(QLabel("Label sync options:")) upload = ThreadedButton("Force upload", partial(self.push_thread, wallet), - partial(self.done_processing, d)) + 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, d)) + partial(self.done_processing_success, d), + partial(self.done_processing_error, d)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) @@ -54,13 +58,20 @@ class Plugin(LabelsPlugin): def on_pulled(self, wallet): self.obj.labels_changed_signal.emit(wallet) - def done_processing(self, dialog, result): + 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 on_new_window(self, window): + 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(window.wallet) + self.start_wallet(wallet) @hook def on_close_window(self, window): diff --git a/plugins/ledger/auth2fa.py b/plugins/ledger/auth2fa.py index add619a82..bdb87f1ea 100644 --- a/plugins/ledger/auth2fa.py +++ b/plugins/ledger/auth2fa.py @@ -1,16 +1,24 @@ +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_gui.qt.util import * from electrum.util import print_msg - -import os, hashlib, websocket, logging, json, copy +from electrum import constants, bitcoin from electrum_gui.qt.qrcodewidget import QRCodeWidget -from btchip.btchip import * + DEBUG = False @@ -37,7 +45,7 @@ class LedgerAuthDialog(QDialog): self.handler = handler self.txdata = data self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else '' - self.setMinimumWidth(600) + 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 @@ -110,17 +118,23 @@ class LedgerAuthDialog(QDialog): 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; }") + 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(120) + 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'] - addr = addr[:i] + '' + addr[i:i+1] + '' + addr[i+1:] - self.addrtext.setHtml(str(addr)) + 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")) @@ -179,8 +193,8 @@ class LedgerAuthDialog(QDialog): 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(200) - + self.setMaximumHeight(400) + def do_pairing(self): rng = os.urandom(16) pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8') @@ -338,11 +352,7 @@ class LedgerWebSocket(QThread): ws.send( self.txreq ) debug_msg("Req Sent", self.txreq) + def debug_msg(*args): if DEBUG: print_msg(*args) - - - - - diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index 3d34bc9da..36f51142c 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -57,6 +57,13 @@ class Ledger_Client(): 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 + def test_pin_unlocked(func): """Function decorator to test the Ledger for being unlocked, and if not, raise a human-readable exception. @@ -180,8 +187,8 @@ class Ledger_Client(): try: self.perform_hw1_preflight() except BTChipException as e: - if (e.sw == 0x6d00): - raise BaseException("Device not in Bitcoin mode") + if (e.sw == 0x6d00 or e.sw == 0x6700): + raise BaseException(_("Device not in Bitcoin mode")) from e raise e self.preflightDone = True @@ -229,6 +236,16 @@ class Ledger_KeyStore(Hardware_KeyStore): 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) @@ -239,8 +256,8 @@ class Ledger_KeyStore(Hardware_KeyStore): def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device)) + @set_and_unset_signing def sign_message(self, sequence, message, password): - self.signing = True message = message.encode('utf8') message_hash = hashlib.sha256(message).hexdigest().upper() # prompt for the PIN before displaying the dialog if necessary @@ -259,16 +276,17 @@ class Ledger_KeyStore(Hardware_KeyStore): 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'' else: self.give_error(e, True) except UserWarning: self.handler.show_error(_('Cancelled by user')) - return '' + return b'' except Exception as e: self.give_error(e, True) finally: self.handler.finished() - self.signing = False # Parse the ASN.1 signature rLength = signature[3] r = signature[4 : 4 + rLength] @@ -281,12 +299,11 @@ class Ledger_KeyStore(Hardware_KeyStore): # And convert it return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s - + @set_and_unset_signing def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() - self.signing = True inputs = [] inputsPaths = [] pubKeys = [] @@ -360,7 +377,8 @@ class Ledger_KeyStore(Hardware_KeyStore): for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) - if (info is not None) and (len(tx.outputs()) != 1): + if (info is not None) and len(tx.outputs()) > 1 \ + and info[0][0] == 1: # "is on 'change' branch" index, xpubs, m = info changePath = self.get_derivation()[2:] + "/%d/%d"%index changeAmount = amount @@ -400,7 +418,12 @@ class Ledger_KeyStore(Hardware_KeyStore): if segwitTransaction: self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) - outputData = self.get_client().finalizeInputFull(txOutput) + 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']: @@ -423,7 +446,12 @@ class Ledger_KeyStore(Hardware_KeyStore): while inputIndex < len(inputs): self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) - outputData = self.get_client().finalizeInputFull(txOutput) + 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'] @@ -446,6 +474,12 @@ class Ledger_KeyStore(Hardware_KeyStore): except UserWarning: self.handler.show_error(_('Cancelled by user')) return + except BTChipException as e: + if e.sw == 0x6985: # cancelled by user + return + 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) @@ -456,10 +490,9 @@ class Ledger_KeyStore(Hardware_KeyStore): signingPos = inputs[i][4] txin['signatures'][signingPos] = bh2u(signatures[i]) tx.raw = tx.serialize() - self.signing = False + @set_and_unset_signing def show_address(self, sequence, txin_type): - self.signing = True client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.handler.show_message(_("Showing address ...")) @@ -478,7 +511,6 @@ class Ledger_KeyStore(Hardware_KeyStore): self.handler.show_error(e) finally: self.handler.finished() - self.signing = False class LedgerPlugin(HW_PluginBase): libraries_available = BTCHIP @@ -499,17 +531,17 @@ class LedgerPlugin(HW_PluginBase): if self.libraries_available: self.device_manager().register_devices(self.DEVICE_IDS) - def btchip_is_connected(self, keystore): - try: - self.get_client(keystore).getFirmwareVersion() - except Exception as e: - return False - return True - def get_btchip_device(self, device): ledger = False - if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97): - ledger = True + 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) @@ -541,7 +573,6 @@ class LedgerPlugin(HW_PluginBase): def get_client(self, keystore, force_pair=True): # All client interaction should not be in the main GUI thread - #assert self.main_thread != threading.current_thread() devmgr = self.device_manager() handler = keystore.handler with devmgr.hid_lock: diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py index cfdd7c383..9bffe5738 100644 --- a/plugins/ledger/qt.py +++ b/plugins/ledger/qt.py @@ -1,15 +1,13 @@ -import threading - -from PyQt5.Qt import QInputDialog, QLineEdit, QVBoxLayout, QLabel +#from btchip.btchipPersoWizard import StartBTChipPersoDialog from electrum.i18n import _ from electrum.plugins 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 -from electrum_gui.qt.util import * -#from btchip.btchipPersoWizard import StartBTChipPersoDialog class Plugin(LedgerPlugin, QtPluginBase): icon_unpaired = ":icons/ledger_unpaired.png" @@ -77,11 +75,7 @@ class Ledger_Handler(QtHandlerBase): return def setup_dialog(self): + self.show_error(_('Initialization of Ledger HW devices is currently disabled.')) + return dialog = StartBTChipPersoDialog() dialog.exec_() - - - - - - diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py index 90710781f..89b5c2927 100644 --- a/plugins/trezor/client.py +++ b/plugins/trezor/client.py @@ -3,8 +3,8 @@ from .clientbase import TrezorClientBase class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient): def __init__(self, transport, handler, plugin): - BaseClient.__init__(self, transport) - ProtocolMixin.__init__(self, transport) + BaseClient.__init__(self, transport=transport) + ProtocolMixin.__init__(self, transport=transport) TrezorClientBase.__init__(self, handler, plugin, proto) diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index 6e10d4c49..1c3207412 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -50,6 +50,9 @@ class GuiMixin(object): 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) @@ -69,7 +72,13 @@ class GuiMixin(object): if passphrase is None: return self.proto.Cancel() passphrase = bip39_normalize_passphrase(passphrase) - return self.proto.PassphraseAck(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() @@ -116,6 +125,14 @@ class TrezorClientBase(GuiMixin, PrintError): 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() diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py index 808f83a6a..032a02f16 100644 --- a/plugins/trezor/qt_generic.py +++ b/plugins/trezor/qt_generic.py @@ -251,7 +251,7 @@ class QtPlugin(QtPluginBase): vbox.addWidget(QLabel(msg)) vbox.addWidget(text) pin = QLineEdit() - pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) + pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) diff --git a/plugins/trezor/transport.py b/plugins/trezor/transport.py new file mode 100644 index 000000000..5ce686c69 --- /dev/null +++ b/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/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 322c2da31..edf4d57bf 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -1,5 +1,3 @@ -import threading - from binascii import hexlify, unhexlify from electrum.util import bfh, bh2u, versiontuple @@ -93,7 +91,6 @@ class TrezorPlugin(HW_PluginBase): def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) - self.main_thread = threading.current_thread() try: # Minimal test if python-trezor is installed @@ -117,6 +114,7 @@ class TrezorPlugin(HW_PluginBase): return from . import client + from . import transport import trezorlib.ckd_public import trezorlib.messages self.client_class = client.TrezorClient @@ -124,17 +122,17 @@ class TrezorPlugin(HW_PluginBase): self.types = trezorlib.messages self.DEVICE_IDS = ('TREZOR',) + self.transport_handler = transport.TrezorTransport() self.device_manager().register_enumerate_func(self.enumerate) def enumerate(self): - from trezorlib.device import TrezorDevice - return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in TrezorDevice.enumerate()] + 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): - from trezorlib.device import TrezorDevice try: self.print_error("connecting to device at", device.path) - transport = TrezorDevice.find_by_path(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 @@ -379,56 +377,86 @@ class TrezorPlugin(HW_PluginBase): return inputs def tx_outputs(self, derivation, tx, script_gen=SCRIPT_GEN_LEGACY): + + def create_output_by_derivation(info): + index, xpubs, m = info + if len(xpubs) == 1: + if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOWITNESS + elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS + else: + script_type = self.types.OutputScriptType.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: + if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOWITNESS + elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: + script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS + else: + script_type = self.types.OutputScriptType.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.OutputScriptType.PAYTOOPRETURN + txoutputtype.op_return_data = address[2:] + elif _type == TYPE_ADDRESS: + txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS + txoutputtype.address = address + return txoutputtype + + def is_any_output_on_change_branch(): + for _type, address, amount in tx.outputs(): + info = tx.output_info.get(address) + if info is not None: + index, xpubs, m = info + if index[0] == 1: + return True + return False + outputs = [] has_change = False + any_output_on_change_branch = is_any_output_on_change_branch() for _type, address, amount in tx.outputs(): + use_create_by_derivation = False + info = tx.output_info.get(address) if info is not None and not has_change: - has_change = True # no more than one change address index, xpubs, m = info - if len(xpubs) == 1: - if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOWITNESS - elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS - else: - script_type = self.types.OutputScriptType.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: - if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOWITNESS - elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: - script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS - else: - script_type = self.types.OutputScriptType.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) + 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(info) else: - txoutputtype = self.types.TxOutputType() - txoutputtype.amount = amount - if _type == TYPE_SCRIPT: - txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN - txoutputtype.op_return_data = address[2:] - elif _type == TYPE_ADDRESS: - txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS - txoutputtype.address = address - + txoutputtype = create_output_by_address() outputs.append(txoutputtype) return outputs diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py index b8e832e66..f20c1172c 100644 --- a/plugins/trustedcoin/trustedcoin.py +++ b/plugins/trustedcoin/trustedcoin.py @@ -214,26 +214,6 @@ class Wallet_2fa(Multisig_Wallet): def get_user_id(self): return get_user_id(self.storage) - def get_max_amount(self, config, inputs, recipient, fee): - from electrum.transaction import Transaction - sendable = sum(map(lambda x:x['value'], inputs)) - for i in inputs: - self.add_input_info(i) - xf = self.extra_fee(config) - _type, addr = recipient - if xf and sendable >= xf: - billing_address = self.billing_info['billing_address'] - sendable -= xf - outputs = [(_type, addr, sendable), - (TYPE_ADDRESS, billing_address, xf)] - else: - outputs = [(_type, addr, sendable)] - dummy_tx = Transaction.from_io(inputs, outputs) - if fee is None: - fee = self.estimate_fee(config, dummy_tx.estimated_size()) - amount = max(0, sendable - fee) - return amount, fee - def min_prepay(self): return min(self.price_per_tx.keys()) @@ -348,12 +328,17 @@ class TrustedCoinPlugin(BasePlugin): def get_tx_extra_fee(self, wallet, tx): if type(wallet) != Wallet_2fa: return + if wallet.billing_info is None: + assert wallet.can_sign_without_server() + return None address = wallet.billing_info['billing_address'] for _type, addr, amount in tx.outputs(): if _type == TYPE_ADDRESS and addr == address: return address, amount def request_billing_info(self, wallet): + if wallet.can_sign_without_server(): + return self.print_error("request billing info") billing_info = server.get(wallet.get_user_id()[1]) billing_address = make_billing_address(wallet, billing_info['billing_index']) @@ -421,7 +406,7 @@ class TrustedCoinPlugin(BasePlugin): xprv1, xpub1 = self.get_xkeys(seed, passphrase, "m/0'/") xprv2, xpub2 = self.get_xkeys(seed, passphrase, "m/1'/") else: - raise BaseException('unrecognized seed length') + raise BaseException('unrecognized seed length: {} words'.format(n)) return xprv1, xpub1, xprv2, xpub2 def create_keystore(self, wizard, seed, passphrase): diff --git a/setup.py b/setup.py index 63581a614..58c938e5e 100755 --- a/setup.py +++ b/setup.py @@ -20,22 +20,24 @@ version = imp.load_source('version', 'lib/version.py') if sys.version_info[:3] < (3, 4, 0): sys.exit("Error: Electrum requires Python version >= 3.4.0...") -data_files = ['contrib/requirements/' + r for r in ['requirements.txt', 'requirements-hw.txt']] +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, 'pixmaps/'), ['icons/electrum.png']) + (os.path.join(usr_share, icons_dirname), ['icons/electrum.png']) ] setup( @@ -43,7 +45,7 @@ setup( version=version.ELECTRUM_VERSION, install_requires=requirements, extras_require={ - 'hardware': requirements_hw, + 'full': requirements_hw + ['pycryptodomex'], }, packages=[ 'electrum',