Browse Source

Merge branch 'master' into TextCompleter

3.2.x
ghost43 7 years ago
committed by GitHub
parent
commit
54d220c311
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 3
      MANIFEST.in
  3. 10
      README.rst
  4. 22
      RELEASE-NOTES
  5. 29
      app.fil
  6. 31
      contrib/build-osx/README.md
  7. 12
      contrib/build-osx/base.sh
  8. 86
      contrib/build-osx/cdrkit-deterministic.patch
  9. 39
      contrib/build-osx/make_osx
  10. 5
      contrib/build-osx/osx.spec
  11. 88
      contrib/build-osx/package.sh
  12. 4
      contrib/build-wine/build.sh
  13. 4
      contrib/build-wine/deterministic.spec
  14. 46
      contrib/build-wine/prepare-wine.sh
  15. 38
      contrib/deterministic-build/find_restricted_dependencies.py
  16. 62
      contrib/deterministic-build/requirements-binaries.txt
  17. 137
      contrib/deterministic-build/requirements-hw.txt
  18. 83
      contrib/deterministic-build/requirements.txt
  19. 23
      contrib/freeze_packages.sh
  20. 26
      contrib/make_locale
  21. 22
      electrum
  22. 1
      electrum-env
  23. 6
      electrum.desktop
  24. 23
      gui/kivy/main.kv
  25. 97
      gui/kivy/main_window.py
  26. BIN
      gui/kivy/theming/light/share.png
  27. 1
      gui/kivy/uix/context_menu.py
  28. 216
      gui/kivy/uix/dialogs/addresses.py
  29. 28
      gui/kivy/uix/dialogs/installwizard.py
  30. 169
      gui/kivy/uix/dialogs/invoices.py
  31. 95
      gui/kivy/uix/dialogs/password_dialog.py
  32. 157
      gui/kivy/uix/dialogs/requests.py
  33. 3
      gui/kivy/uix/dialogs/settings.py
  34. 2
      gui/kivy/uix/menus.py
  35. 218
      gui/kivy/uix/screens.py
  36. 90
      gui/kivy/uix/ui_screens/address.kv
  37. 66
      gui/kivy/uix/ui_screens/invoices.kv
  38. 23
      gui/kivy/uix/ui_screens/receive.kv
  39. 66
      gui/kivy/uix/ui_screens/requests.kv
  40. 23
      gui/kivy/uix/ui_screens/send.kv
  41. 70
      gui/qt/__init__.py
  42. 2
      gui/qt/address_list.py
  43. 2
      gui/qt/console.py
  44. 8
      gui/qt/exception_window.py
  45. 15
      gui/qt/history_list.py
  46. 28
      gui/qt/installwizard.py
  47. 2
      gui/qt/invoice_list.py
  48. 58
      gui/qt/main_window.py
  49. 9
      gui/qt/paytoedit.py
  50. 10
      gui/qt/qrtextedit.py
  51. 4
      gui/qt/request_list.py
  52. 9
      gui/qt/transaction_dialog.py
  53. 13
      gui/qt/util.py
  54. 51
      lib/base_wizard.py
  55. 76
      lib/bitcoin.py
  56. 6
      lib/blockchain.py
  57. 24
      lib/commands.py
  58. 2
      lib/constants.py
  59. 11
      lib/contacts.py
  60. 7
      lib/daemon.py
  61. 8
      lib/exchange_rate.py
  62. 14
      lib/interface.py
  63. 37
      lib/keystore.py
  64. 25
      lib/mnemonic.py
  65. 12
      lib/network.py
  66. 16
      lib/paymentrequest.py
  67. 10
      lib/plot.py
  68. 52
      lib/plugins.py
  69. 2
      lib/qrscanner.py
  70. 17
      lib/simple_config.py
  71. 32
      lib/storage.py
  72. 14
      lib/synchronizer.py
  73. 16
      lib/tests/__init__.py
  74. 90
      lib/tests/test_bitcoin.py
  75. 588
      lib/tests/test_transaction.py
  76. 112
      lib/tests/test_wallet_vertical.py
  77. 49
      lib/transaction.py
  78. 16
      lib/util.py
  79. 2
      lib/version.py
  80. 152
      lib/wallet.py
  81. 2
      lib/websockets.py
  82. 4
      lib/x509.py
  83. 7
      plugins/cosigner_pool/qt.py
  84. 100
      plugins/digitalbitbox/digitalbitbox.py
  85. 11
      plugins/digitalbitbox/qt.py
  86. 59
      plugins/email_requests/qt.py
  87. 3
      plugins/hw_wallet/cmdline.py
  88. 19
      plugins/keepkey/clientbase.py
  89. 115
      plugins/keepkey/plugin.py
  90. 2
      plugins/keepkey/qt_generic.py
  91. 91
      plugins/labels/labels.py
  92. 21
      plugins/labels/qt.py
  93. 40
      plugins/ledger/auth2fa.py
  94. 77
      plugins/ledger/ledger.py
  95. 16
      plugins/ledger/qt.py
  96. 4
      plugins/trezor/client.py
  97. 19
      plugins/trezor/clientbase.py
  98. 2
      plugins/trezor/qt_generic.py
  99. 95
      plugins/trezor/transport.py
  100. 126
      plugins/trezor/trezor.py

1
.gitignore

@ -16,6 +16,7 @@ env/
.tox/ .tox/
.buildozer/ .buildozer/
bin/ bin/
/app.fil
# tox files # tox files
.cache/ .cache/

3
MANIFEST.in

@ -4,12 +4,13 @@ include electrum.conf.sample
include electrum.desktop include electrum.desktop
include *.py include *.py
include electrum include electrum
include contrib/requirements/requirements.txt
include contrib/requirements/requirements-hw.txt
recursive-include lib *.py recursive-include lib *.py
recursive-include gui *.py recursive-include gui *.py
recursive-include plugins *.py recursive-include plugins *.py
recursive-include packages *.py recursive-include packages *.py
recursive-include packages cacert.pem recursive-include packages cacert.pem
include app.fil
include icons.qrc include icons.qrc
recursive-include icons * recursive-include icons *
recursive-include scripts * recursive-include scripts *

10
README.rst

@ -15,7 +15,9 @@ Electrum - Lightweight Bitcoin client
.. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master .. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master
:target: https://coveralls.io/github/spesmilo/electrum?branch=master :target: https://coveralls.io/github/spesmilo/electrum?branch=master
:alt: Test coverage statistics :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:: You can also install Electrum on your system, by running this command::
sudo apt-get install python3-setuptools sudo apt-get install python3-setuptools
python3 setup.py install pip3 install .[full]
This will download and install the Python dependencies used by This will download and install the Python dependencies used by
Electrum, instead of using the 'packages' directory. 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 If you cloned the git repository, you need to compile extra files
before you can run Electrum. Read the next section, "Development 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):: Run install (this should install dependencies)::
python3 setup.py install pip3 install .[full]
Compile the icons file for Qt:: Compile the icons file for Qt::

22
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 * Memory-pool based fee estimation. Dynamic fees can target a desired
depth in the memory pool. This feature is optional, and ETA-based depth in the memory pool. This feature is optional, and ETA-based

29
app.fil

@ -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

31
contrib/build-osx/README.md

@ -2,16 +2,35 @@ Building Mac OS binaries
======================== ========================
This guide explains how to build Electrum binaries for macOS systems. 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/

12
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
}

86
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
/*

39
contrib/build-osx/make_osx

@ -1,28 +1,18 @@
#!/bin/bash #!/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
}
build_dir=$(dirname "$0")
test -n "$build_dir" -a -d "$build_dir" || exit
cd $build_dir/../..
export PYTHONHASHSEED=22 # Parameterize
VERSION=`git describe --tags`
# Paramterize
PYTHON_VERSION=3.6.4 PYTHON_VERSION=3.6.4
BUILDDIR=/tmp/electrum-build BUILDDIR=/tmp/electrum-build
PACKAGE=Electrum PACKAGE=Electrum
GIT_REPO=https://github.com/spesmilo/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" info "Installing Python $PYTHON_VERSION"
export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH" 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..." 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 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..." info "Installing requirements..."
python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \ 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..." info "Building $PACKAGE..."
python3 setup.py install --user > /dev/null || fail "Could not build $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" info "Building binary"
pyinstaller --noconfirm --ascii --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary" pyinstaller --noconfirm --ascii --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary"

5
contrib/build-osx/osx.spec

@ -93,7 +93,8 @@ app = BUNDLE(exe,
name=PACKAGE + '.app', name=PACKAGE + '.app',
icon=electrum+ICONS_FILE, icon=electrum+ICONS_FILE,
bundle_identifier=None, bundle_identifier=None,
info_plist = { info_plist={
'NSHighResolutionCapable':'True' 'NSHighResolutionCapable': 'True',
'NSSupportsAutomaticGraphicsSwitching': 'True'
} }
) )

88
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>(.*)<\/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

4
contrib/build-wine/build.sh

@ -13,6 +13,10 @@ echo "Clearing $here/build and $here/dist..."
rm "$here"/build/* -rf rm "$here"/build/* -rf
rm "$here"/dist/* -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 $here/prepare-wine.sh || exit 1
echo "Resetting modification time in C:\Python..." echo "Resetting modification time in C:\Python..."

4
contrib/build-wine/deterministic.spec

@ -10,6 +10,8 @@ for i, x in enumerate(sys.argv):
else: else:
raise BaseException('no name') raise BaseException('no name')
PYTHON_VERSION = '3.5.4'
PYHOME = 'c:/python' + PYTHON_VERSION
home = 'C:\\electrum\\' home = 'C:\\electrum\\'
@ -21,7 +23,7 @@ hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket') hiddenimports += collect_submodules('websocket')
# Add libusb binary # Add libusb binary
binaries = [("c:/python3.5.4/libusb-1.0.dll", ".")] binaries = [(PYHOME+"/libusb-1.0.dll", ".")]
# Workaround for "Retro Look": # Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]] binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]]

46
contrib/build-wine/prepare-wine.sh

@ -1,17 +1,17 @@
#!/bin/bash #!/bin/bash
# Please update these carefully, some versions won't work under Wine # 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_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download
NSIS_SHA256=736c9062a02e297e335f82252e648a883171c98e0d5120439f538c81d429552e NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743
ZBAR_FILENAME=zbarw-20121031-setup.exe ZBAR_FILENAME=zbarw-20121031-setup.exe
ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download
ZBAR_SHA256=177e32b272fa76528a3af486b74e9cb356707be1c5ace4ed3fcee9723e2c2c02 ZBAR_SHA256=177e32b272fa76528a3af486b74e9cb356707be1c5ace4ed3fcee9723e2c2c02
LIBUSB_FILENAME=libusb-1.0.21.7z LIBUSB_FILENAME=libusb-1.0.22.7z
LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.21/$LIBUSB_FILENAME?download LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download
LIBUSB_SHA256=acdde63a40b1477898aee6153f9d91d1a2e8a5d93f832ca8ab876498f3a6d2b8 LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b
PYTHON_VERSION=3.5.4 PYTHON_VERSION=3.5.4
@ -54,6 +54,27 @@ download_if_not_exist() {
fi 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! # Let's begin!
here=$(dirname $(readlink -e $0)) here=$(dirname $(readlink -e $0))
set -e set -e
@ -65,8 +86,6 @@ echo "done"
wine 'wineboot' wine 'wineboot'
mkdir -p /tmp/electrum-build
cd /tmp/electrum-build cd /tmp/electrum-build
# Install Python # Install Python
@ -74,12 +93,12 @@ cd /tmp/electrum-build
# keys from https://www.python.org/downloads/#pubkeys # keys from https://www.python.org/downloads/#pubkeys
KEYLIST_PYTHON_DEV="531F072D39700991925FED0C0EDDC5F26A45C816 26DEA9D4613391EF3E25C9FF0A5B101836580288 CBC547978A3964D14B9AB36A6AF053F07D9DC8D2 C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF 12EF3DC38047DA382D18A5B999CDEA9DA4135B38 8417157EDBE73D9EAC1E539B126EB563A74B06BF DBBF2EEBF925FAADCF1F3FFFD9866941EA5BBD71 2BA0DB82515BBB9EFFAC71C5C9BE28DEE6DF025C 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D C9B104B3DD3AA72D7CCB1066FB9921286F5E1540 97FC712E4C024BBEA48A61ED3A5CA953F73C700D 7ED10B6531D7C8E1BC296021FC624643487034E5" KEYLIST_PYTHON_DEV="531F072D39700991925FED0C0EDDC5F26A45C816 26DEA9D4613391EF3E25C9FF0A5B101836580288 CBC547978A3964D14B9AB36A6AF053F07D9DC8D2 C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF 12EF3DC38047DA382D18A5B999CDEA9DA4135B38 8417157EDBE73D9EAC1E539B126EB563A74B06BF DBBF2EEBF925FAADCF1F3FFFD9866941EA5BBD71 2BA0DB82515BBB9EFFAC71C5C9BE28DEE6DF025C 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D C9B104B3DD3AA72D7CCB1066FB9921286F5E1540 97FC712E4C024BBEA48A61ED3A5CA953F73C700D 7ED10B6531D7C8E1BC296021FC624643487034E5"
KEYRING_PYTHON_DEV="keyring-electrum-build-python-dev.gpg" KEYRING_PYTHON_DEV="keyring-electrum-build-python-dev.gpg"
KEYSERVER_PYTHON_DEV="hkp://keys.gnupg.net" KEYSERVER_PYTHON_DEV="hkp://pool.sks-keyservers.net"
gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver $KEYSERVER_PYTHON_DEV --recv-keys $KEYLIST_PYTHON_DEV 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 for msifile in core dev exe lib pip tools; do
echo "Installing $msifile..." echo "Installing $msifile..."
wget -nc "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"
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.asc"
verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV
wine msiexec /i "${msifile}.msi" /qb TARGETDIR=C:/python$PYTHON_VERSION wine msiexec /i "${msifile}.msi" /qb TARGETDIR=C:/python$PYTHON_VERSION
done 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/ 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: # add dlls needed for pyinstaller:
cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/ cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/

38
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

62
contrib/deterministic-build/requirements-binaries.txt

@ -1,5 +1,57 @@
pycryptodomex==3.4.12 pip==9.0.3 \
PyQt5==5.10 --hash=sha256:7bf48f9a693be1d58f49f7af7e0ae9fe29fd671cde8a55e6edca3581c4ef5796 \
sip==4.19.7 --hash=sha256:c3ede34530e0e0b2381e7363aded78e0c33291654937e7373032fda04e8803e5
six==1.11.0 pycryptodomex==3.5.1 \
websocket-client==0.46.0 --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

137
contrib/deterministic-build/requirements-hw.txt

@ -1,18 +1,119 @@
btchip-python==0.1.24 btchip-python==0.1.26 \
certifi==2018.1.18 --hash=sha256:427d67c5b4f4709605c51dd91d5d44a2ad8f541693673817765271e4b3a1461e
chardet==3.0.4 certifi==2018.1.18 \
click==6.7 --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \
Cython==0.27.3 --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d
ecdsa==0.13 chardet==3.0.4 \
hidapi==0.7.99.post21 --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
idna==2.6 --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
keepkey==4.0.2 click==6.7 \
libusb1==1.6.4 --hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \
mnemonic==0.18 --hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b
pbkdf2==1.3 Cython==0.28.1 \
protobuf==3.5.1 --hash=sha256:0a44c3645a84724d4f7f7c1126f3807cad14271f210bb1a9a15bfd330c8d20b9 \
pyblake2==1.1.0 --hash=sha256:152ee5f345012ca3bb7cc71da2d3736ee20f52cd8476e4d49e5e25c5a4102b12 \
requests==2.18.4 --hash=sha256:15cbde95cdf6a346c63c0b38b895aa3186d0a49adcaf42b9e19c4cb0473cb921 \
six==1.11.0 --hash=sha256:175273eb6a90af364b9b2aea74e3c3eebcde9caec02d617cd8886c0933ec3304 \
trezor==0.9.0 --hash=sha256:1a434e7621ca6671ce949893fef94b13a580853ba976e729f68dda5ab270ee8a \
urllib3==1.22 --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

83
contrib/deterministic-build/requirements.txt

@ -1,14 +1,69 @@
certifi==2018.1.18 certifi==2018.1.18 \
chardet==3.0.4 --hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \
dnspython==1.15.0 --hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d
ecdsa==0.13 chardet==3.0.4 \
idna==2.6 --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
jsonrpclib-pelix==0.3.1 --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
pbkdf2==1.3 dnspython==1.15.0 \
protobuf==3.5.1 --hash=sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c \
pyaes==1.6.1 --hash=sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31
PySocks==1.6.8 ecdsa==0.13 \
qrcode==5.3 --hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
requests==2.18.4 --hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
six==1.11.0 idna==2.6 \
urllib3==1.22 --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

23
contrib/freeze_packages.sh

@ -5,18 +5,35 @@ venv_dir=~/.electrum-venv
contrib=$(dirname "$0") contrib=$(dirname "$0")
which virtualenv > /dev/null 2>&1 || { echo "Please install virtualenv" && exit 1; } 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 for i in '' '-hw' '-binaries'; do
rm "$venv_dir" -rf rm -rf "$venv_dir"
virtualenv -p $(which python3) $venv_dir virtualenv -p $(which python3) $venv_dir
source $venv_dir/bin/activate source $venv_dir/bin/activate
echo "Installing $i dependencies" echo "Installing $m dependencies"
python -m pip install -r $contrib/requirements/requirements${i}.txt --upgrade 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 done
echo "Done. Updated requirements" echo "Done. Updated requirements"

26
contrib/make_locale

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import subprocess
import io import io
import zipfile import zipfile
import requests import requests
@ -7,21 +8,31 @@ import requests
os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..') 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 # Generate fresh translation template
if not os.path.exists('lib/locale'): if not os.path.exists('lib/locale'):
os.mkdir('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') print('Generate template')
os.system(cmd) os.system(cmd)
os.chdir('lib') os.chdir('lib')
crowdin_identifier = 'electrum' crowdin_identifier = 'electrum'
crowdin_file_name = 'electrum-client/messages.pot' crowdin_file_name = 'files[electrum-client/messages.pot]'
locale_file_name = 'locale/messages.pot' locale_file_name = 'locale/messages.pot'
crowdin_api_key = None crowdin_api_key = None
filename = '~/.crowdin_api_key' filename = os.path.expanduser('~/.crowdin_api_key')
if os.path.exists(filename): if os.path.exists(filename):
with open(filename) as f: with open(filename) as f:
crowdin_api_key = f.read().strip() crowdin_api_key = f.read().strip()
@ -33,13 +44,14 @@ if crowdin_api_key:
# Push to Crowdin # Push to Crowdin
print('Push to Crowdin') print('Push to Crowdin')
url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key) 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} 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 # Build translations
print('Build translations') print('Build translations')
response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key).content response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key)
print(response) print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n")
# Download & unzip # Download & unzip
print('Download translations') print('Download translations')

22
electrum

@ -26,21 +26,6 @@
import os import os
import sys 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__)) script_dir = os.path.dirname(os.path.realpath(__file__))
is_bundle = getattr(sys, 'frozen', False) is_bundle = getattr(sys, 'frozen', False)
is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop")) 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 import SimpleConfig, Network
from electrum.wallet import Wallet, Imported_Wallet from electrum.wallet import Wallet, Imported_Wallet
from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption 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.util import set_verbosity, InvalidPassword
from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum.commands import get_parser, known_commands, Commands, config_variables
from electrum import daemon from electrum import daemon
@ -295,7 +280,10 @@ def get_password_for_hw_device_encrypted_storage(plugins):
name, device_info = devices[0] name, device_info = devices[0]
plugin = plugins.get_plugin(name) plugin = plugins.get_plugin(name)
derivation = get_derivation_used_for_hw_device_encryption() 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, ()) password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
return password return password

1
electrum-env

@ -11,6 +11,7 @@
PYTHON_VER="$(python3 -c 'import sys; print(sys.version[:3])')" PYTHON_VER="$(python3 -c 'import sys; print(sys.version[:3])')"
cd $(dirname $0)
if [ -e ./env/bin/activate ]; then if [ -e ./env/bin/activate ]; then
source ./env/bin/activate source ./env/bin/activate
else else

6
electrum.desktop

@ -3,7 +3,7 @@
[Desktop Entry] [Desktop Entry]
Comment=Lightweight Bitcoin Client Comment=Lightweight Bitcoin Client
Exec=electrum %u Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum %u"
GenericName[en_US]=Bitcoin Wallet GenericName[en_US]=Bitcoin Wallet
GenericName=Bitcoin Wallet GenericName=Bitcoin Wallet
Icon=electrum Icon=electrum
@ -14,4 +14,8 @@ StartupNotify=false
Terminal=false Terminal=false
Type=Application Type=Application
MimeType=x-scheme-handler/bitcoin; MimeType=x-scheme-handler/bitcoin;
Actions=Testnet;
[Desktop Action Testnet]
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum --testnet %u"
Name=Testnet mode

23
gui/kivy/main.kv

@ -285,7 +285,8 @@
<KButton@Button>: <KButton@Button>:
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '60dp'
font_size: '30dp'
on_release: on_release:
self.parent.update_amount(self.text) self.parent.update_amount(self.text)
@ -372,9 +373,6 @@
tab_height: '48dp' tab_height: '48dp'
tab_width: panel.width/3 tab_width: panel.width/3
strip_border: 0, 0, 0, 0 strip_border: 0, 0, 0, 0
InvoicesScreen:
id: invoices_screen
tab: invoices_tab
SendScreen: SendScreen:
id: send_screen id: send_screen
tab: send_tab tab: send_tab
@ -384,29 +382,18 @@
ReceiveScreen: ReceiveScreen:
id: receive_screen id: receive_screen
tab: receive_tab tab: receive_tab
AddressScreen:
id: address_screen
tab: address_tab
CleanHeader:
id: invoices_tab
text: _('Invoices')
slide: 0
CleanHeader: CleanHeader:
id: send_tab id: send_tab
text: _('Send') text: _('Send')
slide: 1 slide: 0
CleanHeader: CleanHeader:
id: history_tab id: history_tab
text: _('Balance') text: _('Balance')
slide: 2 slide: 1
CleanHeader: CleanHeader:
id: receive_tab id: receive_tab
text: _('Receive') text: _('Receive')
slide: 3 slide: 2
CleanHeader:
id: address_tab
text: _('Addresses')
slide: 4
<ActionOvrButton@ActionButton> <ActionOvrButton@ActionButton>

97
gui/kivy/main_window.py

@ -244,6 +244,7 @@ class ElectrumWindow(App):
self.tabs = None self.tabs = None
self.is_exit = False self.is_exit = False
self.wallet = None self.wallet = None
self.pause_time = 0
App.__init__(self)#, **kwargs) App.__init__(self)#, **kwargs)
@ -445,7 +446,6 @@ class ElectrumWindow(App):
#win.softinput_mode = 'below_target' #win.softinput_mode = 'below_target'
self.on_size(win, win.size) self.on_size(win, win.size)
self.init_ui() self.init_ui()
self.load_wallet_by_name(self.electrum_config.get_wallet_path())
# init plugins # init plugins
run_hook('init_kivy', self) run_hook('init_kivy', self)
# fiat currency # fiat currency
@ -467,6 +467,8 @@ class ElectrumWindow(App):
self.network.register_callback(self.on_fee, ['fee']) self.network.register_callback(self.on_fee, ['fee'])
self.network.register_callback(self.on_quotes, ['on_quotes']) self.network.register_callback(self.on_quotes, ['on_quotes'])
self.network.register_callback(self.on_history, ['on_history']) 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 passed in config
uri = self.electrum_config.get('url') uri = self.electrum_config.get('url')
if uri: if uri:
@ -484,17 +486,18 @@ class ElectrumWindow(App):
wallet.start_threads(self.daemon.network) wallet.start_threads(self.daemon.network)
self.daemon.add_wallet(wallet) self.daemon.add_wallet(wallet)
self.load_wallet(wallet) self.load_wallet(wallet)
self.on_resume()
def load_wallet_by_name(self, path): def load_wallet_by_name(self, path):
if not path: if not path:
return return
if self.wallet and self.wallet.storage.path == path:
return
wallet = self.daemon.load_wallet(path, None) wallet = self.daemon.load_wallet(path, None)
if wallet: if wallet:
if wallet != self.wallet: if wallet.has_password():
self.stop_wallet() self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
else:
self.load_wallet(wallet) self.load_wallet(wallet)
self.on_resume()
else: else:
Logger.debug('Electrum: Wallet not found. Launching install wizard') Logger.debug('Electrum: Wallet not found. Launching install wizard')
storage = WalletStorage(path) storage = WalletStorage(path)
@ -504,6 +507,7 @@ class ElectrumWindow(App):
wizard.run(action) wizard.run(action)
def on_stop(self): def on_stop(self):
Logger.info('on_stop')
self.stop_wallet() self.stop_wallet()
def stop_wallet(self): def stop_wallet(self):
@ -617,6 +621,8 @@ class ElectrumWindow(App):
@profiler @profiler
def load_wallet(self, wallet): def load_wallet(self, wallet):
if self.wallet:
self.stop_wallet()
self.wallet = wallet self.wallet = wallet
self.update_wallet() self.update_wallet()
# Once GUI has been initialized check if we want to announce something # 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`') Logger.Error('Notification: needs plyer; `sudo pip install plyer`')
def on_pause(self): def on_pause(self):
self.pause_time = time.time()
# pause nfc # pause nfc
if self.nfcscanner: if self.nfcscanner:
self.nfcscanner.nfc_disable() self.nfcscanner.nfc_disable()
return True return True
def on_resume(self): 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: if self.nfcscanner:
self.nfcscanner.nfc_enable() self.nfcscanner.nfc_enable()
@ -824,7 +834,6 @@ class ElectrumWindow(App):
d = LabelDialog(_('Enter description'), text, callback) d = LabelDialog(_('Enter description'), text, callback)
d.open() d.open()
@profiler
def amount_dialog(self, screen, show_max): def amount_dialog(self, screen, show_max):
from .uix.dialogs.amount_dialog import AmountDialog from .uix.dialogs.amount_dialog import AmountDialog
amount = screen.amount amount = screen.amount
@ -836,6 +845,34 @@ class ElectrumWindow(App):
popup = AmountDialog(show_max, amount, cb) popup = AmountDialog(show_max, amount, cb)
popup.open() 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): def fee_dialog(self, label, dt):
from .uix.dialogs.fee_dialog import FeeDialog from .uix.dialogs.fee_dialog import FeeDialog
def cb(): def cb():
@ -848,7 +885,8 @@ class ElectrumWindow(App):
def protected(self, msg, f, args): def protected(self, msg, f, args):
if self.wallet.has_password(): 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: else:
f(*(args + (None,))) f(*(args + (None,)))
@ -860,7 +898,7 @@ class ElectrumWindow(App):
def _delete_wallet(self, b): def _delete_wallet(self, b):
if 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, ()) self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ())
def __delete_wallet(self, pw): def __delete_wallet(self, pw):
@ -898,40 +936,23 @@ class ElectrumWindow(App):
if passphrase: if passphrase:
label.text += '\n\n' + _('Passphrase') + ': ' + passphrase label.text += '\n\n' + _('Passphrase') + ': ' + passphrase
def change_password(self, cb): def password_dialog(self, wallet, msg, on_success, on_failure):
if self.wallet.has_password(): from .uix.dialogs.password_dialog import PasswordDialog
self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,)) if self._password_dialog is None:
else: self._password_dialog = PasswordDialog()
self._change_password(cb, None) self._password_dialog.init(self, wallet, msg, on_success, on_failure)
self._password_dialog.open()
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, msg, f, args): def change_password(self, cb):
from .uix.dialogs.password_dialog import PasswordDialog 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: if self._password_dialog is None:
self._password_dialog = PasswordDialog() 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() self._password_dialog.open()
def export_private_keys(self, pk_label, addr): def export_private_keys(self, pk_label, addr):

BIN
gui/kivy/theming/light/share.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

1
gui/kivy/uix/context_menu.py

@ -47,7 +47,6 @@ class ContextMenu(Bubble):
l = MenuItem() l = MenuItem()
l.text = _(k) l.text = _(k)
def func(f=v): def func(f=v):
Clock.schedule_once(lambda dt: self.hide(), 0.1)
Clock.schedule_once(lambda dt: f(obj), 0.15) Clock.schedule_once(lambda dt: f(obj), 0.15)
l.on_release = func l.on_release = func
self.ids.buttons.add_widget(l) self.ids.buttons.add_widget(l)

216
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('''
<AddressLabel@Label>
text_size: self.width, None
halign: 'left'
valign: 'top'
<AddressItem@CardItem>
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
<AddressesDialog@Popup>
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

28
gui/kivy/uix/dialogs/installwizard.py

@ -802,28 +802,18 @@ class InstallWizard(BaseWizard, Widget):
app = App.get_running_app() app = App.get_running_app()
Clock.schedule_once(lambda dt: app.show_error(msg)) 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 = 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() 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): def action_dialog(self, action, run_next):
f = getattr(self, action) f = getattr(self, action)
f() f()

169
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('''
<InvoicesLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<InvoiceItem@CardItem>
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
<InvoicesDialog@Popup>
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

95
gui/kivy/uix/dialogs/password_dialog.py

@ -5,35 +5,42 @@ from kivy.lang import Builder
from decimal import Decimal from decimal import Decimal
from kivy.clock import Clock from kivy.clock import Clock
from electrum.util import InvalidPassword
from electrum_gui.kivy.i18n import _
Builder.load_string(''' Builder.load_string('''
<PasswordDialog@Popup> <PasswordDialog@Popup>
id: popup id: popup
title: _('PIN Code') title: 'Electrum'
message: '' message: ''
size_hint: 0.9, 0.9
BoxLayout: BoxLayout:
size_hint: 1, 1
orientation: 'vertical' orientation: 'vertical'
Widget: Widget:
size_hint: 1, 1 size_hint: 1, 0.05
Label: Label:
font_size: '20dp'
text: root.message text: root.message
text_size: self.width, None text_size: self.width, None
size: self.texture_size size: self.texture_size
Widget: Widget:
size_hint: 1, 1 size_hint: 1, 0.05
Label: Label:
id: a 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: Widget:
size_hint: 1, 1 size_hint: 1, 0.05
GridLayout: GridLayout:
id: kb id: kb
size_hint: 1, None
height: self.minimum_height
update_amount: popup.update_password update_amount: popup.update_password
password: '' password: ''
on_password: popup.on_password(self.password) on_password: popup.on_password(self.password)
size_hint: 1, None spacing: '2dp'
height: '200dp'
cols: 3 cols: 3
KButton: KButton:
text: '1' text: '1'
@ -59,30 +66,44 @@ Builder.load_string('''
text: '0' text: '0'
KButton: KButton:
text: '<' 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): class PasswordDialog(Factory.Popup):
#def __init__(self, message, callback): def init(self, app, wallet, message, on_success, on_failure, is_change=0):
# Factory.Popup.__init__(self) self.app = app
self.wallet = wallet
def init(self, message, callback):
self.message = message self.message = message
self.callback = callback self.on_success = on_success
self.on_failure = on_failure
self.ids.kb.password = '' 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): def update_password(self, c):
kb = self.ids.kb kb = self.ids.kb
@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup):
def on_password(self, pw): def on_password(self, pw):
if len(pw) == 6: if len(pw) == 6:
self.dismiss() if self.check_password(pw):
Clock.schedule_once(lambda dt: self.callback(pw), 0.1) 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 = ''

157
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('''
<RequestLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<RequestItem@CardItem>
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
<RequestsDialog@Popup>
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

3
gui/kivy/uix/dialogs/settings.py

@ -36,9 +36,8 @@ Builder.load_string('''
action: partial(root.language_dialog, self) action: partial(root.language_dialog, self)
CardSeparator CardSeparator
SettingsItem: SettingsItem:
status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF')
disabled: root.disable_pin disabled: root.disable_pin
title: _('PIN code') + ': ' + self.status title: _('PIN code')
description: _("Change your PIN code.") description: _("Change your PIN code.")
action: partial(root.change_password, self) action: partial(root.change_password, self)
CardSeparator CardSeparator

2
gui/kivy/uix/menus.py

@ -7,7 +7,7 @@ from kivy.uix.bubble import Bubble, BubbleButton
from kivy.properties import ListProperty from kivy.properties import ListProperty
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from electrum_gui.i18n import _ from electrum_gui.kivy.i18n import _
class ContextMenuItem(Widget): class ContextMenuItem(Widget):
'''abstract class '''abstract class

218
gui/kivy/uix/screens.py

@ -27,8 +27,6 @@ from .context_menu import ContextMenu
from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.i18n import _
class EmptyLabel(Factory.Label):
pass
class CScreen(Factory.Screen): class CScreen(Factory.Screen):
__events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave') __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
@ -219,7 +217,6 @@ class SendScreen(CScreen):
pr = make_unsigned_request(req).SerializeToString() pr = make_unsigned_request(req).SerializeToString()
pr = PaymentRequest(pr) pr = PaymentRequest(pr)
self.app.wallet.invoices.add(pr) self.app.wallet.invoices.add(pr)
self.app.update_tab('invoices')
self.app.show_info(_("Invoice saved")) self.app.show_info(_("Invoice saved"))
if pr.is_pr(): if pr.is_pr():
self.screen.is_pr = True self.screen.is_pr = True
@ -374,221 +371,32 @@ class ReceiveScreen(CScreen):
def save_request(self): def save_request(self):
addr = self.screen.address addr = self.screen.address
if not addr: if not addr:
return return False
amount = self.screen.amount amount = self.screen.amount
message = self.screen.message message = self.screen.message
amount = self.app.get_amount(amount) if amount else 0 amount = self.app.get_amount(amount) if amount else 0
req = self.app.wallet.make_payment_request(addr, amount, message, None) req = self.app.wallet.make_payment_request(addr, amount, message, None)
self.app.wallet.add_payment_request(req, self.app.electrum_config) try:
self.app.update_tab('requests') 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): def on_amount_or_message(self):
self.save_request()
Clock.schedule_once(lambda dt: self.update_qr()) Clock.schedule_once(lambda dt: self.update_qr())
def do_new(self): def do_new(self):
addr = self.get_new_address() addr = self.get_new_address()
if not addr: if not addr:
self.app.show_info(_('Please use the existing requests first.')) 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): class TabbedCarousel(Factory.TabbedPanel):

90
gui/kivy/uix/ui_screens/address.kv

@ -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'
<AddressLabel@Label>
text_size: self.width, None
halign: 'left'
valign: 'top'
<AddressItem@CardItem>
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

66
gui/kivy/uix/ui_screens/invoices.kv

@ -1,66 +0,0 @@
<InvoicesLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<InvoiceItem@CardItem>
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'

23
gui/kivy/uix/ui_screens/receive.kv

@ -70,7 +70,7 @@ ReceiveScreen:
id: address_label id: address_label
text: s.address if s.address else _('Bitcoin Address') text: s.address if s.address else _('Bitcoin Address')
shorten: True shorten: True
disabled: True on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
CardSeparator: CardSeparator:
opacity: message_selection.opacity opacity: message_selection.opacity
color: blue_bottom.foreground_color color: blue_bottom.foreground_color
@ -110,16 +110,31 @@ ReceiveScreen:
BoxLayout: BoxLayout:
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '48dp'
IconButton:
icon: 'atlas://gui/kivy/theming/light/save'
size_hint: 0.6, None
height: '48dp'
on_release: s.parent.do_save()
Button: Button:
text: _('Copy') text: _('Requests')
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '48dp'
on_release: s.parent.do_copy() on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
Button: Button:
text: _('Share') text: _('Copy')
size_hint: 1, None size_hint: 1, None
height: '48dp' 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() on_release: s.parent.do_share()
BoxLayout:
size_hint: 1, None
height: '48dp'
Widget
size_hint: 2, 1
Button: Button:
text: _('New') text: _('New')
size_hint: 1, None size_hint: 1, None

66
gui/kivy/uix/ui_screens/requests.kv

@ -1,66 +0,0 @@
<RequestLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<RequestItem@CardItem>
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'

23
gui/kivy/uix/ui_screens/send.kv

@ -34,6 +34,7 @@ SendScreen:
text: s.address if s.address else _('Recipient') text: s.address if s.address else _('Recipient')
shorten: True 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.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: CardSeparator:
opacity: int(not root.is_pr) opacity: int(not root.is_pr)
color: blue_bottom.foreground_color color: blue_bottom.foreground_color
@ -93,25 +94,29 @@ SendScreen:
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '48dp'
IconButton: IconButton:
id: qr
size_hint: 0.6, 1 size_hint: 0.6, 1
on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr)) on_release: s.parent.do_save()
icon: 'atlas://gui/kivy/theming/light/camera' 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: Button:
text: _('Paste') text: _('Paste')
on_release: s.parent.do_paste() on_release: s.parent.do_paste()
Button:
text: _('Clear')
on_release: s.parent.do_clear()
IconButton: IconButton:
id: qr
size_hint: 0.6, 1 size_hint: 0.6, 1
on_release: s.parent.do_save() on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))
icon: 'atlas://gui/kivy/theming/light/save' icon: 'atlas://gui/kivy/theming/light/camera'
BoxLayout: BoxLayout:
size_hint: 1, None size_hint: 1, None
height: '48dp' height: '48dp'
Button:
text: _('Clear')
on_release: s.parent.do_clear()
Widget: Widget:
size_hint: 2, 1 size_hint: 1, 1
Button: Button:
text: _('Pay') text: _('Pay')
size_hint: 1, 1 size_hint: 1, 1

70
gui/qt/__init__.py

@ -44,7 +44,8 @@ from electrum import WalletStorage
# from electrum.synchronizer import Synchronizer # from electrum.synchronizer import Synchronizer
# from electrum.verifier import SPV # from electrum.verifier import SPV
# from electrum.util import DebugMem # 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 electrum.wallet import Abstract_Wallet
from .installwizard import InstallWizard, GoBack from .installwizard import InstallWizard, GoBack
@ -185,42 +186,51 @@ class ElectrumGui:
def start_new_window(self, path, uri): def start_new_window(self, path, uri):
'''Raises the window for the wallet if it is open. Otherwise '''Raises the window for the wallet if it is open. Otherwise
opens the wallet and creates a new window for it.''' opens the wallet and creates a new window for it'''
for w in self.windows: try:
if w.wallet.storage.path == path: wallet = self.daemon.load_wallet(path, None)
w.bring_to_top() except BaseException as e:
break traceback.print_exc(file=sys.stdout)
else: 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: try:
wallet = self.daemon.load_wallet(path, None) wallet = wizard.run_and_get_wallet(self.daemon.get_wallet)
except BaseException as e: except UserCancelled:
traceback.print_exc(file=sys.stdout) 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'), d = QMessageBox(QMessageBox.Warning, _('Error'),
_('Cannot load wallet:') + '\n' + str(e)) _('Cannot load wallet') + ' (2):\n' + str(e))
d.exec_() d.exec_()
return return
if not wallet: finally:
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)
wizard.terminate() wizard.terminate()
if not wallet: if not wallet:
return return
if not self.daemon.get_wallet(wallet.storage.path):
# wallet was not in memory
wallet.start_threads(self.daemon.network) wallet.start_threads(self.daemon.network)
self.daemon.add_wallet(wallet) self.daemon.add_wallet(wallet)
try: try:
w = self.create_window_for_wallet(wallet) for w in self.windows:
except BaseException as e: if w.wallet.storage.path == wallet.storage.path:
traceback.print_exc(file=sys.stdout) w.bring_to_top()
d = QMessageBox(QMessageBox.Warning, _('Error'), return
_('Cannot create window for wallet:') + '\n' + str(e)) w = self.create_window_for_wallet(wallet)
d.exec_() except BaseException as e:
return traceback.print_exc(file=sys.stdout)
d = QMessageBox(QMessageBox.Warning, _('Error'),
_('Cannot create window for wallet') + ':\n' + str(e))
d.exec_()
return
if uri: if uri:
w.pay_to_URI(uri) w.pay_to_URI(uri)
w.bring_to_top() w.bring_to_top()

2
gui/qt/address_list.py

@ -123,7 +123,7 @@ class AddressList(MyTreeWidget):
address_item.setText(0, _('receiving')) address_item.setText(0, _('receiving'))
address_item.setBackground(0, ColorScheme.GREEN.as_color(True)) address_item.setBackground(0, ColorScheme.GREEN.as_color(True))
address_item.setFont(1, QFont(MONOSPACE_FONT)) 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): if self.wallet.is_frozen(address):
address_item.setBackground(1, ColorScheme.BLUE.as_color(True)) address_item.setBackground(1, ColorScheme.BLUE.as_color(True))
if self.wallet.is_beyond_limit(address): if self.wallet.is_beyond_limit(address):

2
gui/qt/console.py

@ -223,7 +223,7 @@ class Console(QtWidgets.QPlainTextEdit):
exec(command, self.namespace, self.namespace) exec(command, self.namespace, self.namespace)
except SystemExit: except SystemExit:
self.close() self.close()
except Exception: except BaseException:
traceback_lines = traceback.format_exc().split('\n') traceback_lines = traceback.format_exc().split('\n')
# Remove traceback mentioning this file, and a linebreak # Remove traceback mentioning this file, and a linebreak
for i in (3,2,1,-1): for i in (3,2,1,-1):

8
gui/qt/exception_window.py

@ -38,6 +38,8 @@ from PyQt5.QtWidgets import *
from electrum.i18n import _ from electrum.i18n import _
from electrum import ELECTRUM_VERSION, bitcoin, constants from electrum import ELECTRUM_VERSION, bitcoin, constants
from .util import MessageBoxMixin
issue_template = """<h2>Traceback</h2> issue_template = """<h2>Traceback</h2>
<pre> <pre>
{traceback} {traceback}
@ -54,7 +56,7 @@ issue_template = """<h2>Traceback</h2>
report_server = "https://crashhub.electrum.org/crash" report_server = "https://crashhub.electrum.org/crash"
class Exception_Window(QWidget): class Exception_Window(QWidget, MessageBoxMixin):
_active_window = None _active_window = None
def __init__(self, main_window, exctype, value, tb): def __init__(self, main_window, exctype, value, tb):
@ -75,7 +77,9 @@ class Exception_Window(QWidget):
'information:'))) 'information:')))
collapse_info = QPushButton(_("Show report contents")) 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(collapse_info)
main_box.addWidget(QLabel(_("Please briefly describe what led to the error (optional):"))) main_box.addWidget(QLabel(_("Please briefly describe what led to the error (optional):")))

15
gui/qt/history_list.py

@ -161,6 +161,9 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
def show_summary(self): def show_summary(self):
h = self.summary h = self.summary
if not h:
self.parent.show_message(_("Nothing to summarize."))
return
start_date = h.get('start_date') start_date = h.get('start_date')
end_date = h.get('end_date') end_date = h.get('end_date')
format_amount = lambda x: self.parent.format_amount(x.value) + ' ' + self.parent.base_unit() 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'] label = tx_item['label']
status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)
has_invoice = self.wallet.invoices.paid.get(tx_hash) 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) v_str = self.parent.format_amount(value, True, whitespaces=True)
balance_str = self.parent.format_amount(balance, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True)
entry = ['', tx_hash, status_str, label, v_str, balance_str] 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.setToolTip(0, str(conf) + " confirmation" + ("s" if conf != 1 else ""))
item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf)) item.setData(0, SortableTreeWidgetItem.DataRole, (status, conf))
if has_invoice: if has_invoice:
item.setIcon(3, QIcon(":icons/seal")) item.setIcon(3, self.icon_cache.get(":icons/seal"))
for i in range(len(entry)): for i in range(len(entry)):
if i>3: if i>3:
item.setTextAlignment(i, Qt.AlignRight) item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter)
if i!=2: if i!=2:
item.setFont(i, QFont(MONOSPACE_FONT)) item.setFont(i, QFont(MONOSPACE_FONT))
if value and value < 0: if value and value < 0:
@ -299,7 +302,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
def update_item(self, tx_hash, height, conf, timestamp): def update_item(self, tx_hash, height, conf, timestamp):
status, status_str = self.wallet.get_tx_status(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) items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
if items: if items:
item = items[0] item = items[0]
@ -344,7 +347,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
if child_tx: if child_tx:
menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx)) menu.addAction(_("Child pays for parent"), lambda: self.parent.cpfp(tx, child_tx))
if pr_key: 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: if tx_URL:
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
menu.exec_(self.viewport().mapToGlobal(position)) 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']]) lines.append([item['txid'], item.get('label', ''), item['confirmations'], item['value'], item['date']])
else: else:
lines.append(item) lines.append(item)
with open(fileName, "w+") as f: with open(fileName, "w+", encoding='utf-8') as f:
if is_csv: if is_csv:
import csv import csv
transaction = csv.writer(f, lineterminator='\n') transaction = csv.writer(f, lineterminator='\n')

28
gui/qt/installwizard.py

@ -148,7 +148,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
self.raise_() self.raise_()
self.refresh_gui() # Need for QT on MacOSX. Lame. 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() vbox = QVBoxLayout()
hbox = QHBoxLayout() hbox = QHBoxLayout()
@ -181,10 +181,15 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
def on_filename(filename): def on_filename(filename):
path = os.path.join(wallet_folder, filename) path = os.path.join(wallet_folder, filename)
wallet_from_memory = get_wallet_from_daemon(path)
try: 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) self.next_button.setEnabled(True)
except IOError: except BaseException:
traceback.print_exc(file=sys.stderr)
self.storage = None self.storage = None
self.next_button.setEnabled(False) self.next_button.setEnabled(False)
if self.storage: if self.storage:
@ -192,7 +197,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
msg =_("This file does not exist.") + '\n' \ msg =_("This file does not exist.") + '\n' \
+ _("Press 'Next' to create this wallet, or choose another file.") + _("Press 'Next' to create this wallet, or choose another file.")
pw = False pw = False
else: elif not wallet_from_memory:
if self.storage.is_encrypted_with_user_pw(): if self.storage.is_encrypted_with_user_pw():
msg = _("This file is encrypted with a password.") + '\n' \ msg = _("This file is encrypted with a password.") + '\n' \
+ _('Enter your password or choose another file.') + _('Enter your password or choose another file.')
@ -204,6 +209,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
else: else:
msg = _("Press 'Next' to open this wallet.") msg = _("Press 'Next' to open this wallet.")
pw = False pw = False
else:
msg = _("This file is already open in memory.") + "\n" \
+ _("Press 'Next' to create/focus window.")
pw = False
else: else:
msg = _('Cannot read file') msg = _('Cannot read file')
pw = False pw = False
@ -228,6 +237,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
return return
if not self.storage.file_exists(): if not self.storage.file_exists():
break 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.file_exists() and self.storage.is_encrypted():
if self.storage.is_encrypted_with_user_pw(): if self.storage.is_encrypted_with_user_pw():
password = self.pw_e.text() password = self.pw_e.text()
@ -245,14 +257,12 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
try: try:
self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET)
except InvalidPassword as e: except InvalidPassword as e:
# FIXME if we get here because of mistyped passphrase
# then that passphrase gets "cached"
QMessageBox.information( QMessageBox.information(
None, _('Error'), None, _('Error'),
_('Failed to decrypt using this hardware device.') + '\n' + _('Failed to decrypt using this hardware device.') + '\n' +
_('If you use a passphrase, make sure it is correct.')) _('If you use a passphrase, make sure it is correct.'))
self.stack = [] self.stack = []
return self.run_and_get_wallet() return self.run_and_get_wallet(get_wallet_from_daemon)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
QMessageBox.information(None, _('Error'), str(e)) QMessageBox.information(None, _('Error'), str(e))
@ -302,8 +312,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
self.wallet = Wallet(self.storage) self.wallet = Wallet(self.storage)
return self.wallet return self.wallet
def finished(self): def finished(self):
"""Called in hardware client wrapper, in order to close popups.""" """Called in hardware client wrapper, in order to close popups."""
return return
@ -340,7 +348,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
if not result and raise_on_cancel: if not result and raise_on_cancel:
raise UserCancelled raise UserCancelled
if result == 1: if result == 1:
raise GoBack raise GoBack from None
self.title.setVisible(False) self.title.setVisible(False)
self.back_button.setEnabled(False) self.back_button.setEnabled(False)
self.next_button.setEnabled(False) self.next_button.setEnabled(False)

2
gui/qt/invoice_list.py

@ -48,7 +48,7 @@ class InvoiceList(MyTreeWidget):
exp = pr.get_expiration_date() exp = pr.get_expiration_date()
date_str = format_time(exp) if exp else _('Never') 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 = 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.setData(0, Qt.UserRole, key)
item.setFont(1, QFont(MONOSPACE_FONT)) item.setFont(1, QFont(MONOSPACE_FONT))
item.setFont(3, QFont(MONOSPACE_FONT)) item.setFont(3, QFont(MONOSPACE_FONT))

58
gui/qt/main_window.py

@ -47,7 +47,7 @@ from electrum.i18n import _
from electrum.util import (format_time, format_satoshis, PrintError, from electrum.util import (format_time, format_satoshis, PrintError,
format_satoshis_plain, NotEnoughFunds, format_satoshis_plain, NotEnoughFunds,
UserCancelled, NoDynamicFeeEstimates, profiler, UserCancelled, NoDynamicFeeEstimates, profiler,
export_meta, import_meta, bh2u, bfh) export_meta, import_meta, bh2u, bfh, InvalidPassword)
from electrum import Transaction from electrum import Transaction
from electrum import util, bitcoin, commands, coinchooser from electrum import util, bitcoin, commands, coinchooser
from electrum import paymentrequest from electrum import paymentrequest
@ -396,7 +396,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_warning(msg, title=_('Information')) self.show_warning(msg, title=_('Information'))
def open_wallet(self): 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) filename, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
if not filename: if not filename:
return 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) filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder)
if not filename: if not filename:
return return
new_path = os.path.join(wallet_folder, filename) new_path = os.path.join(wallet_folder, filename)
if new_path != path: if new_path != path:
try: try:
shutil.copy2(path, new_path) 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")) 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")) 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): 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())) return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
def new_wallet(self): 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 i = 1
while True: while True:
filename = "wallet_%d" % i filename = "wallet_%d" % i
@ -531,7 +538,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
help_menu = menubar.addMenu(_("&Help")) help_menu = menubar.addMenu(_("&Help"))
help_menu.addAction(_("&About"), self.show_about) 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.addSeparator()
help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents) help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents)
help_menu.addAction(_("&Report Bug"), self.show_report_bug) help_menu.addAction(_("&Report Bug"), self.show_report_bug)
@ -663,8 +670,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
fiat_e.is_last_edited = (edit == fiat_e) fiat_e.is_last_edited = (edit == fiat_e)
amount = edit.get_amount() amount = edit.get_amount()
rate = self.fx.exchange_rate() if self.fx else None rate = self.fx.exchange_rate() if self.fx else Decimal('NaN')
if rate is None or amount is None: if rate.is_nan() or amount is None:
if edit is fiat_e: if edit is fiat_e:
btc_e.setText("") btc_e.setText("")
if fee_e: if fee_e:
@ -1182,7 +1189,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.fee_adv_controls.setVisible(False) self.fee_adv_controls.setVisible(False)
self.preview_button = EnterButton(_("Preview"), self.do_preview) 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.send_button = EnterButton(_("Send"), self.do_send)
self.clear_button = EnterButton(_("Clear"), self.do_clear) self.clear_button = EnterButton(_("Clear"), self.do_clear)
buttons = QHBoxLayout() buttons = QHBoxLayout()
@ -1329,8 +1336,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# actual fees often differ somewhat. # actual fees often differ somewhat.
if freeze_feerate or self.fee_slider.is_active(): if freeze_feerate or self.fee_slider.is_active():
displayed_feerate = self.feerate_e.get_amount() displayed_feerate = self.feerate_e.get_amount()
displayed_feerate = displayed_feerate // 1000 if displayed_feerate else 0 if displayed_feerate:
displayed_fee = displayed_feerate * size 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) self.fee_e.setAmount(displayed_fee)
else: else:
if freeze_fee: if freeze_fee:
@ -1767,8 +1779,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def remove_address(self, addr): def remove_address(self, addr):
if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")): if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
self.wallet.delete_address(addr) self.wallet.delete_address(addr)
self.address_list.update() self.need_update.set() # history, addresses, coins
self.history_list.update()
self.clear_receive_tab() self.clear_receive_tab()
def get_coins(self): def get_coins(self):
@ -1827,6 +1838,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def show_invoice(self, key): def show_invoice(self, key):
pr = self.invoices.get(key) pr = self.invoices.get(key)
if pr is None:
self.show_error('Cannot find payment request in wallet.')
return
pr.verify(self.contacts) pr.verify(self.contacts)
self.show_pr_details(pr) self.show_pr_details(pr)
@ -1968,10 +1982,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return return
try: try:
self.wallet.update_password(old_password, new_password, encrypt_file) self.wallet.update_password(old_password, new_password, encrypt_file)
except BaseException as e: except InvalidPassword as e:
self.show_error(str(e)) self.show_error(str(e))
return return
except: except BaseException:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
self.show_error(_('Failed to update password')) self.show_error(_('Failed to update password'))
return return
@ -2304,7 +2318,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.pay_to_URI(data) self.pay_to_URI(data)
return return
# else if the user scanned an offline signed tx # 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) tx = self.tx_from_text(data)
if not tx: if not tx:
return return
@ -2621,13 +2639,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
msg = '\n'.join([ msg = '\n'.join([
_('Time based: fee rate is based on average confirmation time estimates'), _('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_label = HelpLabel(_('Fee estimation') + ':', msg)
fee_type_combo = QComboBox() fee_type_combo = QComboBox()
fee_type_combo.addItems([_('Static'), _('ETA'), _('Mempool')]) 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): def on_fee_type(x):
self.config.set_key('mempool_fees', x==2) self.config.set_key('mempool_fees', x==2)
self.config.set_key('dynamic_fees', x>0) 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.setChecked(self.config.get('use_rbf', True))
use_rbf_cb.setToolTip( use_rbf_cb.setToolTip(
_('If you check this box, your transactions will be marked as non-final,') + '\n' + \ _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
_('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that 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.')) _('Note that some merchants do not accept non-final transactions until they are confirmed.'))
def on_use_rbf(x): def on_use_rbf(x):
self.config.set_key('use_rbf', x == Qt.Checked) 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'\ msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
+ _('The following alias providers are available:') + '\n'\ + _('The following alias providers are available:') + '\n'\
+ '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\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_label = HelpLabel(_('OpenAlias') + ':', msg)
alias = self.config.get('alias','') alias = self.config.get('alias','')
alias_e = QLineEdit(alias) alias_e = QLineEdit(alias)

9
gui/qt/paytoedit.py

@ -28,6 +28,8 @@ import re
from decimal import Decimal from decimal import Decimal
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
from . import util from . import util
@ -92,9 +94,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
for word in x.split(): for word in x.split():
if word[0:3] == 'OP_': if word[0:3] == 'OP_':
assert word in opcodes.lookup 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: else:
script += push_script(word).decode('hex') bfh(word) # to test it is hex data
script += push_script(word)
return script return script
def parse_amount(self, x): def parse_amount(self, x):

10
gui/qt/qrtextedit.py

@ -46,9 +46,13 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
fileName, __ = QFileDialog.getOpenFileName(self, 'select file') fileName, __ = QFileDialog.getOpenFileName(self, 'select file')
if not fileName: if not fileName:
return return
with open(fileName, "r") as f: try:
data = f.read() with open(fileName, "r") as f:
self.setText(data) data = f.read()
except BaseException as e:
self.show_error(_('Error opening file') + ':\n' + str(e))
else:
self.setText(data)
def qr_input(self): def qr_input(self):
from electrum import qrscanner, get_config from electrum import qrscanner, get_config

4
gui/qt/request_list.py

@ -98,10 +98,10 @@ class RequestList(MyTreeWidget):
amount_str = self.parent.format_amount(amount) if amount else "" amount_str = self.parent.format_amount(amount) if amount else ""
item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')]) item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])
if signature is not None: 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) item.setToolTip(2, 'signed by '+ requestor)
if status is not PR_UNKNOWN: 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) self.addTopLevelItem(item)

9
gui/qt/transaction_dialog.py

@ -179,11 +179,12 @@ class TxDialog(QDialog, MessageBoxMixin):
def sign(self): def sign(self):
def sign_done(success): 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.prompt_if_unsaved = True
self.saved = False self.saved = False
self.save_button.setDisabled(False) self.save_button.setDisabled(False)
self.save_button.setToolTip("") self.save_button.setToolTip("")
self.update() self.update()
self.main_window.pop_top_level_window(self) 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) cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext)
addr = x.get('address') addr = x.get('address')
if addr == "(pubkey)": if addr == "(pubkey)":
_addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n) _addr = self.wallet.get_txin_address(x)
if _addr: if _addr:
addr = _addr addr = _addr
if addr is None: if addr is None:

13
gui/qt/util.py

@ -393,6 +393,8 @@ class MyTreeWidget(QTreeWidget):
self.addChild = self.addTopLevelItem self.addChild = self.addTopLevelItem
self.insertChild = self.insertTopLevelItem self.insertChild = self.insertTopLevelItem
self.icon_cache = IconCache()
# Control which columns are editable # Control which columns are editable
self.editor = None self.editor = None
self.pending_update = False self.pending_update = False
@ -779,6 +781,17 @@ class SortableTreeWidgetItem(QTreeWidgetItem):
return self.text(column) < other.text(column) 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__": if __name__ == "__main__":
app = QApplication([]) app = QApplication([])
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))

51
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 .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 .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption
from .i18n import _ from .i18n import _
from .util import UserCancelled from .util import UserCancelled, InvalidPassword
# hardware device setup purpose # hardware device setup purpose
HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2) HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2)
@ -164,7 +164,7 @@ class BaseWizard(object):
k = keystore.Imported_KeyStore({}) k = keystore.Imported_KeyStore({})
self.storage.put('keystore', k.dump()) self.storage.put('keystore', k.dump())
w = Imported_Wallet(self.storage) w = Imported_Wallet(self.storage)
for x in text.split(): for x in keystore.get_private_keys(text):
w.import_private_key(x, None) w.import_private_key(x, None)
self.keystores.append(w.keystore) self.keystores.append(w.keystore)
else: else:
@ -202,20 +202,32 @@ class BaseWizard(object):
# scan devices # scan devices
devices = [] devices = []
devmgr = self.plugins.device_manager devmgr = self.plugins.device_manager
for name, description, plugin in support: try:
try: scanned_devices = devmgr.scan_devices()
# FIXME: side-effect: unpaired_device_info sets client.handler except BaseException as e:
u = devmgr.unpaired_device_infos(None, plugin) devmgr.print_error('error scanning devices: {}'.format(e))
except: debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
devmgr.print_error("error", name) else:
continue debug_msg = ''
devices += list(map(lambda x: (name, x), u)) 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: if not devices:
msg = ''.join([ msg = ''.join([
_('No hardware device detected.') + '\n', _('No hardware device detected.') + '\n',
_('To trigger a rescan, press \'Next\'.') + '\n\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.') + ' ', _('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)) self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
return return
@ -243,6 +255,9 @@ class BaseWizard(object):
devmgr.unpair_id(device_info.device.id_) devmgr.unpair_id(device_info.device.id_)
self.choose_hw_device(purpose) self.choose_hw_device(purpose)
return return
except UserCancelled:
self.choose_hw_device(purpose)
return
except BaseException as e: except BaseException as e:
self.show_error(str(e)) self.show_error(str(e))
self.choose_hw_device(purpose) self.choose_hw_device(purpose)
@ -259,7 +274,15 @@ class BaseWizard(object):
derivation = get_derivation_used_for_hw_device_encryption() derivation = get_derivation_used_for_hw_device_encryption()
xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self) xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self)
password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) 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: else:
raise Exception('unknown purpose: %s' % purpose) raise Exception('unknown purpose: %s' % purpose)
@ -459,10 +482,6 @@ class BaseWizard(object):
def show_xpub_and_add_cosigners(self, xpub): def show_xpub_and_add_cosigners(self, xpub):
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) 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): def choose_seed_type(self):
title = _('Choose Seed type') title = _('Choose Seed type')
message = ' '.join([ message = ' '.join([

76
lib/bitcoin.py

@ -32,7 +32,7 @@ import json
import ecdsa import ecdsa
import pyaes import pyaes
from .util import bfh, bh2u, to_string from .util import bfh, bh2u, to_string, BitcoinException
from . import version from . import version
from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict
from . import segwit_addr from . import segwit_addr
@ -44,7 +44,7 @@ from . import constants
COINBASE_MATURITY = 100 COINBASE_MATURITY = 100
COIN = 100000000 COIN = 100000000
# supported types of transction outputs # supported types of transaction outputs
TYPE_ADDRESS = 0 TYPE_ADDRESS = 0
TYPE_PUBKEY = 1 TYPE_PUBKEY = 1
TYPE_SCRIPT = 2 TYPE_SCRIPT = 2
@ -144,6 +144,9 @@ def rev_hex(s):
def int_to_hex(i, length=1): def int_to_hex(i, length=1):
assert isinstance(i, int) assert isinstance(i, int)
if i < 0:
# two's complement
i = pow(256, length) + i
s = hex(i)[2:].rstrip('L') s = hex(i)[2:].rstrip('L')
s = "0"*(2*length - len(s)) + s s = "0"*(2*length - len(s)) + s
return rev_hex(s) return rev_hex(s)
@ -261,7 +264,7 @@ def hash_160(public_key):
return md.digest() 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 = bytes([addrtype])
s += h160 s += h160
return base_encode(s+Hash(s)[0:4], base=58) 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] return _bytes[0], _bytes[1:21]
def hash160_to_p2pkh(h160): def hash160_to_p2pkh(h160, *, net=None):
return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2PKH) if net is None:
net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
def hash160_to_p2sh(h160): def hash160_to_p2sh(h160, *, net=None):
return hash160_to_b58_address(h160, constants.net.ADDRTYPE_P2SH) if net is None:
net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
def public_key_to_p2pkh(public_key): def public_key_to_p2pkh(public_key):
return hash160_to_p2pkh(hash_160(public_key)) return hash160_to_p2pkh(hash_160(public_key))
def hash_to_segwit_addr(h): def hash_to_segwit_addr(h, witver, *, net=None):
return segwit_addr.encode(constants.net.SEGWIT_HRP, 0, h) if net is None:
net = constants.net
return segwit_addr.encode(net.SEGWIT_HRP, witver, h)
def public_key_to_p2wpkh(public_key): 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): 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): def p2wpkh_nested_script(pubkey):
pkh = bh2u(hash_160(bfh(pubkey))) pkh = bh2u(hash_160(bfh(pubkey)))
@ -303,7 +312,7 @@ def pubkey_to_address(txin_type, pubkey):
if txin_type == 'p2pkh': if txin_type == 'p2pkh':
return public_key_to_p2pkh(bfh(pubkey)) return public_key_to_p2pkh(bfh(pubkey))
elif txin_type == 'p2wpkh': 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': elif txin_type == 'p2wpkh-p2sh':
scriptSig = p2wpkh_nested_script(pubkey) scriptSig = p2wpkh_nested_script(pubkey)
return hash160_to_p2sh(hash_160(bfh(scriptSig))) 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) raise NotImplementedError(txin_type)
def script_to_address(script): def script_to_address(script, *, net=None):
from .transaction import get_address_from_output_script 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 assert t == TYPE_ADDRESS
return addr return addr
def address_to_script(addr): def address_to_script(addr, *, net=None):
witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr) if net is None:
net = constants.net
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
if witprog is not None: if witprog is not None:
assert (0 <= witver <= 16) assert (0 <= witver <= 16)
OP_n = witver + 0x50 if witver > 0 else 0 OP_n = witver + 0x50 if witver > 0 else 0
@ -337,16 +348,16 @@ def address_to_script(addr):
script += push_script(bh2u(bytes(witprog))) script += push_script(bh2u(bytes(witprog)))
return script return script
addrtype, hash_160 = b58_address_to_hash160(addr) 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 = '76a9' # op_dup, op_hash_160
script += push_script(bh2u(hash_160)) script += push_script(bh2u(hash_160))
script += '88ac' # op_equalverify, op_checksig script += '88ac' # op_equalverify, op_checksig
elif addrtype == constants.net.ADDRTYPE_P2SH: elif addrtype == net.ADDRTYPE_P2SH:
script = 'a9' # op_hash_160 script = 'a9' # op_hash_160
script += push_script(bh2u(hash_160)) script += push_script(bh2u(hash_160))
script += '87' # op_equal script += '87' # op_equal
else: else:
raise BaseException('unknown address type') raise BitcoinException('unknown address type: {}'.format(addrtype))
return script return script
def address_to_scripthash(addr): def address_to_scripthash(addr):
@ -408,7 +419,10 @@ def base_decode(v, length, base):
chars = __b43chars chars = __b43chars
long_value = 0 long_value = 0
for (i, c) in enumerate(v[::-1]): 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() result = bytearray()
while long_value >= 256: while long_value >= 256:
div, mod = divmod(long_value, 256) div, mod = divmod(long_value, 256)
@ -428,6 +442,10 @@ def base_decode(v, length, base):
return bytes(result) return bytes(result)
class InvalidChecksum(Exception):
pass
def EncodeBase58Check(vchIn): def EncodeBase58Check(vchIn):
hash = Hash(vchIn) hash = Hash(vchIn)
return base_encode(vchIn + hash[0:4], base=58) return base_encode(vchIn + hash[0:4], base=58)
@ -440,13 +458,14 @@ def DecodeBase58Check(psz):
hash = Hash(key) hash = Hash(key)
cs32 = hash[0:4] cs32 = hash[0:4]
if cs32 != csum: if cs32 != csum:
return None raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum)))
else: else:
return key return key
# backwards compat # backwards compat
# extended WIF for segwit (used in 3.0.x; but still used internally) # 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 = { SCRIPT_TYPES = {
'p2pkh':0, 'p2pkh':0,
'p2wpkh':1, 'p2wpkh':1,
@ -479,9 +498,12 @@ def deserialize_privkey(key):
if ':' in key: if ':' in key:
txin_type, key = key.split(sep=':', maxsplit=1) txin_type, key = key.split(sep=':', maxsplit=1)
assert txin_type in SCRIPT_TYPES assert txin_type in SCRIPT_TYPES
vch = DecodeBase58Check(key) try:
if not vch: vch = DecodeBase58Check(key)
raise BaseException("cannot deserialize", key) except BaseException:
neutered_privkey = str(key)[:3] + '..' + str(key)[-2:]
raise BitcoinException("cannot deserialize privkey {}"
.format(neutered_privkey))
if txin_type is None: if txin_type is None:
# keys exported in version 3.0.x encoded script type in first byte # 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 net = constants.net
xkey = DecodeBase58Check(xkey) xkey = DecodeBase58Check(xkey)
if len(xkey) != 78: if len(xkey) != 78:
raise BaseException('Invalid length') raise BitcoinException('Invalid length for extended key: {}'
.format(len(xkey)))
depth = xkey[4] depth = xkey[4]
fingerprint = xkey[5:9] fingerprint = xkey[5:9]
child_number = xkey[9:13] child_number = xkey[9:13]
@ -884,7 +907,8 @@ def deserialize_xkey(xkey, prv, *, net=None):
header = int('0x' + bh2u(xkey[0:4]), 16) header = int('0x' + bh2u(xkey[0:4]), 16)
headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS
if header not in headers.values(): 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)] xtype = list(headers.keys())[list(headers.values()).index(header)]
n = 33 if prv else 32 n = 33 if prv else 32
K_or_k = xkey[13+n:] K_or_k = xkey[13+n:]

6
lib/blockchain.py

@ -255,6 +255,10 @@ class Blockchain(util.PrintError):
with open(name, 'rb') as f: with open(name, 'rb') as f:
f.seek(delta * 80) f.seek(delta * 80)
h = f.read(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: if h == bytes([0])*80:
return None return None
return deserialize_header(h, height) return deserialize_header(h, height)
@ -313,6 +317,8 @@ class Blockchain(util.PrintError):
return bitsN << 24 | bitsBase return bitsN << 24 | bitsBase
def can_connect(self, header, check_height=True): def can_connect(self, header, check_height=True):
if header is None:
return False
height = header['block_height'] height = header['block_height']
if check_height and self.height() != height - 1: if check_height and self.height() != height - 1:
#self.print_error("cannot connect at height", height) #self.print_error("cannot connect at height", height)

24
lib/commands.py

@ -34,7 +34,7 @@ from functools import wraps
from decimal import Decimal from decimal import Decimal
from .import util 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 .import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _ from .i18n import _
@ -159,19 +159,13 @@ class Commands:
return True return True
@command('') @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""" """Create a seed"""
from .mnemonic import Mnemonic from .mnemonic import Mnemonic
t = 'segwit' if segwit else 'standard' 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 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') @command('n')
def getaddresshistory(self, address): def getaddresshistory(self, address):
"""Return the transaction history of any address. Note: This is a """Return the transaction history of any address. Note: This is a
@ -207,7 +201,7 @@ class Commands:
keypairs = {} keypairs = {}
inputs = jsontx.get('inputs') inputs = jsontx.get('inputs')
outputs = jsontx.get('outputs') outputs = jsontx.get('outputs')
locktime = jsontx.get('locktime', 0) locktime = jsontx.get('lockTime', 0)
for txin in inputs: for txin in inputs:
if txin.get('output'): if txin.get('output'):
prevout_hash, prevout_n = txin['output'].split(':') 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) tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
if locktime != None: if locktime != None:
tx.locktime = locktime tx.locktime = locktime
if rbf is None:
rbf = self.config.get('use_rbf', True)
if rbf: if rbf:
tx.set_rbf(True) tx.set_rbf(True)
if not unsigned: if not unsigned:
@ -426,7 +422,7 @@ class Commands:
return tx return tx
@command('wp') @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. """ """Create a transaction. """
tx_fee = satoshis(fee) tx_fee = satoshis(fee)
domain = from_addr.split(',') if from_addr else None domain = from_addr.split(',') if from_addr else None
@ -434,7 +430,7 @@ class Commands:
return tx.as_dict() return tx.as_dict()
@command('wp') @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. """ """Create a multi-output transaction. """
tx_fee = satoshis(fee) tx_fee = satoshis(fee)
domain = from_addr.split(',') if from_addr else None domain = from_addr.split(',') if from_addr else None
@ -455,7 +451,7 @@ class Commands:
from .exchange_rate import FxThread from .exchange_rate import FxThread
fx = FxThread(self.config, None) fx = FxThread(self.config, None)
kwargs['fx'] = fx kwargs['fx'] = fx
return self.wallet.get_full_history(**kwargs) return json_encode(self.wallet.get_full_history(**kwargs))
@command('w') @command('w')
def setlabel(self, key, label): 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)."), '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"), '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"), 'nbits': (None, "Number of bits of entropy"),
'entropy': (None, "Custom entropy"),
'segwit': (None, "Create segwit seed"), 'segwit': (None, "Create segwit seed"),
'language': ("-L", "Default language for wordlist"), 'language': ("-L", "Default language for wordlist"),
'privkey': (None, "Private key. Set to '?' to get a prompt."), 'privkey': (None, "Private key. Set to '?' to get a prompt."),
@ -726,7 +721,6 @@ arg_types = {
'nbits': int, 'nbits': int,
'imax': int, 'imax': int,
'year': int, 'year': int,
'entropy': int,
'tx': tx_from_str, 'tx': tx_from_str,
'pubkeys': json_loads, 'pubkeys': json_loads,
'jsontx': json_loads, 'jsontx': json_loads,

2
lib/constants.py

@ -87,7 +87,7 @@ class BitcoinTestnet:
XPUB_HEADERS = { XPUB_HEADERS = {
'standard': 0x043587cf, # tpub 'standard': 0x043587cf, # tpub
'p2wpkh-p2sh': 0x044a5262, # upub 'p2wpkh-p2sh': 0x044a5262, # upub
'p2wsh-p2sh': 0x024285ef, # Upub 'p2wsh-p2sh': 0x024289ef, # Upub
'p2wpkh': 0x045f1cf6, # vpub 'p2wpkh': 0x045f1cf6, # vpub
'p2wsh': 0x02575483, # Vpub 'p2wsh': 0x02575483, # Vpub
} }

11
lib/contacts.py

@ -22,13 +22,14 @@
# SOFTWARE. # SOFTWARE.
import re import re
import dns import dns
from dns.exception import DNSException
import json import json
import traceback import traceback
import sys import sys
from . import bitcoin from . import bitcoin
from . import dnssec from . import dnssec
from .util import export_meta, import_meta from .util import export_meta, import_meta, print_error, to_string
class Contacts(dict): class Contacts(dict):
@ -96,10 +97,14 @@ class Contacts(dict):
def resolve_openalias(self, url): def resolve_openalias(self, url):
# support email-style addresses, per the OA standard # support email-style addresses, per the OA standard
url = url.replace('@', '.') 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' prefix = 'btc'
for record in records: for record in records:
string = record.strings[0] string = to_string(record.strings[0], 'utf8')
if string.startswith('oa1:' + prefix): if string.startswith('oa1:' + prefix):
address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)') address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
name = self.find_regex(string, r'recipient_name=([^;]+)') name = self.find_regex(string, r'recipient_name=([^;]+)')

7
lib/daemon.py

@ -173,7 +173,8 @@ class Daemon(DaemonThread):
elif sub == 'load_wallet': elif sub == 'load_wallet':
path = config.get_wallet_path() path = config.get_wallet_path()
wallet = self.load_wallet(path, config.get('password')) 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 response = wallet is not None
elif sub == 'close_wallet': elif sub == 'close_wallet':
path = config.get_wallet_path() path = config.get_wallet_path()
@ -185,6 +186,9 @@ class Daemon(DaemonThread):
elif sub == 'status': elif sub == 'status':
if self.network: if self.network:
p = self.network.get_parameters() p = self.network.get_parameters()
current_wallet = self.cmd_runner.wallet
current_wallet_path = current_wallet.storage.path \
if current_wallet else None
response = { response = {
'path': self.network.config.path, 'path': self.network.config.path,
'server': p[0], 'server': p[0],
@ -196,6 +200,7 @@ class Daemon(DaemonThread):
'version': ELECTRUM_VERSION, 'version': ELECTRUM_VERSION,
'wallets': {k: w.is_up_to_date() 'wallets': {k: w.is_up_to_date()
for k, w in self.wallets.items()}, for k, w in self.wallets.items()},
'current_wallet': current_wallet_path,
'fee_per_kb': self.config.fee_per_kb(), 'fee_per_kb': self.config.fee_per_kb(),
} }
else: else:

8
lib/exchange_rate.py

@ -66,7 +66,7 @@ class ExchangeBase(PrintError):
if os.path.exists(filename): if os.path.exists(filename):
timestamp = os.stat(filename).st_mtime timestamp = os.stat(filename).st_mtime
try: try:
with open(filename, 'r') as f: with open(filename, 'r', encoding='utf-8') as f:
h = json.loads(f.read()) h = json.loads(f.read())
h['timestamp'] = timestamp h['timestamp'] = timestamp
except: except:
@ -87,7 +87,7 @@ class ExchangeBase(PrintError):
self.print_error("failed fx history:", e) self.print_error("failed fx history:", e)
return return
filename = os.path.join(cache_dir, self.name() + '_' + ccy) 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)) f.write(json.dumps(h))
h['timestamp'] = time.time() h['timestamp'] = time.time()
self.history[ccy] = h self.history[ccy] = h
@ -382,7 +382,7 @@ def get_exchanges_and_currencies():
import os, json import os, json
path = os.path.join(os.path.dirname(__file__), 'currencies.json') path = os.path.join(os.path.dirname(__file__), 'currencies.json')
try: try:
with open(path, 'r') as f: with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read()) return json.loads(f.read())
except: except:
pass pass
@ -399,7 +399,7 @@ def get_exchanges_and_currencies():
except: except:
print(name, "error") print(name, "error")
continue 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)) f.write(json.dumps(d, indent=4, sort_keys=True))
return d return d

14
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) context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path)
s = context.wrap_socket(s, do_handshake_on_connect=True) s = context.wrap_socket(s, do_handshake_on_connect=True)
except ssl.SSLError as e: except ssl.SSLError as e:
print_error(e) self.print_error(e)
s = None s = None
except: except:
return return
@ -172,8 +172,10 @@ class TcpConnection(threading.Thread, util.PrintError):
# workaround android bug # workaround android bug
cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert) cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert)
temporary_path = cert_path + '.temp' 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.write(cert)
f.flush()
os.fsync(f.fileno())
else: else:
is_new = False is_new = False
@ -199,7 +201,7 @@ class TcpConnection(threading.Thread, util.PrintError):
os.unlink(rej) os.unlink(rej)
os.rename(temporary_path, rej) os.rename(temporary_path, rej)
else: else:
with open(cert_path) as f: with open(cert_path, encoding='utf-8') as f:
cert = f.read() cert = f.read()
try: try:
b = pem.dePem(cert, 'CERTIFICATE') b = pem.dePem(cert, 'CERTIFICATE')
@ -295,8 +297,8 @@ class Interface(util.PrintError):
wire_requests = self.unsent_requests[0:n] wire_requests = self.unsent_requests[0:n]
try: try:
self.pipe.send_all([make_dict(*r) for r in wire_requests]) self.pipe.send_all([make_dict(*r) for r in wire_requests])
except socket.error as e: except BaseException as e:
self.print_error("socket error:", e) self.print_error("pipe send error:", e)
return False return False
self.unsent_requests = self.unsent_requests[n:] self.unsent_requests = self.unsent_requests[n:]
for request in wire_requests: for request in wire_requests:
@ -396,7 +398,7 @@ def test_certificates():
certs = os.listdir(mydir) certs = os.listdir(mydir)
for c in certs: for c in certs:
p = os.path.join(mydir,c) p = os.path.join(mydir,c)
with open(p) as f: with open(p, encoding='utf-8') as f:
cert = f.read() cert = f.read()
check_cert(c, cert) check_cert(c, cert)

37
lib/keystore.py

@ -29,7 +29,8 @@ from unicodedata import normalize
from . import bitcoin from . import bitcoin
from .bitcoin import * from .bitcoin import *
from . import constants from . import constants
from .util import PrintError, InvalidPassword, hfu from .util import (PrintError, InvalidPassword, hfu, WalletFileException,
BitcoinException)
from .mnemonic import Mnemonic, load_wordlist from .mnemonic import Mnemonic, load_wordlist
from .plugins import run_hook from .plugins import run_hook
@ -75,6 +76,8 @@ class KeyStore(PrintError):
return False return False
return bool(self.get_tx_derivations(tx)) return bool(self.get_tx_derivations(tx))
def ready_to_sign(self):
return not self.is_watching_only()
class Software_KeyStore(KeyStore): class Software_KeyStore(KeyStore):
@ -142,6 +145,10 @@ class Imported_KeyStore(Software_KeyStore):
# re-serialize the key so the internal storage format is consistent # re-serialize the key so the internal storage format is consistent
serialized_privkey = serialize_privkey( serialized_privkey = serialize_privkey(
privkey, compressed, txin_type, internal_use=True) 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) self.keypairs[pubkey] = pw_encode(serialized_privkey, password)
return txin_type, pubkey return txin_type, pubkey
@ -531,6 +538,17 @@ class Hardware_KeyStore(KeyStore, Xpub):
password = self.get_pubkey_from_xpub(xpub, ()) password = self.get_pubkey_from_xpub(xpub, ())
return password 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): def bip39_normalize_passphrase(passphrase):
return normalize('NFKD', passphrase or '') return normalize('NFKD', passphrase or '')
@ -615,7 +633,8 @@ def xpubkey_to_address(x_pubkey):
mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
else: else:
raise BaseException("Cannot parse pubkey") raise BitcoinException("Cannot parse pubkey. prefix: {}"
.format(x_pubkey[0:2]))
if pubkey: if pubkey:
address = public_key_to_p2pkh(bfh(pubkey)) address = public_key_to_p2pkh(bfh(pubkey))
return pubkey, address return pubkey, address
@ -634,14 +653,15 @@ def hardware_keystore(d):
if hw_type in hw_keystores: if hw_type in hw_keystores:
constructor = hw_keystores[hw_type] constructor = hw_keystores[hw_type]
return constructor(d) return constructor(d)
raise BaseException('unknown hardware type', hw_type) raise WalletFileException('unknown hardware type: {}'.format(hw_type))
def load_keystore(storage, name): def load_keystore(storage, name):
w = storage.get('wallet_type', 'standard')
d = storage.get(name, {}) d = storage.get(name, {})
t = d.get('type') t = d.get('type')
if not t: 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': if t == 'old':
k = Old_KeyStore(d) k = Old_KeyStore(d)
elif t == 'imported': elif t == 'imported':
@ -651,7 +671,8 @@ def load_keystore(storage, name):
elif t == 'hardware': elif t == 'hardware':
k = hardware_keystore(d) k = hardware_keystore(d)
else: else:
raise BaseException('unknown wallet type', t) raise WalletFileException(
'Unknown type {} for keystore named {}'.format(t, name))
return k return k
@ -709,7 +730,7 @@ def from_seed(seed, passphrase, is_p2sh):
xtype = 'p2wsh' if is_p2sh else 'p2wpkh' xtype = 'p2wsh' if is_p2sh else 'p2wpkh'
keystore.add_xprv_from_seed(bip32_seed, xtype, der) keystore.add_xprv_from_seed(bip32_seed, xtype, der)
else: else:
raise BaseException(t) raise BitcoinException('Unexpected seed type {}'.format(t))
return keystore return keystore
def from_private_key_list(text): def from_private_key_list(text):
@ -743,5 +764,5 @@ def from_master_key(text):
elif is_xpub(text): elif is_xpub(text):
k = from_xpub(text) k = from_xpub(text)
else: else:
raise BaseException('Invalid key') raise BitcoinException('Invalid master key')
return k return k

25
lib/mnemonic.py

@ -91,7 +91,7 @@ def normalize_text(seed):
def load_wordlist(filename): def load_wordlist(filename):
path = os.path.join(os.path.dirname(__file__), '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 = f.read().strip()
s = unicodedata.normalize('NFKD', s) s = unicodedata.normalize('NFKD', s)
lines = s.split('\n') lines = s.split('\n')
@ -157,28 +157,21 @@ class Mnemonic(object):
i = i*n + k i = i*n + k
return i return i
def check_seed(self, seed, custom_entropy): def make_seed(self, seed_type='standard', num_bits=132):
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):
prefix = version.seed_prefix(seed_type) prefix = version.seed_prefix(seed_type)
# increase num_bits in order to obtain a uniform distibution for the last word # increase num_bits in order to obtain a uniform distibution for the last word
bpw = math.log(len(self.wordlist), 2) bpw = math.log(len(self.wordlist), 2)
num_bits = int(math.ceil(num_bits/bpw) * bpw) # rounding
# handle custom entropy; make sure we add at least 16 bits n = int(math.ceil(num_bits/bpw) * bpw)
n_custom = int(math.ceil(math.log(custom_entropy, 2))) print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n)
n = max(16, num_bits - n_custom) entropy = 1
print_error("make_seed", prefix, "adding %d bits"%n) while entropy < pow(2, n - bpw):
my_entropy = 1
while my_entropy < pow(2, n - bpw):
# try again if seed would not contain enough words # 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 nonce = 0
while True: while True:
nonce += 1 nonce += 1
i = custom_entropy * (my_entropy + nonce) i = entropy + nonce
seed = self.mnemonic_encode(i) seed = self.mnemonic_encode(i)
assert i == self.mnemonic_decode(seed) assert i == self.mnemonic_decode(seed)
if is_old_seed(seed): if is_old_seed(seed):

12
lib/network.py

@ -246,7 +246,7 @@ class Network(util.DaemonThread):
return [] return []
path = os.path.join(self.config.path, "recent_servers") path = os.path.join(self.config.path, "recent_servers")
try: try:
with open(path, "r") as f: with open(path, "r", encoding='utf-8') as f:
data = f.read() data = f.read()
return json.loads(data) return json.loads(data)
except: except:
@ -258,7 +258,7 @@ class Network(util.DaemonThread):
path = os.path.join(self.config.path, "recent_servers") path = os.path.join(self.config.path, "recent_servers")
s = json.dumps(self.recent_servers, indent=4, sort_keys=True) s = json.dumps(self.recent_servers, indent=4, sort_keys=True)
try: try:
with open(path, "w") as f: with open(path, "w", encoding='utf-8') as f:
f.write(s) f.write(s)
except: except:
pass pass
@ -319,7 +319,7 @@ class Network(util.DaemonThread):
self.queue_request('server.peers.subscribe', []) self.queue_request('server.peers.subscribe', [])
self.request_fee_estimates() self.request_fee_estimates()
self.queue_request('blockchain.relayfee', []) 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]) self.queue_request('blockchain.scripthash.subscribe', [h])
def request_fee_estimates(self): def request_fee_estimates(self):
@ -563,7 +563,7 @@ class Network(util.DaemonThread):
self.notify('fee') self.notify('fee')
elif method == 'blockchain.relayfee': elif method == 'blockchain.relayfee':
if error is None: 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) self.print_error("relayfee", self.relay_fee)
elif method == 'blockchain.block.get_chunk': elif method == 'blockchain.block.get_chunk':
self.on_get_chunk(interface, response) self.on_get_chunk(interface, response)
@ -677,7 +677,7 @@ class Network(util.DaemonThread):
# check cached response for subscriptions # check cached response for subscriptions
r = self.sub_cache.get(k) r = self.sub_cache.get(k)
if r is not None: if r is not None:
util.print_error("cache hit", k) self.print_error("cache hit", k)
callback(r) callback(r)
else: else:
message_id = self.queue_request(method, params) message_id = self.queue_request(method, params)
@ -1089,7 +1089,7 @@ class Network(util.DaemonThread):
def export_checkpoints(self, path): def export_checkpoints(self, path):
# run manually from the console to generate checkpoints # run manually from the console to generate checkpoints
cp = self.blockchain().get_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)) f.write(json.dumps(cp, indent=4))
def max_checkpoint(self): def max_checkpoint(self):

16
lib/paymentrequest.py

@ -89,7 +89,7 @@ def get_payment_request(url):
error = "payment URL not pointing to a valid server" error = "payment URL not pointing to a valid server"
elif u.scheme == 'file': elif u.scheme == 'file':
try: try:
with open(u.path, 'r') as f: with open(u.path, 'r', encoding='utf-8') as f:
data = f.read() data = f.read()
except IOError: except IOError:
data = None data = None
@ -385,9 +385,9 @@ def check_ssl_config(config):
from . import pem from . import pem
key_path = config.get('ssl_privkey') key_path = config.get('ssl_privkey')
cert_path = config.get('ssl_chain') 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()) 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() s = f.read()
bList = pem.dePemList(s, "CERTIFICATE") bList = pem.dePemList(s, "CERTIFICATE")
# verify chain # verify chain
@ -405,10 +405,10 @@ def check_ssl_config(config):
def sign_request_with_x509(pr, key_path, cert_path): def sign_request_with_x509(pr, key_path, cert_path):
from . import pem 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()) params = pem.parse_private_key(f.read())
privkey = rsakey.RSAKey(*params) privkey = rsakey.RSAKey(*params)
with open(cert_path, 'r') as f: with open(cert_path, 'r', encoding='utf-8') as f:
s = f.read() s = f.read()
bList = pem.dePemList(s, "CERTIFICATE") bList = pem.dePemList(s, "CERTIFICATE")
certificates = pb2.X509Certificates() certificates = pb2.X509Certificates()
@ -453,7 +453,11 @@ class InvoiceStore(object):
def set_paid(self, pr, txid): def set_paid(self, pr, txid):
pr.tx = 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): def load(self, d):
for k, v in d.items(): for k, v in d.items():

10
lib/plot.py

@ -1,17 +1,13 @@
from PyQt5.QtGui import *
from electrum.i18n import _
import datetime import datetime
from collections import defaultdict from collections import defaultdict
from electrum.bitcoin import COIN
import matplotlib import matplotlib
matplotlib.use('Qt5Agg') matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.dates as md 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): class NothingToPlotException(Exception):

52
lib/plugins.py

@ -461,12 +461,14 @@ class DeviceMgr(ThreadJob, PrintError):
def unpaired_device_infos(self, handler, plugin, devices=None): def unpaired_device_infos(self, handler, plugin, devices=None):
'''Returns a list of DeviceInfo objects: one for each connected, '''Returns a list of DeviceInfo objects: one for each connected,
unpaired device accepted by the plugin.''' unpaired device accepted by the plugin.'''
if not plugin.libraries_available:
raise Exception('Missing libraries for {}'.format(plugin.name))
if devices is None: if devices is None:
devices = self.scan_devices() devices = self.scan_devices()
devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)] devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
infos = [] infos = []
for device in devices: for device in devices:
if not device.product_key in plugin.DEVICE_IDS: if device.product_key not in plugin.DEVICE_IDS:
continue continue
client = self.create_client(device, handler, plugin) client = self.create_client(device, handler, plugin)
if not client: if not client:
@ -482,9 +484,14 @@ class DeviceMgr(ThreadJob, PrintError):
infos = self.unpaired_device_infos(handler, plugin, devices) infos = self.unpaired_device_infos(handler, plugin, devices)
if infos: if infos:
break break
msg = _('Please insert your {}. Verify the cable is ' msg = _('Please insert your {}').format(plugin.device)
'connected and that no other application is using it.\n\n' if keystore.label:
'Try to connect again?').format(plugin.device) 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): if not handler.yes_no_question(msg):
raise UserCancelled() raise UserCancelled()
devices = None devices = None
@ -495,7 +502,7 @@ class DeviceMgr(ThreadJob, PrintError):
if info.label == keystore.label: if info.label == keystore.label:
return info return info
msg = _("Please select which {} device to use:").format(plugin.device) 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) c = handler.query_choice(msg, descriptions)
if c is None: if c is None:
raise UserCancelled() raise UserCancelled()
@ -506,17 +513,15 @@ class DeviceMgr(ThreadJob, PrintError):
handler.win.wallet.save_keystore() handler.win.wallet.save_keystore()
return info return info
def scan_devices(self): def _scan_devices_with_hid(self):
# All currently supported hardware libraries use hid, so we try:
# assume it here. This can be easily abstracted if necessary. import hid
# Note this import must be local so those without hardware except ImportError:
# wallet libraries are not affected. return []
import hid
self.print_error("scanning devices...")
with self.hid_lock: with self.hid_lock:
hid_list = hid.enumerate(0, 0) hid_list = hid.enumerate(0, 0)
# First see what's connected that we know about
devices = [] devices = []
for d in hid_list: for d in hid_list:
product_key = (d['vendor_id'], d['product_id']) product_key = (d['vendor_id'], d['product_id'])
@ -530,18 +535,31 @@ class DeviceMgr(ThreadJob, PrintError):
id_ += str(interface_number) + str(usage_page) id_ += str(interface_number) + str(usage_page)
devices.append(Device(d['path'], interface_number, devices.append(Device(d['path'], interface_number,
id_, product_key, usage_page)) 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 # Let plugin handlers enumerate devices we don't know about
for f in self.enumerate_func: for f in self.enumerate_func:
devices.extend(f()) try:
new_devices = f()
# Now find out what was disconnected 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] pairs = [(dev.path, dev.id_) for dev in devices]
disconnected_ids = [] disconnected_ids = []
with self.lock: with self.lock:
connected = {} connected = {}
for client, pair in self.clients.items(): 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 connected[client] = pair
else: else:
disconnected_ids.append(pair[1]) disconnected_ids.append(pair[1])

2
lib/qrscanner.py

@ -36,7 +36,7 @@ else:
try: try:
libzbar = ctypes.cdll.LoadLibrary(name) libzbar = ctypes.cdll.LoadLibrary(name)
except OSError: except BaseException:
libzbar = None libzbar = None

17
lib/simple_config.py

@ -211,9 +211,14 @@ class SimpleConfig(PrintError):
return return
path = os.path.join(self.path, "config") path = os.path.join(self.path, "config")
s = json.dumps(self.user_config, indent=4, sort_keys=True) s = json.dumps(self.user_config, indent=4, sort_keys=True)
with open(path, "w") as f: try:
f.write(s) with open(path, "w", encoding='utf-8') as f:
os.chmod(path, stat.S_IREAD | stat.S_IWRITE) 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): def get_wallet_path(self):
"""Set the path of the wallet.""" """Set the path of the wallet."""
@ -228,6 +233,10 @@ class SimpleConfig(PrintError):
return path return path
# default 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") dirpath = os.path.join(self.path, "wallets")
if not os.path.exists(dirpath): if not os.path.exists(dirpath):
if os.path.islink(dirpath): if os.path.islink(dirpath):
@ -489,7 +498,7 @@ def read_user_config(path):
if not os.path.exists(config_path): if not os.path.exists(config_path):
return {} return {}
try: try:
with open(config_path, "r") as f: with open(config_path, "r", encoding='utf-8') as f:
data = f.read() data = f.read()
result = json.loads(data) result = json.loads(data)
except: except:

32
lib/storage.py

@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib
import base64 import base64
import zlib import zlib
from .util import PrintError, profiler, InvalidPassword from .util import PrintError, profiler, InvalidPassword, WalletFileException
from .plugins import run_hook, plugin_loaders from .plugins import run_hook, plugin_loaders
from .keystore import bip44_derivation from .keystore import bip44_derivation
from . import bitcoin from . import bitcoin
@ -51,6 +51,8 @@ FINAL_SEED_VERSION = 16 # electrum >= 2.7 will set this to prevent
def multisig_type(wallet_type): def multisig_type(wallet_type):
'''If wallet_type is mofn multi-sig, return [m, n], '''If wallet_type is mofn multi-sig, return [m, n],
otherwise return None.''' otherwise return None.'''
if not wallet_type:
return None
match = re.match('(\d+)of(\d+)', wallet_type) match = re.match('(\d+)of(\d+)', wallet_type)
if match: if match:
match = [int(x) for x in match.group(1, 2)] match = [int(x) for x in match.group(1, 2)]
@ -75,7 +77,7 @@ class WalletStorage(PrintError):
self.modified = False self.modified = False
self.pubkey = None self.pubkey = None
if self.file_exists(): 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.raw = f.read()
self._encryption_version = self._init_encryption_version() self._encryption_version = self._init_encryption_version()
if not self.is_encrypted(): if not self.is_encrypted():
@ -111,7 +113,7 @@ class WalletStorage(PrintError):
if not self.manual_upgrades: if not self.manual_upgrades:
if self.requires_split(): 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(): if self.requires_upgrade():
self.upgrade() self.upgrade()
@ -172,7 +174,7 @@ class WalletStorage(PrintError):
elif v == STO_EV_XPUB_PW: elif v == STO_EV_XPUB_PW:
return b'BIE2' return b'BIE2'
else: else:
raise Exception('no encryption magic for version: %s' % v) raise WalletFileException('no encryption magic for version: %s' % v)
def decrypt(self, password): def decrypt(self, password):
ec_key = self.get_key(password) ec_key = self.get_key(password)
@ -255,7 +257,7 @@ class WalletStorage(PrintError):
s = s.decode('utf8') s = s.decode('utf8')
temp_path = "%s.tmp.%s" % (self.path, os.getpid()) 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.write(s)
f.flush() f.flush()
os.fsync(f.fileno()) os.fsync(f.fileno())
@ -318,7 +320,7 @@ class WalletStorage(PrintError):
storage2.write() storage2.write()
result.append(new_path) result.append(new_path)
else: 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 return result
def requires_upgrade(self): def requires_upgrade(self):
@ -417,7 +419,7 @@ class WalletStorage(PrintError):
d['seed'] = seed d['seed'] = seed
self.put(key, d) self.put(key, d)
else: else:
raise raise WalletFileException('Unable to tell wallet type. Is this even a wallet file?')
# remove junk # remove junk
self.put('master_public_key', None) self.put('master_public_key', None)
self.put('master_public_keys', None) self.put('master_public_keys', None)
@ -541,7 +543,7 @@ class WalletStorage(PrintError):
else: else:
addresses.append(addr) addresses.append(addr)
if addresses and keypairs: if addresses and keypairs:
raise BaseException('mixed addresses and privkeys') raise WalletFileException('mixed addresses and privkeys')
elif addresses: elif addresses:
self.put('addresses', addresses) self.put('addresses', addresses)
self.put('accounts', None) self.put('accounts', None)
@ -551,7 +553,7 @@ class WalletStorage(PrintError):
self.put('keypairs', keypairs) self.put('keypairs', keypairs)
self.put('accounts', None) self.put('accounts', None)
else: else:
raise BaseException('no addresses or privkeys') raise WalletFileException('no addresses or privkeys')
def convert_account(self): def convert_account(self):
if not self._is_upgrade_method_needed(0, 13): if not self._is_upgrade_method_needed(0, 13):
@ -564,9 +566,9 @@ class WalletStorage(PrintError):
if cur_version > max_version: if cur_version > max_version:
return False return False
elif cur_version < min_version: elif cur_version < min_version:
raise BaseException( raise WalletFileException(
('storage upgrade: unexpected version %d (should be %d-%d)' 'storage upgrade: unexpected version {} (should be {}-{})'
% (cur_version, min_version, max_version))) .format(cur_version, min_version, max_version))
else: else:
return True return True
@ -582,7 +584,9 @@ class WalletStorage(PrintError):
if not seed_version: if not seed_version:
seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_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: 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': if seed_version==14 and self.get('seed_type') == 'segwit':
self.raise_unsupported_version(seed_version) self.raise_unsupported_version(seed_version)
if seed_version >=12: if seed_version >=12:
@ -605,4 +609,4 @@ class WalletStorage(PrintError):
else: else:
# creation was complete if electrum was run from source # 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." msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet."
raise BaseException(msg) raise WalletFileException(msg)

14
lib/synchronizer.py

@ -50,6 +50,8 @@ class Synchronizer(ThreadJob):
self.requested_histories = {} self.requested_histories = {}
self.requested_addrs = set() self.requested_addrs = set()
self.lock = Lock() self.lock = Lock()
self.initialized = False
self.initialize() self.initialize()
def parse_response(self, response): def parse_response(self, response):
@ -84,7 +86,7 @@ class Synchronizer(ThreadJob):
return bh2u(hashlib.sha256(status.encode('ascii')).digest()) return bh2u(hashlib.sha256(status.encode('ascii')).digest())
def on_address_status(self, response): 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 return # we have been killed, this was just an orphan callback
params, result = self.parse_response(response) params, result = self.parse_response(response)
if not params: if not params:
@ -100,14 +102,17 @@ class Synchronizer(ThreadJob):
self.requested_addrs.remove(addr) self.requested_addrs.remove(addr)
def on_address_history(self, response): 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 return # we have been killed, this was just an orphan callback
params, result = self.parse_response(response) params, result = self.parse_response(response)
if not params: if not params:
return return
addr = params[0] 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)) self.print_error("receiving history", addr, len(result))
server_status = self.requested_histories[addr]
hashes = set(map(lambda item: item['tx_hash'], result)) hashes = set(map(lambda item: item['tx_hash'], result))
hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result))
# tx_fees # tx_fees
@ -131,7 +136,7 @@ class Synchronizer(ThreadJob):
self.requested_histories.pop(addr) self.requested_histories.pop(addr)
def tx_response(self, response): 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 return # we have been killed, this was just an orphan callback
params, result = self.parse_response(response) params, result = self.parse_response(response)
if not params: if not params:
@ -183,6 +188,7 @@ class Synchronizer(ThreadJob):
if self.requested_tx: if self.requested_tx:
self.print_error("missing tx", self.requested_tx) self.print_error("missing tx", self.requested_tx)
self.subscribe_to_addresses(set(self.wallet.get_addresses())) self.subscribe_to_addresses(set(self.wallet.get_addresses()))
self.initialized = True
def run(self): def run(self):
'''Called from the network proxy thread main loop.''' '''Called from the network proxy thread main loop.'''

16
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()

90
lib/tests/test_bitcoin.py

@ -11,10 +11,13 @@ from lib.bitcoin import (
var_int, op_push, address_to_script, regenerate_key, var_int, op_push, address_to_script, regenerate_key,
verify_message, deserialize_privkey, serialize_privkey, is_segwit_address, verify_message, deserialize_privkey, serialize_privkey, is_segwit_address,
is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub, 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.util import bfh
from lib import constants from lib import constants
from . import TestCaseForTestnet
try: try:
import ecdsa import ecdsa
except ImportError: except ImportError:
@ -164,17 +167,7 @@ class Test_bitcoin(unittest.TestCase):
self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387')
class Test_bitcoin_testnet(unittest.TestCase): class Test_bitcoin_testnet(TestCaseForTestnet):
@classmethod
def setUpClass(cls):
super().setUpClass()
constants.set_testnet()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
constants.set_mainnet()
def test_address_to_script(self): def test_address_to_script(self):
# bech32 native segwit # bech32 native segwit
@ -267,6 +260,79 @@ class Test_xprv_xpub(unittest.TestCase):
self.assertFalse(is_bip32_derivation("")) self.assertFalse(is_bip32_derivation(""))
self.assertFalse(is_bip32_derivation("m/q8462")) 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): class Test_keyImport(unittest.TestCase):

588
lib/tests/test_transaction.py

@ -1,10 +1,9 @@
import unittest import unittest
from lib import transaction from lib import transaction
from lib.bitcoin import TYPE_ADDRESS from lib.bitcoin import TYPE_ADDRESS
from lib.keystore import xpubkey_to_address from lib.keystore import xpubkey_to_address
from lib.util import bh2u, bfh
from lib.util import bh2u
unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
@ -167,450 +166,593 @@ class TestTransaction(unittest.TestCase):
tx = transaction.Transaction(v2_blob) tx = transaction.Transaction(v2_blob)
self.assertEqual(tx.txid(), "b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe") 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): def test_txid_coinbase_to_p2pk(self):
tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000') raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000'
self.assertEqual('dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10', tx.txid()) txid = 'dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_coinbase_to_p2pkh(self): def test_txid_coinbase_to_p2pkh(self):
tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000') raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000'
self.assertEqual('4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8', tx.txid()) txid = '4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_segwit_coinbase_to_p2pk(self): def test_txid_segwit_coinbase_to_p2pk(self):
tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000'
self.assertEqual('fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301', tx.txid()) txid = 'fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_segwit_coinbase_to_p2pkh(self): def test_txid_segwit_coinbase_to_p2pkh(self):
tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000') raw_tx = '020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000'
self.assertEqual('ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e', tx.txid()) txid = 'ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pk_to_p2pkh(self): def test_txid_p2pk_to_p2pkh(self):
tx = transaction.Transaction('010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000') raw_tx = '010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000'
self.assertEqual('90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9', tx.txid()) txid = '90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pk_to_p2sh(self): def test_txid_p2pk_to_p2sh(self):
tx = transaction.Transaction('0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000') raw_tx = '0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000'
self.assertEqual('172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5', tx.txid()) txid = '172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pk_to_p2wpkh(self): def test_txid_p2pk_to_p2wpkh(self):
tx = transaction.Transaction('01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000') raw_tx = '01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000'
self.assertEqual('ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d', tx.txid()) txid = 'ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pkh_to_p2pkh(self): def test_txid_p2pkh_to_p2pkh(self):
tx = transaction.Transaction('0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000') raw_tx = '0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000'
self.assertEqual('24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce', tx.txid()) txid = '24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pkh_to_p2sh(self): def test_txid_p2pkh_to_p2sh(self):
tx = transaction.Transaction('010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000') raw_tx = '010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000'
self.assertEqual('155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc', tx.txid()) txid = '155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2pkh_to_p2wpkh(self): def test_txid_p2pkh_to_p2wpkh(self):
tx = transaction.Transaction('0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000') raw_tx = '0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000'
self.assertEqual('ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c', tx.txid()) txid = 'ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2sh_to_p2pkh(self): def test_txid_p2sh_to_p2pkh(self):
tx = transaction.Transaction('01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000') raw_tx = '01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000'
self.assertEqual('17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986', tx.txid()) txid = '17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2sh_to_p2sh(self): def test_txid_p2sh_to_p2sh(self):
tx = transaction.Transaction('01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000') raw_tx = '01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000'
self.assertEqual('ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7', tx.txid()) txid = 'ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2sh_to_p2wpkh(self): def test_txid_p2sh_to_p2wpkh(self):
tx = transaction.Transaction('010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000') raw_tx = '010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000'
self.assertEqual('6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3', tx.txid()) txid = '6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2wpkh_to_p2pkh(self): def test_txid_p2wpkh_to_p2pkh(self):
tx = transaction.Transaction('0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000') raw_tx = '0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000'
self.assertEqual('c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc', tx.txid()) txid = 'c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2wpkh_to_p2sh(self): def test_txid_p2wpkh_to_p2sh(self):
tx = transaction.Transaction('010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000') raw_tx = '010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000'
self.assertEqual('390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9', tx.txid()) txid = '390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_p2wpkh_to_p2wpkh(self): def test_txid_p2wpkh_to_p2wpkh(self):
tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000') raw_tx = '010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000'
self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid()) txid = '51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_input_p2wsh_p2sh_not_multisig(self): def test_txid_input_p2wsh_p2sh_not_multisig(self):
tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000') raw_tx = '0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000'
self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid()) txid = 'e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d'
self._run_naive_tests_on_tx(raw_tx, txid)
# input: p2sh, not multisig # input: p2sh, not multisig
def test_txid_regression_issue_3899(self): def test_txid_regression_issue_3899(self):
tx = transaction.Transaction('0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000') raw_tx = '0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000'
self.assertEqual('f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d', tx.txid()) 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 ---> # these transactions are from Bitcoin Core unit tests --->
# https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json # https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json
def test_txid_bitcoin_core_0001(self): def test_txid_bitcoin_core_0001(self):
tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
self.assertEqual('23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63', tx.txid()) txid = '23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0002(self): def test_txid_bitcoin_core_0002(self):
tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
self.assertEqual('fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed', tx.txid()) txid = 'fcabc409d8e685da28536e1e5ccc91264d755cd4c57ed4cae3dbaa4d3b93e8ed'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0003(self): def test_txid_bitcoin_core_0003(self):
tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
self.assertEqual('c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575', tx.txid()) txid = 'c9aa95f2c48175fdb70b34c23f1c3fc44f869b073a6f79b1343fbce30c3cb575'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0004(self): def test_txid_bitcoin_core_0004(self):
tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000495147304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
self.assertEqual('da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2', tx.txid()) txid = 'da94fda32b55deb40c3ed92e135d69df7efc4ee6665e0beb07ef500f407c9fd2'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0005(self): def test_txid_bitcoin_core_0005(self):
tx = transaction.Transaction('0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000') raw_tx = '0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000494f47304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000'
self.assertEqual('f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86', tx.txid()) txid = 'f76f897b206e4f78d60fe40f2ccb542184cfadc34354d3bb9bdc30cc2f432b86'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0006(self): def test_txid_bitcoin_core_0006(self):
tx = transaction.Transaction('01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000') raw_tx = '01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000'
self.assertEqual('c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73', tx.txid()) txid = 'c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0007(self): def test_txid_bitcoin_core_0007(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000'
self.assertEqual('e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092', tx.txid()) txid = 'e41ffe19dff3cbedb413a2ca3fbbcd05cb7fd7397ffa65052f8928aa9c700092'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0008(self): def test_txid_bitcoin_core_0008(self):
tx = transaction.Transaction('01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000') raw_tx = '01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000'
self.assertEqual('f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb', tx.txid()) txid = 'f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0009(self): def test_txid_bitcoin_core_0009(self):
tx = transaction.Transaction('01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000') raw_tx = '01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000'
self.assertEqual('b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5', tx.txid()) txid = 'b56471690c3ff4f7946174e51df68b47455a0d29344c351377d712e6d00eabe5'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0010(self): def test_txid_bitcoin_core_0010(self):
tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000') raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000'
self.assertEqual('99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4', tx.txid()) txid = '99517e5b47533453cc7daa332180f578be68b80370ecfe84dbfff7f19d791da4'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0011(self): def test_txid_bitcoin_core_0011(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000'
self.assertEqual('ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea', tx.txid()) txid = 'ab097537b528871b9b64cb79a769ae13c3c3cd477cc9dddeebe657eabd7fdcea'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0012(self): def test_txid_bitcoin_core_0012(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000'
self.assertEqual('4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9', tx.txid()) txid = '4d163e00f1966e9a1eab8f9374c3e37f4deb4857c247270e25f7d79a999d2dc9'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0013(self): def test_txid_bitcoin_core_0013(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000'
self.assertEqual('9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4', tx.txid()) txid = '9fe2ef9dde70e15d78894a4800b7df3bbfb1addb9a6f7d7c204492fdb6ee6cc4'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0014(self): def test_txid_bitcoin_core_0014(self):
tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000') raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff010000000000000000015100000000'
self.assertEqual('99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4', tx.txid()) txid = '99d3825137602e577aeaf6a2e3c9620fd0e605323dc5265da4a570593be791d4'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0015(self): def test_txid_bitcoin_core_0015(self):
tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000') raw_tx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6451515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000'
self.assertEqual('c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84', tx.txid()) txid = 'c0d67409923040cc766bbea12e4c9154393abef706db065ac2e07d91a9ba4f84'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0016(self): def test_txid_bitcoin_core_0016(self):
tx = transaction.Transaction('010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') raw_tx = '010000000200010000000000000000000000000000000000000000000000000000000000000000000049483045022100d180fd2eb9140aeb4210c9204d3f358766eb53842b2a9473db687fa24b12a3cc022079781799cd4f038b85135bbe49ec2b57f306b2bb17101b17f71f000fcab2b6fb01ffffffff0002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000'
self.assertEqual('c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666', tx.txid()) txid = 'c610d85d3d5fdf5046be7f123db8a0890cee846ee58de8a44667cfd1ab6b8666'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0017(self): def test_txid_bitcoin_core_0017(self):
tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000') raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df101010000000002000000000000000000000000000000000000000000000000000000000000000000004847304402205f7530653eea9b38699e476320ab135b74771e1c48b81a5d041e2ca84b9be7a802200ac8d1f40fb026674fe5a5edd3dea715c27baa9baca51ed45ea750ac9dc0a55e81ffffffff010100000000000000015100000000'
self.assertEqual('a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f', tx.txid()) txid = 'a647a7b3328d2c698bfa1ee2dd4e5e05a6cea972e764ccb9bd29ea43817ca64f'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0018(self): def test_txid_bitcoin_core_0018(self):
tx = transaction.Transaction('010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000') raw_tx = '010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000'
self.assertEqual('afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae', tx.txid()) txid = 'afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0019(self): def test_txid_bitcoin_core_0019(self):
tx = transaction.Transaction('01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000') raw_tx = '01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe0000483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa0148304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f4014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000'
self.assertEqual('f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d', tx.txid()) txid = 'f4b05f978689c89000f729cae187dcfbe64c9819af67a4f05c0b4d59e717d64d'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0020(self): def test_txid_bitcoin_core_0020(self):
tx = transaction.Transaction('0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000') raw_tx = '0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000'
self.assertEqual('cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984', tx.txid()) txid = 'cc60b1f899ec0a69b7c3f25ddf32c4524096a9c5b01cbd84c6d0312a0c478984'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0021(self): def test_txid_bitcoin_core_0021(self):
tx = transaction.Transaction('01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000') raw_tx = '01000000012c651178faca83be0b81c8c1375c4b0ad38d53c8fe1b1c4255f5e795c25792220000000049483045022100d6044562284ac76c985018fc4a90127847708c9edb280996c507b28babdc4b2a02203d74eca3f1a4d1eea7ff77b528fde6d5dc324ec2dbfdb964ba885f643b9704cd01ffffffff010100000000000000232102c2410f8891ae918cab4ffc4bb4a3b0881be67c7a1e7faa8b5acf9ab8932ec30cac00000000'
self.assertEqual('1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667', tx.txid()) txid = '1edc7f214659d52c731e2016d258701911bd62a0422f72f6c87a1bc8dd3f8667'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0022(self): def test_txid_bitcoin_core_0022(self):
tx = transaction.Transaction('0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000') raw_tx = '0100000001f725ea148d92096a79b1709611e06e94c63c4ef61cbae2d9b906388efd3ca99c000000000100ffffffff0101000000000000002321028a1d66975dbdf97897e3a4aef450ebeb5b5293e4a0b4a6d3a2daaa0b2b110e02ac00000000'
self.assertEqual('018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff', tx.txid()) txid = '018adb7133fde63add9149a2161802a1bcf4bdf12c39334e880c073480eda2ff'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0023(self): def test_txid_bitcoin_core_0023(self):
tx = transaction.Transaction('0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000') raw_tx = '0100000001be599efaa4148474053c2fa031c7262398913f1dc1d9ec201fd44078ed004e44000000004900473044022022b29706cb2ed9ef0cb3c97b72677ca2dfd7b4160f7b4beb3ba806aa856c401502202d1e52582412eba2ed474f1f437a427640306fd3838725fab173ade7fe4eae4a01ffffffff010100000000000000232103ac4bba7e7ca3e873eea49e08132ad30c7f03640b6539e9b59903cf14fd016bbbac00000000'
self.assertEqual('1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2', tx.txid()) txid = '1464caf48c708a6cc19a296944ded9bb7f719c9858986d2501cf35068b9ce5a2'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0024(self): def test_txid_bitcoin_core_0024(self):
tx = transaction.Transaction('010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000') raw_tx = '010000000112b66d5e8c7d224059e946749508efea9d66bf8d0c83630f080cf30be8bb6ae100000000490047304402206ffe3f14caf38ad5c1544428e99da76ffa5455675ec8d9780fac215ca17953520220779502985e194d84baa36b9bd40a0dbd981163fa191eb884ae83fc5bd1c86b1101ffffffff010100000000000000232103905380c7013e36e6e19d305311c1b81fce6581f5ee1c86ef0627c68c9362fc9fac00000000'
self.assertEqual('1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a', tx.txid()) txid = '1fb73fbfc947d52f5d80ba23b67c06a232ad83fdd49d1c0a657602f03fbe8f7a'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0025(self): def test_txid_bitcoin_core_0025(self):
tx = transaction.Transaction('0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000') raw_tx = '0100000001b0ef70cc644e0d37407e387e73bfad598d852a5aa6d691d72b2913cebff4bceb000000004a00473044022068cd4851fc7f9a892ab910df7a24e616f293bcb5c5fbdfbc304a194b26b60fba022078e6da13d8cb881a22939b952c24f88b97afd06b4c47a47d7f804c9a352a6d6d0100ffffffff0101000000000000002321033bcaa0a602f0d44cc9d5637c6e515b0471db514c020883830b7cefd73af04194ac00000000'
self.assertEqual('24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab', tx.txid()) txid = '24cecfce0fa880b09c9b4a66c5134499d1b09c01cc5728cd182638bea070e6ab'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0026(self): def test_txid_bitcoin_core_0026(self):
tx = transaction.Transaction('0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000') raw_tx = '0100000001c188aa82f268fcf08ba18950f263654a3ea6931dabc8bf3ed1d4d42aaed74cba000000004b0000483045022100940378576e069aca261a6b26fb38344e4497ca6751bb10905c76bb689f4222b002204833806b014c26fd801727b792b1260003c55710f87c5adbd7a9cb57446dbc9801ffffffff0101000000000000002321037c615d761e71d38903609bf4f46847266edc2fb37532047d747ba47eaae5ffe1ac00000000'
self.assertEqual('9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7', tx.txid()) txid = '9eaa819e386d6a54256c9283da50c230f3d8cd5376d75c4dcc945afdeb157dd7'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0027(self): def test_txid_bitcoin_core_0027(self):
tx = transaction.Transaction('01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000') raw_tx = '01000000012432b60dc72cebc1a27ce0969c0989c895bdd9e62e8234839117f8fc32d17fbc000000004a493046022100a576b52051962c25e642c0fd3d77ee6c92487048e5d90818bcf5b51abaccd7900221008204f8fb121be4ec3b24483b1f92d89b1b0548513a134e345c5442e86e8617a501ffffffff010000000000000000016a00000000'
self.assertEqual('46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa', tx.txid()) txid = '46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0028(self): def test_txid_bitcoin_core_0028(self):
tx = transaction.Transaction('01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000') raw_tx = '01000000014710b0e7cf9f8930de259bdc4b84aa5dfb9437b665a3e3a21ff26e0bf994e183000000004a493046022100a166121a61b4eeb19d8f922b978ff6ab58ead8a5a5552bf9be73dc9c156873ea02210092ad9bc43ee647da4f6652c320800debcf08ec20a094a0aaf085f63ecb37a17201ffffffff010000000000000000016a00000000'
self.assertEqual('8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e', tx.txid()) txid = '8d66836045db9f2d7b3a75212c5e6325f70603ee27c8333a3bce5bf670d9582e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0029(self): def test_txid_bitcoin_core_0029(self):
tx = transaction.Transaction('01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000') raw_tx = '01000000015ebaa001d8e4ec7a88703a3bcf69d98c874bca6299cca0f191512bf2a7826832000000004948304502203bf754d1c6732fbf87c5dcd81258aefd30f2060d7bd8ac4a5696f7927091dad1022100f5bcb726c4cf5ed0ed34cc13dadeedf628ae1045b7cb34421bc60b89f4cecae701ffffffff010000000000000000016a00000000'
self.assertEqual('aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8', tx.txid()) txid = 'aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0030(self): def test_txid_bitcoin_core_0030(self):
tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000') raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000'
self.assertEqual('6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190', tx.txid()) txid = '6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0031(self): def test_txid_bitcoin_core_0031(self):
tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000') raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000'
self.assertEqual('892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880', tx.txid()) txid = '892464645599cc3c2d165adcc612e5f982a200dfaa3e11e9ce1d228027f46880'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0032(self): def test_txid_bitcoin_core_0032(self):
tx = transaction.Transaction('010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000') raw_tx = '010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000'
self.assertEqual('578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc', tx.txid()) txid = '578db8c6c404fec22c4a8afeaf32df0e7b767c4dda3478e0471575846419e8fc'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0033(self): def test_txid_bitcoin_core_0033(self):
tx = transaction.Transaction('0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') raw_tx = '0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
self.assertEqual('974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f', tx.txid()) txid = '974f5148a0946f9985e75a240bb24c573adbbdc25d61e7b016cdbb0a5355049f'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0034(self): def test_txid_bitcoin_core_0034(self):
tx = transaction.Transaction('01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') raw_tx = '01000000013c6f30f99a5161e75a2ce4bca488300ca0c6112bde67f0807fe983feeff0c91001000000e608646561646265656675ab61493046022100ce18d384221a731c993939015e3d1bcebafb16e8c0b5b5d14097ec8177ae6f28022100bcab227af90bab33c3fe0a9abfee03ba976ee25dc6ce542526e9b2e56e14b7f10121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac493046022100c3b93edcc0fd6250eb32f2dd8a0bba1754b0f6c3be8ed4100ed582f3db73eba2022100bf75b5bd2eff4d6bf2bda2e34a40fcc07d4aa3cf862ceaa77b47b81eff829f9a01ab21038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
self.assertEqual('b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b', tx.txid()) txid = 'b0097ec81df231893a212657bf5fe5a13b2bff8b28c0042aca6fc4159f79661b'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0035(self): def test_txid_bitcoin_core_0035(self):
tx = transaction.Transaction('01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') raw_tx = '01000000016f3dbe2ca96fa217e94b1017860be49f20820dea5c91bdcb103b0049d5eb566000000000fd1d0147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140147304402203989ac8f9ad36b5d0919d97fa0a7f70c5272abee3b14477dc646288a8b976df5022027d19da84a066af9053ad3d1d7459d171b7e3a80bc6c4ef7a330677a6be548140121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac47304402203757e937ba807e4a5da8534c17f9d121176056406a6465054bdd260457515c1a02200f02eccf1bec0f3a0d65df37889143c2e88ab7acec61a7b6f5aa264139141a2b0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
self.assertEqual('feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9', tx.txid()) txid = 'feeba255656c80c14db595736c1c7955c8c0a497622ec96e3f2238fbdd43a7c9'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0036(self): def test_txid_bitcoin_core_0036(self):
tx = transaction.Transaction('01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000') raw_tx = '01000000012139c555ccb81ee5b1e87477840991ef7b386bc3ab946b6b682a04a621006b5a01000000fdb40148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f2204148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a5800390175ac4830450220646b72c35beeec51f4d5bc1cbae01863825750d7f490864af354e6ea4f625e9c022100f04b98432df3a9641719dbced53393022e7249fb59db993af1118539830aab870148304502201723e692e5f409a7151db386291b63524c5eb2030df652b1f53022fd8207349f022100b90d9bbf2f3366ce176e5e780a00433da67d9e5c79312c6388312a296a580039017521038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000'
self.assertEqual('a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e', tx.txid()) txid = 'a0c984fc820e57ddba97f8098fa640c8a7eb3fe2f583923da886b7660f505e1e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0037(self): def test_txid_bitcoin_core_0037(self):
tx = transaction.Transaction('0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000') raw_tx = '0100000002f9cbafc519425637ba4227f8d0a0b7160b4e65168193d5af39747891de98b5b5000000006b4830450221008dd619c563e527c47d9bd53534a770b102e40faa87f61433580e04e271ef2f960220029886434e18122b53d5decd25f1f4acb2480659fea20aabd856987ba3c3907e0121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffff42e7988254800876b69f24676b3e0205b77be476512ca4d970707dd5c60598ab00000000fd260100483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a53034930460221008431bdfa72bc67f9d41fe72e94c88fb8f359ffa30b33c72c121c5a877d922e1002210089ef5fc22dd8bfc6bf9ffdb01a9862d27687d424d1fefbab9e9c7176844a187a014c9052483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7153aeffffffff01a08601000000000017a914d8dacdadb7462ae15cd906f1878706d0da8660e68700000000'
self.assertEqual('5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f', tx.txid()) txid = '5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0038(self): def test_txid_bitcoin_core_0038(self):
tx = transaction.Transaction('0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000') raw_tx = '0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000'
self.assertEqual('ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea', tx.txid()) txid = 'ded7ff51d89a4e1ec48162aee5a96447214d93dfb3837946af2301a28f65dbea'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0039(self): def test_txid_bitcoin_core_0039(self):
tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0040(self): def test_txid_bitcoin_core_0040(self):
tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d') raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d'
self.assertEqual('abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649', tx.txid()) txid = 'abd62b4627d8d9b2d95fcfd8c87e37d2790637ce47d28018e3aece63c1d62649'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0041(self): def test_txid_bitcoin_core_0041(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d'
self.assertEqual('58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da', tx.txid()) txid = '58b6de8413603b7f556270bf48caedcf17772e7105f5419f6a80be0df0b470da'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0042(self): def test_txid_bitcoin_core_0042(self):
tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff') raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff'
self.assertEqual('5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324', tx.txid()) txid = '5f99c0abf511294d76cbe144d86b77238a03e086974bc7a8ea0bdb2c681a0324'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0043(self): def test_txid_bitcoin_core_0043(self):
tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000'
self.assertEqual('25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453', tx.txid()) txid = '25d35877eaba19497710666473c50d5527d38503e3521107a3fc532b74cd7453'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0044(self): def test_txid_bitcoin_core_0044(self):
tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff') raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff'
self.assertEqual('1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b', tx.txid()) txid = '1b9aef851895b93c62c29fbd6ca4d45803f4007eff266e2f96ff11e9b6ef197b'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0045(self): def test_txid_bitcoin_core_0045(self):
tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
self.assertEqual('3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e', tx.txid()) txid = '3444be2e216abe77b46015e481d8cc21abd4c20446aabf49cd78141c9b9db87e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0046(self): def test_txid_bitcoin_core_0046(self):
tx = transaction.Transaction('01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000') raw_tx = '01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000'
self.assertEqual('f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd', tx.txid()) txid = 'f53761038a728b1f17272539380d96e93f999218f8dcb04a8469b523445cd0fd'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0047(self): def test_txid_bitcoin_core_0047(self):
tx = transaction.Transaction('0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000') raw_tx = '0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000'
self.assertEqual('d193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1', tx.txid()) txid = 'd193f0f32fceaf07bb25c897c8f99ca6f69a52f6274ca64efc2a2e180cb97fc1'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0048(self): def test_txid_bitcoin_core_0048(self):
tx = transaction.Transaction('010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000') raw_tx = '010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000'
self.assertEqual('50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89', tx.txid()) txid = '50a1e0e6a134a564efa078e3bd088e7e8777c2c0aec10a752fd8706470103b89'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0049(self): def test_txid_bitcoin_core_0049(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'
self.assertEqual('e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755', tx.txid()) txid = 'e2207d1aaf6b74e5d98c2fa326d2dc803b56b30a3f90ce779fa5edb762f38755'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0050(self): def test_txid_bitcoin_core_0050(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000'
self.assertEqual('f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c', tx.txid()) txid = 'f335864f7c12ec7946d2c123deb91eb978574b647af125a414262380c7fbd55c'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0051(self): def test_txid_bitcoin_core_0051(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000'
self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0052(self): def test_txid_bitcoin_core_0052(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000'
self.assertEqual('3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6', tx.txid()) txid = '3a13e1b6371c545147173cc4055f0ed73686a9f73f092352fb4b39ca27d360e6'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0053(self): def test_txid_bitcoin_core_0053(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000'
self.assertEqual('bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d', tx.txid()) txid = 'bffda23e40766d292b0510a1b556453c558980c70c94ab158d8286b3413e220d'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0054(self): def test_txid_bitcoin_core_0054(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000'
self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0055(self): def test_txid_bitcoin_core_0055(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000'
self.assertEqual('f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1', tx.txid()) txid = 'f6d2359c5de2d904e10517d23e7c8210cca71076071bbf46de9fbd5f6233dbf1'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0056(self): def test_txid_bitcoin_core_0056(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000'
self.assertEqual('19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9', tx.txid()) txid = '19c2b7377229dae7aa3e50142a32fd37cef7171a01682f536e9ffa80c186f6c9'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0057(self): def test_txid_bitcoin_core_0057(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000'
self.assertEqual('c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d', tx.txid()) txid = 'c9dda3a24cc8a5acb153d1085ecd2fecf6f87083122f8cdecc515b1148d4c40d'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0058(self): def test_txid_bitcoin_core_0058(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000'
self.assertEqual('d1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2', tx.txid()) txid = 'd1edbcde44691e98a7b7f556bd04966091302e29ad9af3c2baac38233667e0d2'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0059(self): def test_txid_bitcoin_core_0059(self):
tx = transaction.Transaction('020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000') raw_tx = '020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000'
self.assertEqual('01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd', tx.txid()) txid = '01a86c65460325dc6699714d26df512a62a854a669f6ed2e6f369a238e048cfd'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0060(self): def test_txid_bitcoin_core_0060(self):
tx = transaction.Transaction('02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000') raw_tx = '02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000'
self.assertEqual('4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7', tx.txid()) txid = '4b5e0aae1251a9dc66b4d5f483f1879bf518ea5e1765abc5a9f2084b43ed1ea7'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0061(self): def test_txid_bitcoin_core_0061(self):
tx = transaction.Transaction('0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000') raw_tx = '0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000'
self.assertEqual('5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5', tx.txid()) txid = '5f16eb3ca4581e2dfb46a28140a4ee15f85e4e1c032947da8b93549b53c105f5'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0062(self): def test_txid_bitcoin_core_0062(self):
tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0063(self): def test_txid_bitcoin_core_0063(self):
tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
self.assertEqual('b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004', tx.txid()) txid = 'b2ce556154e5ab22bec0a2f990b2b843f4f4085486c0d2cd82873685c0012004'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0064(self): def test_txid_bitcoin_core_0064(self):
tx = transaction.Transaction('01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') raw_tx = '01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
self.assertEqual('fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece', tx.txid()) txid = 'fee125c6cd142083fabd0187b1dd1f94c66c89ec6e6ef6da1374881c0c19aece'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0065(self): def test_txid_bitcoin_core_0065(self):
tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000') raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
self.assertEqual('5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da', tx.txid()) txid = '5f32557914351fee5f89ddee6c8983d476491d29e601d854e3927299e50450da'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0066(self): def test_txid_bitcoin_core_0066(self):
tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000') raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000'
self.assertEqual('07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7', tx.txid()) txid = '07dfa2da3d67c8a2b9f7bd31862161f7b497829d5da90a88ba0f1a905e7a43f7'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0067(self): def test_txid_bitcoin_core_0067(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0068(self): def test_txid_bitcoin_core_0068(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a', tx.txid()) txid = 'f92bb6e4f3ff89172f23ef647f74c13951b665848009abb5862cdf7a0412415a'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0069(self): def test_txid_bitcoin_core_0069(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0070(self): def test_txid_bitcoin_core_0070(self):
tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb', tx.txid()) txid = 'e657e25fc9f2b33842681613402759222a58cf7dd504d6cdc0b69a0b8c2e7dcb'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0071(self): def test_txid_bitcoin_core_0071(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0072(self): def test_txid_bitcoin_core_0072(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8', tx.txid()) txid = '4ede5e22992d43d42ccdf6553fb46e448aa1065ba36423f979605c1e5ab496b8'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0073(self): def test_txid_bitcoin_core_0073(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0074(self): def test_txid_bitcoin_core_0074(self):
tx = transaction.Transaction('01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8', tx.txid()) txid = 'cfe9f4b19f52b8366860aec0d2b5815e329299b2e9890d477edd7f1182be7ac8'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0075(self): def test_txid_bitcoin_core_0075(self):
tx = transaction.Transaction('0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb', tx.txid()) txid = 'aee8f4865ca40fa77ff2040c0d7de683bea048b103d42ca406dc07dd29d539cb'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0076(self): def test_txid_bitcoin_core_0076(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0077(self): def test_txid_bitcoin_core_0077(self):
tx = transaction.Transaction('0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424', tx.txid()) txid = '8a1bddf924d24570074b09d7967c145e54dc4cee7972a92fd975a2ad9e64b424'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0078(self): def test_txid_bitcoin_core_0078(self):
tx = transaction.Transaction('0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000') raw_tx = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000'
self.assertEqual('d93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97', tx.txid()) txid = 'd93ab9e12d7c29d2adc13d5cdf619d53eec1f36eb6612f55af52be7ba0448e97'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0079(self): def test_txid_bitcoin_core_0079(self):
tx = transaction.Transaction('0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000') raw_tx = '0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000'
self.assertEqual('b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91', tx.txid()) txid = 'b83579db5246aa34255642768167132a0c3d2932b186cd8fb9f5490460a0bf91'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0080(self): def test_txid_bitcoin_core_0080(self):
tx = transaction.Transaction('010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000') raw_tx = '010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000'
self.assertEqual('2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874', tx.txid()) txid = '2b1e44fff489d09091e5e20f9a01bbc0e8d80f0662e629fd10709cdb4922a874'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0081(self): def test_txid_bitcoin_core_0081(self):
tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000') raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000'
self.assertEqual('60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7', tx.txid()) txid = '60ebb1dd0b598e20dd0dd462ef6723dd49f8f803b6a2492926012360119cfdd7'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0082(self): def test_txid_bitcoin_core_0082(self):
tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
self.assertEqual('ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003', tx.txid()) txid = 'ed0c7f4163e275f3f77064f471eac861d01fdf55d03aa6858ebd3781f70bf003'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0083(self): def test_txid_bitcoin_core_0083(self):
tx = transaction.Transaction('0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000') raw_tx = '0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000'
self.assertEqual('f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862', tx.txid()) txid = 'f531ddf5ce141e1c8a7fdfc85cc634e5ff686f446a5cf7483e9dbe076b844862'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0084(self): def test_txid_bitcoin_core_0084(self):
tx = transaction.Transaction('01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000') raw_tx = '01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000'
self.assertEqual('98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654', tx.txid()) txid = '98229b70948f1c17851a541f1fe532bf02c408267fecf6d7e174c359ae870654'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0085(self): def test_txid_bitcoin_core_0085(self):
tx = transaction.Transaction('01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000') raw_tx = '01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000'
self.assertEqual('570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab', tx.txid()) txid = '570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0086(self): def test_txid_bitcoin_core_0086(self):
tx = transaction.Transaction('01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') raw_tx = '01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000'
self.assertEqual('e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e', tx.txid()) txid = 'e0b8142f587aaa322ca32abce469e90eda187f3851043cc4f2a0fff8c13fc84e'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0087(self): def test_txid_bitcoin_core_0087(self):
tx = transaction.Transaction('0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000') raw_tx = '0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000'
self.assertEqual('b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d', tx.txid()) txid = 'b9ecf72df06b8f98f8b63748d1aded5ffc1a1186f8a302e63cf94f6250e29f4d'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0088(self): def test_txid_bitcoin_core_0088(self):
tx = transaction.Transaction('0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000') raw_tx = '0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000'
self.assertEqual('27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac', tx.txid()) txid = '27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0089(self): def test_txid_bitcoin_core_0089(self):
tx = transaction.Transaction('010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000') raw_tx = '010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000'
self.assertEqual('22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641', tx.txid()) txid = '22d020638e3b7e1f2f9a63124ac76f5e333c74387862e3675f64b25e960d3641'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0090(self): def test_txid_bitcoin_core_0090(self):
tx = transaction.Transaction('0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000') raw_tx = '0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000'
self.assertEqual('2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5', tx.txid()) txid = '2862bc0c69d2af55da7284d1b16a7cddc03971b77e5a97939cca7631add83bf5'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0091(self): def test_txid_bitcoin_core_0091(self):
tx = transaction.Transaction('01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000') raw_tx = '01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000'
self.assertEqual('1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c', tx.txid()) txid = '1aebf0c98f01381765a8c33d688f8903e4d01120589ac92b78f1185dc1f4119c'
self._run_naive_tests_on_tx(raw_tx, txid)
def test_txid_bitcoin_core_0092(self): def test_txid_bitcoin_core_0092(self):
tx = transaction.Transaction('010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000') raw_tx = '010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000'
self.assertEqual('45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3', tx.txid()) txid = '45d17fb7db86162b2b6ca29fa4e163acf0ef0b54110e49b819bda1f948d423a3'
self._run_naive_tests_on_tx(raw_tx, txid)
# txns from Bitcoin Core ends <--- # txns from Bitcoin Core ends <---

112
lib/tests/test_wallet_vertical.py

@ -5,36 +5,42 @@ import lib.bitcoin as bitcoin
import lib.keystore as keystore import lib.keystore as keystore
import lib.storage as storage import lib.storage as storage
import lib.wallet as wallet import lib.wallet as wallet
from lib import constants
from plugins.trustedcoin import trustedcoin 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): class WalletIntegrityHelper:
self.assertTrue (ks.is_deterministic())
self.assertFalse(ks.is_watching_only())
self.assertFalse(ks.can_import())
self.assertTrue (ks.has_seed())
def _check_xpub_keystore_sanity(self, ks): gap_limit = 1 # make tests run faster
self.assertTrue (ks.is_deterministic())
self.assertTrue (ks.is_watching_only())
self.assertFalse(ks.can_import())
self.assertFalse(ks.has_seed())
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 = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
store.put('keystore', ks.dump()) 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 = wallet.Standard_Wallet(store)
w.synchronize() w.synchronize()
return w 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.""" """Creates a 2-of-2 or 2-of-3 multisig wallet."""
store = storage.WalletStorage('if_this_exists_mocking_failed_648151893') store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
store.put('x%d/' % 1, ks1.dump()) store.put('x%d/' % 1, ks1.dump())
@ -45,11 +51,15 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
multisig_type = "%dof%d" % (2, 3) multisig_type = "%dof%d" % (2, 3)
store.put('x%d/' % 3, ks3.dump()) store.put('x%d/' % 3, ks3.dump())
store.put('wallet_type', multisig_type) 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 = wallet.Multisig_Wallet(store)
w.synchronize() w.synchronize()
return w return w
# TODO passphrase/seed_extension
class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_standard(self, mock_write): def test_electrum_seed_standard(self, mock_write):
seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' 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) 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.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6') self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6')
self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U') 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.txin_type, 'p2pkh')
self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf') self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf')
@ -76,13 +86,13 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
ks = keystore.from_seed(seed_words, '', False) 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.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2') self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2')
self.assertEqual(ks.xpub, 'zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ') 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.txin_type, 'p2wpkh')
self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af') self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af')
@ -95,12 +105,12 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
ks = keystore.from_seed(seed_words, '', False) 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.assertTrue(isinstance(ks, keystore.Old_KeyStore))
self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3') 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.txin_type, 'p2pkh')
self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo') self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo')
@ -130,10 +140,10 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
'x2/': {'xpub': xpub2}}) 'x2/': {'xpub': xpub2}})
xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id) xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id)
ks3 = keystore.from_xpub(xpub3) 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)) 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.txin_type, 'p2sh')
self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV') self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')
@ -151,7 +161,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD') self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD')
self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv') 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.txin_type, 'p2pkh')
self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo') self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo')
@ -169,7 +179,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7') self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7')
self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4') 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.txin_type, 'p2wpkh-p2sh')
self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W')
@ -188,7 +198,7 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE') self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE')
self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs') 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.txin_type, 'p2wpkh')
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu')
@ -200,17 +210,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
self.assertEqual(bitcoin.seed_type(seed_words), 'standard') self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
ks1 = keystore.from_seed(seed_words, '', True) 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.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6') self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6')
self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c') self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c')
# electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud # electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud
ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec') 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)) 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.txin_type, 'p2sh')
self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN') self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN')
@ -222,17 +232,17 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase):
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit') self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
ks1 = keystore.from_seed(seed_words, '', True) 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.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4') self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4')
self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg') self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg')
# electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool # electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
ks2 = keystore.from_xpub('Zpub6y4oYeETXAbzLNg45wcFDGwEG3vpgsyMJybiAfi2pJtNF3i3fJVxK2BeZJaw7VeKZm192QHvXP3uHDNpNmNDbQft9FiMzkKUhNXQafUMYUY') 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)) 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.txin_type, 'p2wsh')
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc') 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 # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact
# der: m/45'/0 # der: m/45'/0
ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg') 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)) 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.txin_type, 'p2sh')
self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN') 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 # bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost
# der: m/49'/0'/0' # der: m/49'/0'/0'
ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau') 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)) 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.txin_type, 'p2wsh-p2sh')
self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns')
self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6') 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')

49
lib/transaction.py

@ -229,10 +229,10 @@ opcodes = Enumeration("Opcodes", [
"OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160",
"OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG",
"OP_CHECKMULTISIGVERIFY", "OP_CHECKMULTISIGVERIFY",
("OP_SINGLEBYTE_END", 0xF0), ("OP_NOP1", 0xB0),
("OP_DOUBLEBYTE_BEGIN", 0xF000), ("OP_CHECKLOCKTIMEVERIFY", 0xB1), ("OP_CHECKSEQUENCEVERIFY", 0xB2),
"OP_PUBKEY", "OP_PUBKEYHASH", "OP_NOP4", "OP_NOP5", "OP_NOP6", "OP_NOP7", "OP_NOP8", "OP_NOP9", "OP_NOP10",
("OP_INVALIDOPCODE", 0xFFFF), ("OP_INVALIDOPCODE", 0xFF),
]) ])
@ -242,10 +242,6 @@ def script_GetOp(_bytes):
vch = None vch = None
opcode = _bytes[i] opcode = _bytes[i]
i += 1 i += 1
if opcode >= opcodes.OP_SINGLEBYTE_END:
opcode <<= 8
opcode |= _bytes[i]
i += 1
if opcode <= opcodes.OP_PUSHDATA4: if opcode <= opcodes.OP_PUSHDATA4:
nSize = opcode nSize = opcode
@ -402,7 +398,8 @@ def parse_redeemScript(s):
redeemScript = multisig_script(pubkeys, m) redeemScript = multisig_script(pubkeys, m)
return m, n, x_pubkeys, pubkeys, redeemScript 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)] decoded = [x for x in script_GetOp(_bytes)]
# The Genesis Block, self-payments, and pay-by-IP-address payments look like: # 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 # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
if match_decoded(decoded, match): 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 # p2sh
match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ]
if match_decoded(decoded, match): 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 # segwit address
match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4 ] possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1))
if match_decoded(decoded, match): for witver, opcode in enumerate(possible_witness_versions):
return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1]) 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) return TYPE_SCRIPT, bh2u(_bytes)
@ -439,16 +438,16 @@ def parse_input(vds):
d['prevout_hash'] = prevout_hash d['prevout_hash'] = prevout_hash
d['prevout_n'] = prevout_n d['prevout_n'] = prevout_n
d['sequence'] = sequence d['sequence'] = sequence
d['x_pubkeys'] = []
d['pubkeys'] = []
d['signatures'] = {}
d['address'] = None
d['num_sig'] = 0
if prevout_hash == '00'*32: if prevout_hash == '00'*32:
d['type'] = 'coinbase' d['type'] = 'coinbase'
d['scriptSig'] = bh2u(scriptSig) d['scriptSig'] = bh2u(scriptSig)
else: else:
d['x_pubkeys'] = []
d['pubkeys'] = []
d['signatures'] = {}
d['address'] = None
d['type'] = 'unknown' d['type'] = 'unknown'
d['num_sig'] = 0
if scriptSig: if scriptSig:
d['scriptSig'] = bh2u(scriptSig) d['scriptSig'] = bh2u(scriptSig)
try: try:
@ -607,6 +606,8 @@ class Transaction:
@classmethod @classmethod
def get_sorted_pubkeys(self, txin): def get_sorted_pubkeys(self, txin):
# sort pubkeys and x_pubkeys, using the order of pubkeys # sort pubkeys and x_pubkeys, using the order of pubkeys
if txin['type'] == 'coinbase':
return [], []
x_pubkeys = txin['x_pubkeys'] x_pubkeys = txin['x_pubkeys']
pubkeys = txin.get('pubkeys') pubkeys = txin.get('pubkeys')
if pubkeys is None: if pubkeys is None:
@ -707,6 +708,8 @@ class Transaction:
def get_siglist(self, txin, estimate_size=False): def get_siglist(self, txin, estimate_size=False):
# if we have enough signatures, we use the actual pubkeys # if we have enough signatures, we use the actual pubkeys
# otherwise, use extended pubkeys (with bip32 derivation) # otherwise, use extended pubkeys (with bip32 derivation)
if txin['type'] == 'coinbase':
return [], []
num_sig = txin.get('num_sig', 1) num_sig = txin.get('num_sig', 1)
if estimate_size: if estimate_size:
pubkey_size = self.estimate_pubkey_size_for_txin(txin) pubkey_size = self.estimate_pubkey_size_for_txin(txin)
@ -728,10 +731,12 @@ class Transaction:
@classmethod @classmethod
def serialize_witness(self, txin, estimate_size=False): 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): if not self.is_segwit_input(txin):
return '00' return '00'
if txin['type'] == 'coinbase':
return txin['witness']
pubkeys, sig_list = self.get_siglist(txin, estimate_size) 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']: if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:
witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0]) witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0])
elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']: elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']:
@ -790,7 +795,9 @@ class Transaction:
return script return script
@classmethod @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) num_sig = txin.get('num_sig', 1)
x_signatures = txin['signatures'] x_signatures = txin['signatures']
signatures = list(filter(None, x_signatures)) signatures = list(filter(None, x_signatures))

16
lib/util.py

@ -84,6 +84,12 @@ class TimeoutException(Exception):
return self.message return self.message
class WalletFileException(Exception): pass
class BitcoinException(Exception): pass
# Throw this exception to unwind the stack like when an error occurs. # Throw this exception to unwind the stack like when an error occurs.
# However unlike other exceptions the user won't be informed. # However unlike other exceptions the user won't be informed.
class UserCancelled(Exception): class UserCancelled(Exception):
@ -412,7 +418,7 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa
return 'unknown' return 'unknown'
x = int(x) # Some callers pass Decimal x = int(x) # Some callers pass Decimal
scale_factor = pow (10, decimal_point) 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: if x < 0:
integer_part = '-' + integer_part integer_part = '-' + integer_part
elif is_diff: elif is_diff:
@ -730,10 +736,6 @@ class SocketPipe:
print_error("SSLError:", e) print_error("SSLError:", e)
time.sleep(0.1) time.sleep(0.1)
continue continue
except OSError as e:
print_error("OSError", e)
time.sleep(0.1)
continue
class QueuePipe: class QueuePipe:
@ -804,7 +806,7 @@ def versiontuple(v):
def import_meta(path, validater, load_meta): def import_meta(path, validater, load_meta):
try: try:
with open(path, 'r') as f: with open(path, 'r', encoding='utf-8') as f:
d = validater(json.loads(f.read())) d = validater(json.loads(f.read()))
load_meta(d) load_meta(d)
#backwards compatibility for JSONDecodeError #backwards compatibility for JSONDecodeError
@ -818,7 +820,7 @@ def import_meta(path, validater, load_meta):
def export_meta(meta, fileName): def export_meta(meta, fileName):
try: 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) json.dump(meta, f, indent=4, sort_keys=True)
except (IOError, os.error) as e: except (IOError, os.error) as e:
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)

2
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 PROTOCOL_VERSION = '1.2' # protocol version requested
# The hash of the mnemonic seed must begin with this # The hash of the mnemonic seed must begin with this

152
lib/wallet.py

@ -39,12 +39,14 @@ from functools import partial
from collections import defaultdict from collections import defaultdict
from numbers import Number from numbers import Number
from decimal import Decimal from decimal import Decimal
import itertools
import sys import sys
from .i18n import _ from .i18n import _
from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
format_satoshis, NoDynamicFeeEstimates, TimeoutException) format_satoshis, NoDynamicFeeEstimates, TimeoutException,
WalletFileException, BitcoinException)
from .bitcoin import * from .bitcoin import *
from .version import * from .version import *
@ -131,6 +133,7 @@ def sweep_preparations(privkeys, network, imax=100):
find_utxos_for_privkey('p2pk', privkey, compressed) find_utxos_for_privkey('p2pk', privkey, compressed)
if not inputs: if not inputs:
raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)')) 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 return inputs, keypairs
@ -186,7 +189,12 @@ class Abstract_Wallet(PrintError):
self.synchronizer = None self.synchronizer = None
self.verifier = 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 # saved fields
self.use_change = storage.get('use_change', True) self.use_change = storage.get('use_change', True)
self.multiple_change = storage.get('multiple_change', False) self.multiple_change = storage.get('multiple_change', False)
@ -200,6 +208,8 @@ class Abstract_Wallet(PrintError):
self.load_transactions() self.load_transactions()
self.build_spent_outpoints() self.build_spent_outpoints()
self.test_addresses_sanity()
# load requests # load requests
self.receive_requests = self.storage.get('payment_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) # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
self.up_to_date = False 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() self.check_history()
# save wallet type the first time # save wallet type the first time
@ -229,6 +235,8 @@ class Abstract_Wallet(PrintError):
self.invoices = InvoiceStore(self.storage) self.invoices = InvoiceStore(self.storage)
self.contacts = Contacts(self.storage) self.contacts = Contacts(self.storage)
self.coin_price_cache = {}
def diagnostic_name(self): def diagnostic_name(self):
return self.basename() return self.basename()
@ -247,6 +255,7 @@ class Abstract_Wallet(PrintError):
self.pruned_txo = self.storage.get('pruned_txo', {}) self.pruned_txo = self.storage.get('pruned_txo', {})
tx_list = self.storage.get('transactions', {}) tx_list = self.storage.get('transactions', {})
self.transactions = {} self.transactions = {}
self._history_local = {} # address -> set(txid)
for tx_hash, raw in tx_list.items(): for tx_hash, raw in tx_list.items():
tx = Transaction(raw) tx = Transaction(raw)
self.transactions[tx_hash] = tx self.transactions[tx_hash] = tx
@ -254,6 +263,8 @@ class Abstract_Wallet(PrintError):
and (tx_hash not in self.pruned_txo.values()): and (tx_hash not in self.pruned_txo.values()):
self.print_error("removing unreferenced tx", tx_hash) self.print_error("removing unreferenced tx", tx_hash)
self.transactions.pop(tx_hash) self.transactions.pop(tx_hash)
else:
self._add_tx_to_local_history(tx_hash)
@profiler @profiler
def save_transactions(self, write=False): def save_transactions(self, write=False):
@ -327,6 +338,12 @@ class Abstract_Wallet(PrintError):
self.receiving_addresses = d.get('receiving', []) self.receiving_addresses = d.get('receiving', [])
self.change_addresses = d.get('change', []) 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): def synchronize(self):
pass pass
@ -657,8 +674,9 @@ class Abstract_Wallet(PrintError):
def get_addr_balance(self, address): def get_addr_balance(self, address):
received, sent = self.get_addr_io(address) received, sent = self.get_addr_io(address)
c = u = x = 0 c = u = x = 0
local_height = self.get_local_height()
for txo, (tx_height, v, is_cb) in received.items(): 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 x += v
elif tx_height > 0: elif tx_height > 0:
c += v c += v
@ -720,12 +738,30 @@ class Abstract_Wallet(PrintError):
# we need self.transaction_lock but get_tx_height will take self.lock # 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 # so we need to take that too here, to enforce order of locks
with self.lock, self.transaction_lock: with self.lock, self.transaction_lock:
for tx_hash in self.transactions: related_txns = self._history_local.get(addr, set())
if addr in self.txi.get(tx_hash, []) or addr in self.txo.get(tx_hash, []): for tx_hash in related_txns:
tx_height = self.get_tx_height(tx_hash)[0] tx_height = self.get_tx_height(tx_hash)[0]
h.append((tx_hash, tx_height)) h.append((tx_hash, tx_height))
return h 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): def get_txin_address(self, txi):
addr = txi.get('address') addr = txi.get('address')
if addr != "(pubkey)": if addr != "(pubkey)":
@ -776,6 +812,9 @@ class Abstract_Wallet(PrintError):
return conflicting_txns return conflicting_txns
def add_transaction(self, tx_hash, tx): 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 # 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 # so we need to take that too here, to enforce order of locks
with self.lock, self.transaction_lock: with self.lock, self.transaction_lock:
@ -861,6 +900,9 @@ class Abstract_Wallet(PrintError):
if dd.get(addr) is None: if dd.get(addr) is None:
dd[addr] = [] dd[addr] = []
dd[addr].append((ser, v)) 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 # save
self.transactions[tx_hash] = tx self.transactions[tx_hash] = tx
return True return True
@ -877,9 +919,11 @@ class Abstract_Wallet(PrintError):
# undo spent_outpoints that are in pruned_txo # undo spent_outpoints that are in pruned_txo
for ser, hh in list(self.pruned_txo.items()): for ser, hh in list(self.pruned_txo.items()):
if hh == tx_hash: if hh == tx_hash:
self.spent_outpoints.pop(ser) self.spent_outpoints.pop(ser, None)
self.pruned_txo.pop(ser) self.pruned_txo.pop(ser)
self._remove_tx_from_local_history(tx_hash)
# add tx to pruned_txo, and undo the txi addition # add tx to pruned_txo, and undo the txi addition
for next_tx, dd in self.txi.items(): for next_tx, dd in self.txi.items():
for addr, l in list(dd.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): 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 from .util import timestamp_to_datetime, Satoshis, Fiat
out = [] out = []
capital_gains = 0
income = 0 income = 0
expenditures = 0 expenditures = 0
fiat_income = 0 capital_gains = Decimal(0)
fiat_expenditures = 0 fiat_income = Decimal(0)
fiat_expenditures = Decimal(0)
h = self.get_history(domain) h = self.get_history(domain)
for tx_hash, height, conf, timestamp, value, balance in h: for tx_hash, height, conf, timestamp, value, balance in h:
if from_timestamp and (timestamp or time.time()) < from_timestamp: if from_timestamp and (timestamp or time.time()) < from_timestamp:
@ -1032,7 +1076,7 @@ class Abstract_Wallet(PrintError):
else: else:
income += value income += value
# fiat computations # fiat computations
if fx is not None: if fx and fx.is_enabled():
date = timestamp_to_datetime(timestamp) date = timestamp_to_datetime(timestamp)
fiat_value = self.get_fiat_value(tx_hash, fx.ccy) fiat_value = self.get_fiat_value(tx_hash, fx.ccy)
fiat_default = fiat_value is None fiat_default = fiat_value is None
@ -1069,7 +1113,7 @@ class Abstract_Wallet(PrintError):
'income': Satoshis(income), 'income': Satoshis(income),
'expenditures': Satoshis(expenditures) 'expenditures': Satoshis(expenditures)
} }
if fx: if fx and fx.is_enabled():
unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy) unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy)
summary['capital_gains'] = Fiat(capital_gains, fx.ccy) summary['capital_gains'] = Fiat(capital_gains, fx.ccy)
summary['fiat_income'] = Fiat(fiat_income, fx.ccy) summary['fiat_income'] = Fiat(fiat_income, fx.ccy)
@ -1153,7 +1197,7 @@ class Abstract_Wallet(PrintError):
_type, data, value = o _type, data, value = o
if _type == TYPE_ADDRESS: if _type == TYPE_ADDRESS:
if not is_address(data): if not is_address(data):
raise BaseException("Invalid bitcoin address:" + data) raise BaseException("Invalid bitcoin address: {}".format(data))
if value == '!': if value == '!':
if i_max is not None: if i_max is not None:
raise BaseException("More than one output set to spend max") raise BaseException("More than one output set to spend max")
@ -1326,7 +1370,7 @@ class Abstract_Wallet(PrintError):
def bump_fee(self, tx, delta): def bump_fee(self, tx, delta):
if tx.is_final(): 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()) inputs = copy.deepcopy(tx.inputs())
outputs = copy.deepcopy(tx.outputs()) outputs = copy.deepcopy(tx.outputs())
for txin in inputs: for txin in inputs:
@ -1357,7 +1401,7 @@ class Abstract_Wallet(PrintError):
if delta > 0: if delta > 0:
continue continue
if delta > 0: 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() locktime = self.get_local_height()
tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) tx_new = Transaction.from_io(inputs, outputs, locktime=locktime)
tx_new.BIP_LI01_sort() tx_new.BIP_LI01_sort()
@ -1429,7 +1473,7 @@ class Abstract_Wallet(PrintError):
xpubs = self.get_master_public_keys() xpubs = self.get_master_public_keys()
for txout in tx.outputs(): for txout in tx.outputs():
_type, addr, amount = txout _type, addr, amount = txout
if self.is_change(addr): if self.is_mine(addr):
index = self.get_address_index(addr) index = self.get_address_index(addr)
pubkeys = self.get_public_keys(addr) pubkeys = self.get_public_keys(addr)
# sort xpubs using the order of pubkeys # sort xpubs using the order of pubkeys
@ -1443,8 +1487,8 @@ class Abstract_Wallet(PrintError):
# hardware wallets require extra info # hardware wallets require extra info
if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]): if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]):
self.add_hw_info(tx) self.add_hw_info(tx)
# sign # sign. start with ready keystores.
for k in self.get_keystores(): for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True):
try: try:
if k.can_sign(tx): if k.can_sign(tx):
k.sign_transaction(tx, password) k.sign_transaction(tx, password)
@ -1516,7 +1560,10 @@ class Abstract_Wallet(PrintError):
baseurl = 'file://' + rdir baseurl = 'file://' + rdir
rewrite = config.get('url_rewrite') rewrite = config.get('url_rewrite')
if 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['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)
out['URI'] += '&r=' + out['request_url'] out['URI'] += '&r=' + out['request_url']
out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key 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): def add_payment_request(self, req, config):
addr = req['address'] 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') amount = req.get('amount')
message = req.get('memo') message = req.get('memo')
self.receive_requests[addr] = req self.receive_requests[addr] = req
@ -1596,7 +1648,7 @@ class Abstract_Wallet(PrintError):
f.write(pr.SerializeToString()) f.write(pr.SerializeToString())
# reload # reload
req = self.get_payment_request(addr, config) 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)) f.write(json.dumps(req))
return req return req
@ -1615,13 +1667,14 @@ class Abstract_Wallet(PrintError):
return True return True
def get_sorted_requests(self, config): def get_sorted_requests(self, config):
def f(x): def f(addr):
try: try:
addr = x.get('address') return self.get_address_index(addr)
return self.get_address_index(addr) or addr
except: except:
return addr return
return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f) 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): def get_fingerprint(self):
raise NotImplementedError() raise NotImplementedError()
@ -1724,11 +1777,12 @@ class Abstract_Wallet(PrintError):
def txin_value(self, txin): def txin_value(self, txin):
txid = txin['prevout_hash'] txid = txin['prevout_hash']
prev_n = txin['prevout_n'] 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: for n, v, cb in d:
if n == prev_n: if n == prev_n:
return v return v
raise BaseException('unknown txin value') # may occur if wallet is not synchronized
return None
def price_at_timestamp(self, txid, price_func): def price_at_timestamp(self, txid, price_func):
height, conf, timestamp = self.get_tx_height(txid) height, conf, timestamp = self.get_tx_height(txid)
@ -1757,8 +1811,16 @@ class Abstract_Wallet(PrintError):
Acquisition price of a coin. Acquisition price of a coin.
This assumes that either all inputs are mine, or no input is mine. 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, {}) != {}: 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: else:
fiat_value = self.get_fiat_value(txid, ccy) fiat_value = self.get_fiat_value(txid, ccy)
if fiat_value is not None: if fiat_value is not None:
@ -1767,6 +1829,7 @@ class Abstract_Wallet(PrintError):
p = self.price_at_timestamp(txid, price_func) p = self.price_at_timestamp(txid, price_func)
return p * txin_value/Decimal(COIN) return p * txin_value/Decimal(COIN)
class Simple_Wallet(Abstract_Wallet): class Simple_Wallet(Abstract_Wallet):
# wallet with a single keystore # wallet with a single keystore
@ -1903,8 +1966,18 @@ class Imported_Wallet(Simple_Wallet):
pubkey = self.get_public_key(address) pubkey = self.get_public_key(address)
self.addresses.pop(address) self.addresses.pop(address)
if pubkey: if pubkey:
self.keystore.delete_imported_key(pubkey) # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key)
self.save_keystore() 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.put('addresses', self.addresses)
self.storage.write() self.storage.write()
@ -1919,14 +1992,15 @@ class Imported_Wallet(Simple_Wallet):
try: try:
txin_type, pubkey = self.keystore.import_privkey(sec, pw) txin_type, pubkey = self.keystore.import_privkey(sec, pw)
except Exception: 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 txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
if redeem_script is not None: 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) addr = bitcoin.pubkey_to_address(txin_type, pubkey)
elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
if redeem_script is None: 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) addr = bitcoin.redeem_script_to_address(txin_type, redeem_script)
else: else:
raise NotImplementedError(txin_type) raise NotImplementedError(txin_type)
@ -2280,5 +2354,5 @@ class Wallet(object):
return Multisig_Wallet return Multisig_Wallet
if wallet_type in wallet_constructors: if wallet_type in wallet_constructors:
return wallet_constructors[wallet_type] return wallet_constructors[wallet_type]
raise RuntimeError("Unknown wallet type: " + wallet_type) raise RuntimeError("Unknown wallet type: " + str(wallet_type))

2
lib/websockets.py

@ -64,7 +64,7 @@ class WsClientThread(util.DaemonThread):
# read json file # read json file
rdir = self.config.get('requests_dir') rdir = self.config.get('requests_dir')
n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json') 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() s = f.read()
d = json.loads(s) d = json.loads(s)
addr = d.get('address') addr = d.get('address')

4
lib/x509.py

@ -284,7 +284,7 @@ class X509(object):
return self.AKI if self.AKI else repr(self.issuer) return self.AKI if self.AKI else repr(self.issuer)
def get_common_name(self): 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): def get_signature(self):
return self.cert_sig_algo, self.signature, self.data return self.cert_sig_algo, self.signature, self.data
@ -313,7 +313,7 @@ def load_certificates(ca_path):
ca_list = {} ca_list = {}
ca_keyID = {} ca_keyID = {}
# ca_path = '/tmp/tmp.txt' # 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() s = f.read()
bList = pem.dePemList(s, "CERTIFICATE") bList = pem.dePemList(s, "CERTIFICATE")
for b in bList: for b in bList:

7
plugins/cosigner_pool/qt.py

@ -43,9 +43,7 @@ import sys
import traceback import traceback
PORT = 12344 server = ServerProxy('https://cosigner.electrum.org/', allow_none=True)
HOST = 'cosigner.electrum.org'
server = ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True)
class Listener(util.DaemonThread): class Listener(util.DaemonThread):
@ -175,7 +173,8 @@ class Plugin(BasePlugin):
for window, xpub, K, _hash in self.cosigner_list: for window, xpub, K, _hash in self.cosigner_list:
if not self.cosigner_can_sign(tx, xpub): if not self.cosigner_can_sign(tx, xpub):
continue 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: try:
server.put(_hash, message) server.put(_hash, message)
except Exception as e: except Exception as e:

100
plugins/digitalbitbox/digitalbitbox.py

@ -82,6 +82,13 @@ class DigitalBitbox_Client():
def is_paired(self): def is_paired(self):
return self.password is not None 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): def _get_xpub(self, bip32_path):
if self.check_device_dialog(): if self.check_device_dialog():
return self.hid_send_encrypt(b'{"xpub": "%s"}' % bip32_path.encode('utf8')) return self.hid_send_encrypt(b'{"xpub": "%s"}' % bip32_path.encode('utf8'))
@ -106,7 +113,7 @@ class DigitalBitbox_Client():
def dbb_has_password(self): def dbb_has_password(self):
reply = self.hid_send_plain(b'{"ping":""}') reply = self.hid_send_plain(b'{"ping":""}')
if 'ping' not in reply: 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': if reply['ping'] == 'password':
return True return True
return False return False
@ -124,9 +131,11 @@ class DigitalBitbox_Client():
if password is None: if password is None:
return None return None
if len(password) < 4: 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: 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: else:
return password.encode('utf8') return password.encode('utf8')
@ -137,9 +146,11 @@ class DigitalBitbox_Client():
if password is None: if password is None:
return False return False
if len(password) < 4: 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: 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: else:
self.password = password.encode('utf8') self.password = password.encode('utf8')
return True return True
@ -150,10 +161,11 @@ class DigitalBitbox_Client():
if self.password is None and not self.dbb_has_password(): if self.password is None and not self.dbb_has_password():
if not self.setupRunning: if not self.setupRunning:
return False # A fresh device cannot connect to an existing wallet return False # A fresh device cannot connect to an existing wallet
msg = _("An uninitialized Digital Bitbox is detected. " \ msg = _("An uninitialized Digital Bitbox is detected.") + " " + \
"Enter a new password below.\r\n\r\n REMEMBER THE PASSWORD!\r\n\r\n" \ _("Enter a new password below.") + "\n\n" + \
"You cannot access your coins or a backup without the password.\r\n" \ _("REMEMBER THE PASSWORD!") + "\n\n" + \
"A backup is saved automatically when generating a new wallet.") _("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): if self.password_dialog(msg):
reply = self.hid_send_plain(b'{"password":"' + self.password + b'"}') reply = self.hid_send_plain(b'{"password":"' + self.password + b'"}')
else: else:
@ -163,19 +175,19 @@ class DigitalBitbox_Client():
msg = _("Enter your Digital Bitbox password:") msg = _("Enter your Digital Bitbox password:")
while self.password is None: while self.password is None:
if not self.password_dialog(msg): if not self.password_dialog(msg):
return False raise UserCancelled()
reply = self.hid_send_encrypt(b'{"led":"blink"}') reply = self.hid_send_encrypt(b'{"led":"blink"}')
if 'error' in reply: if 'error' in reply:
self.password = None self.password = None
if reply['error']['code'] == 109: if reply['error']['code'] == 109:
msg = _("Incorrect password entered.\r\n\r\n" \ msg = _("Incorrect password entered.") + "\n\n" + \
+ reply['error']['message'] + "\r\n\r\n" \ reply['error']['message'] + "\n\n" + \
"Enter your Digital Bitbox password:") _("Enter your Digital Bitbox password:")
else: else:
# Should never occur # Should never occur
msg = _("Unexpected error occurred.\r\n\r\n" \ msg = _("Unexpected error occurred.") + "\n\n" + \
+ reply['error']['message'] + "\r\n\r\n" \ reply['error']['message'] + "\n\n" + \
"Enter your Digital Bitbox password:") _("Enter your Digital Bitbox password:")
# Initialize device if not yet initialized # Initialize device if not yet initialized
if not self.setupRunning: if not self.setupRunning:
@ -191,7 +203,7 @@ class DigitalBitbox_Client():
def recover_or_erase_dialog(self): 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 = [ choices = [
(_("Create a wallet using the current seed")), (_("Create a wallet using the current seed")),
(_("Load a wallet from the micro SD card (the current seed is overwritten)")), (_("Load a wallet from the micro SD card (the current seed is overwritten)")),
@ -208,13 +220,13 @@ class DigitalBitbox_Client():
return return
else: else:
if self.hid_send_encrypt(b'{"device":"info"}')['device']['lock']: 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 # Use existing seed
self.isInitialized = True self.isInitialized = True
def seed_device_dialog(self): def seed_device_dialog(self):
msg = _("Choose how to initialize your Digital Bitbox:\n") msg = _("Choose how to initialize your Digital Bitbox:") + "\n"
choices = [ choices = [
(_("Generate a new random wallet")), (_("Generate a new random wallet")),
(_("Load a wallet from the micro SD card")) (_("Load a wallet from the micro SD card"))
@ -280,9 +292,9 @@ class DigitalBitbox_Client():
def dbb_erase(self): def dbb_erase(self):
self.handler.show_message(_("Are you sure you want to erase the Digital Bitbox?\r\n\r\n" \ 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.\r\n\r\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.")) _("To cancel, briefly touch the light or wait for the timeout."))
hid_reply = self.hid_send_encrypt(b'{"reset":"__ERASE__"}') hid_reply = self.hid_send_encrypt(b'{"reset":"__ERASE__"}')
self.handler.finished() self.handler.finished()
if 'error' in hid_reply: if 'error' in hid_reply:
@ -305,9 +317,9 @@ class DigitalBitbox_Client():
raise Exception('Canceled by user') raise Exception('Canceled by user')
key = self.stretch_key(key) key = self.stretch_key(key)
if show_msg: if show_msg:
self.handler.show_message(_("Loading backup...\r\n\r\n" \ self.handler.show_message(_("Loading backup...") + "\n\n" +
"To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\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.")) _("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')) msg = b'{"seed":{"source": "backup", "key": "%s", "filename": "%s"}}' % (key, backups['backup'][f].encode('utf8'))
hid_reply = self.hid_send_encrypt(msg) hid_reply = self.hid_send_encrypt(msg)
self.handler.finished() self.handler.finished()
@ -441,12 +453,12 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
dbb_client = self.plugin.get_client(self) dbb_client = self.plugin.get_client(self)
if not dbb_client.is_paired(): 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) reply = dbb_client.hid_send_encrypt(msg)
self.handler.show_message(_("Signing message ...\r\n\r\n" \ self.handler.show_message(_("Signing message ...") + "\n\n" +
"To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\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.")) _("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) reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)
self.handler.finished() self.handler.finished()
@ -454,7 +466,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
raise Exception(reply['error']['message']) raise Exception(reply['error']['message'])
if 'sign' not in reply: if 'sign' not in reply:
raise Exception("Could not sign message.") raise Exception(_("Could not sign message."))
if 'recid' in reply['sign'][0]: if 'recid' in reply['sign'][0]:
# firmware > v2.1.1 # firmware > v2.1.1
@ -463,7 +475,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
pk = point_to_ser(pk.pubkey.point, compressed) pk = point_to_ser(pk.pubkey.point, compressed)
addr = public_key_to_p2pkh(pk) addr = public_key_to_p2pkh(pk)
if verify_message(addr, sig, message) is False: 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]: elif 'pubkey' in reply['sign'][0]:
# firmware <= v2.1.1 # firmware <= v2.1.1
for i in range(4): for i in range(4):
@ -475,7 +487,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
except Exception: except Exception:
continue continue
else: else:
raise Exception("Could not sign message") raise Exception(_("Could not sign message"))
except BaseException as e: except BaseException as e:
@ -576,14 +588,14 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
self.plugin.comserver_post_notification(reply) self.plugin.comserver_post_notification(reply)
if steps > 1: if steps > 1:
self.handler.show_message(_("Signing large transaction. Please be patient ...\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. " \ _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " +
"(Touch " + str(step + 1) + " of " + str(int(steps)) + ")\r\n\r\n" \ _("(Touch {} of {})").format((step + 1), steps) + "\n\n" +
"To cancel, briefly touch the blinking light or wait for the timeout.\r\n\r\n")) _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n")
else: else:
self.handler.show_message(_("Signing transaction ...\r\n\r\n" \ self.handler.show_message(_("Signing transaction...") + "\n\n" +
"To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\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.")) _("To cancel, briefly touch the blinking light or wait for the timeout."))
# Send twice, first returns an echo for smart verification # Send twice, first returns an echo for smart verification
reply = dbb_client.hid_send_encrypt(msg) reply = dbb_client.hid_send_encrypt(msg)
@ -719,3 +731,13 @@ class DigitalBitboxPlugin(HW_PluginBase):
if client is not None: if client is not None:
client.check_device_dialog() client.check_device_dialog()
return client 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)

11
plugins/digitalbitbox/qt.py

@ -1,3 +1,5 @@
from functools import partial
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
from .digitalbitbox import DigitalBitboxPlugin from .digitalbitbox import DigitalBitboxPlugin
@ -30,14 +32,7 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase):
if len(addrs) == 1: if len(addrs) == 1:
def show_address(): def show_address():
change, index = wallet.get_address_index(addrs[0]) keystore.thread.add(partial(self.show_address, wallet, keystore, 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)
menu.addAction(_("Show on {}").format(self.device), show_address) menu.addAction(_("Show on {}").format(self.device), show_address)

59
plugins/email_requests/qt.py

@ -22,7 +22,7 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import random
import time import time
import threading import threading
import base64 import base64
@ -45,11 +45,12 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.paymentrequest import PaymentRequest from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError
from electrum_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, from electrum_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
WindowModalDialog, get_parent_main_window) WindowModalDialog, get_parent_main_window)
class Processor(threading.Thread): class Processor(threading.Thread, PrintError):
polling_interval = 5*60 polling_interval = 5*60
def __init__(self, imap_server, username, password, callback): def __init__(self, imap_server, username, password, callback):
@ -59,6 +60,8 @@ class Processor(threading.Thread):
self.password = password self.password = password
self.imap_server = imap_server self.imap_server = imap_server
self.on_receive = callback self.on_receive = callback
self.M = None
self.connect_wait = 100 # ms, between failed connection attempts
def poll(self): def poll(self):
try: try:
@ -80,13 +83,18 @@ class Processor(threading.Thread):
self.on_receive(pr_str) self.on_receive(pr_str)
def run(self): def run(self):
self.M = imaplib.IMAP4_SSL(self.imap_server)
self.M.login(self.username, self.password)
while True: while True:
self.poll() try:
time.sleep(self.polling_interval) self.M = imaplib.IMAP4_SSL(self.imap_server)
self.M.close() self.M.login(self.username, self.password)
self.M.logout() 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): def send(self, recipient, message, payment_request):
msg = MIMEMultipart() msg = MIMEMultipart()
@ -98,10 +106,13 @@ class Processor(threading.Thread):
encode_base64(part) encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"') part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"')
msg.attach(part) msg.attach(part)
s = smtplib.SMTP_SSL(self.imap_server, timeout=2) try:
s.login(self.username, self.password) s = smtplib.SMTP_SSL(self.imap_server, timeout=2)
s.sendmail(self.username, [recipient], msg.as_string()) s.login(self.username, self.password)
s.quit() s.sendmail(self.username, [recipient], msg.as_string())
s.quit()
except BaseException as e:
self.print_error(e)
class QEmailSignalObject(QObject): class QEmailSignalObject(QObject):
@ -225,3 +236,27 @@ class Plugin(BasePlugin):
password = str(password_e.text()) password = str(password_e.text())
self.config.set_key('email_password', password) self.config.set_key('email_password', password)
self.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))

3
plugins/hw_wallet/cmdline.py

@ -32,6 +32,9 @@ class CmdLineHandler:
def show_message(self, msg, on_cancel=None): def show_message(self, msg, on_cancel=None):
print_msg(msg) print_msg(msg)
def show_error(self, msg):
print_msg(msg)
def update_status(self, b): def update_status(self, b):
print_error('trezor status', b) print_error('trezor status', b)

19
plugins/keepkey/clientbase.py

@ -50,6 +50,9 @@ class GuiMixin(object):
else: else:
msg = _("Enter your current {} PIN:") msg = _("Enter your current {} PIN:")
pin = self.handler.get_pin(msg.format(self.device)) 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: if not pin:
return self.proto.Cancel() return self.proto.Cancel()
return self.proto.PinMatrixAck(pin=pin) return self.proto.PinMatrixAck(pin=pin)
@ -66,7 +69,13 @@ class GuiMixin(object):
if passphrase is None: if passphrase is None:
return self.proto.Cancel() return self.proto.Cancel()
passphrase = bip39_normalize_passphrase(passphrase) 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): def callback_WordRequest(self, msg):
self.step += 1 self.step += 1
@ -110,6 +119,14 @@ class KeepKeyClientBase(GuiMixin, PrintError):
def is_pairable(self): def is_pairable(self):
return not self.features.bootloader_mode 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): def used(self):
self.last_operation = time.time() self.last_operation = time.time()

115
plugins/keepkey/plugin.py

@ -1,5 +1,3 @@
import threading
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from electrum.util import bfh, bh2u from electrum.util import bfh, bh2u
@ -72,8 +70,6 @@ class KeepKeyCompatiblePlugin(HW_PluginBase):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
HW_PluginBase.__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: if self.libraries_available:
self.device_manager().register_devices(self.DEVICE_IDS) self.device_manager().register_devices(self.DEVICE_IDS)
@ -303,56 +299,83 @@ class KeepKeyCompatiblePlugin(HW_PluginBase):
return inputs return inputs
def tx_outputs(self, derivation, tx, segwit=False): 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 = [] outputs = []
has_change = False has_change = False
any_output_on_change_branch = is_any_output_on_change_branch()
for _type, address, amount in tx.outputs(): for _type, address, amount in tx.outputs():
use_create_by_derivation = False
info = tx.output_info.get(address) info = tx.output_info.get(address)
if info is not None and not has_change: 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 index, xpubs, m = info
if len(xpubs) == 1: on_change_branch = index[0] == 1
script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS # prioritise hiding outputs on the 'change' branch from user
address_n = self.client_class.expand_path(derivation + "/%d/%d"%index) # because no more than one change address allowed
txoutputtype = self.types.TxOutputType( if on_change_branch == any_output_on_change_branch:
amount = amount, use_create_by_derivation = True
script_type = script_type, has_change = True
address_n = address_n,
) if use_create_by_derivation:
else: txoutputtype = create_output_by_derivation(info)
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)
else: else:
txoutputtype = self.types.TxOutputType() txoutputtype = create_output_by_address()
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
outputs.append(txoutputtype) outputs.append(txoutputtype)
return outputs return outputs

2
plugins/keepkey/qt_generic.py

@ -250,7 +250,7 @@ class QtPlugin(QtPluginBase):
vbox.addWidget(QLabel(msg)) vbox.addWidget(QLabel(msg))
vbox.addWidget(text) vbox.addWidget(text)
pin = QLineEdit() pin = QLineEdit()
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
pin.setMaximumWidth(100) pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout() hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))

91
plugins/labels/labels.py

@ -16,7 +16,7 @@ class LabelsPlugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
BasePlugin.__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 = {} self.wallets = {}
def encode(self, wallet, msg): def encode(self, wallet, msg):
@ -45,7 +45,7 @@ class LabelsPlugin(BasePlugin):
@hook @hook
def set_label(self, wallet, item, label): def set_label(self, wallet, item, label):
if not wallet in self.wallets: if wallet not in self.wallets:
return return
if not item: if not item:
return return
@ -55,7 +55,7 @@ class LabelsPlugin(BasePlugin):
"walletNonce": nonce, "walletNonce": nonce,
"externalId": self.encode(wallet, item), "externalId": self.encode(wallet, item),
"encryptedLabel": self.encode(wallet, label)} "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]) args=["POST", "/label", False, bundle])
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
@ -78,8 +78,18 @@ class LabelsPlugin(BasePlugin):
raise BaseException(response["error"]) raise BaseException(response["error"])
return response 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): 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": [], bundle = {"labels": [],
"walletId": wallet_id, "walletId": wallet_id,
"walletNonce": self.get_nonce(wallet)} "walletNonce": self.get_nonce(wallet)}
@ -95,42 +105,47 @@ class LabelsPlugin(BasePlugin):
self.do_request("POST", "/labels", True, bundle) self.do_request("POST", "/labels", True, bundle)
def pull_thread(self, wallet, force): 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 nonce = 1 if force else self.get_nonce(wallet) - 1
self.print_error("asking for labels since nonce", nonce) 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: try:
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) )) self.pull_thread(wallet, force)
if response["labels"] is None: except BaseException as e:
self.print_error('no new labels') # traceback.print_exc(file=sys.stderr)
return self.print_error('could not retrieve labels')
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")
def start_wallet(self, wallet): def start_wallet(self, wallet):
nonce = self.get_nonce(wallet) nonce = self.get_nonce(wallet)
@ -144,7 +159,7 @@ class LabelsPlugin(BasePlugin):
wallet_id = hashlib.sha256(mpk).hexdigest() wallet_id = hashlib.sha256(mpk).hexdigest()
self.wallets[wallet] = (password, iv, wallet_id) self.wallets[wallet] = (password, iv, wallet_id)
# If there is an auth token we can try to actually start syncing # 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.setDaemon(True)
t.start() t.start()

21
plugins/labels/qt.py

@ -1,4 +1,6 @@
from functools import partial from functools import partial
import traceback
import sys
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
@ -37,10 +39,12 @@ class Plugin(LabelsPlugin):
hbox.addWidget(QLabel("Label sync options:")) hbox.addWidget(QLabel("Label sync options:"))
upload = ThreadedButton("Force upload", upload = ThreadedButton("Force upload",
partial(self.push_thread, wallet), 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", download = ThreadedButton("Force download",
partial(self.pull_thread, wallet, True), 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 = QVBoxLayout()
vbox.addWidget(upload) vbox.addWidget(upload)
vbox.addWidget(download) vbox.addWidget(download)
@ -54,13 +58,20 @@ class Plugin(LabelsPlugin):
def on_pulled(self, wallet): def on_pulled(self, wallet):
self.obj.labels_changed_signal.emit(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.")) 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 @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.obj.labels_changed_signal.connect(window.update_tabs)
self.start_wallet(window.wallet) self.start_wallet(wallet)
@hook @hook
def on_close_window(self, window): def on_close_window(self, window):

40
plugins/ledger/auth2fa.py

@ -1,16 +1,24 @@
import os
import hashlib
import logging
import json
import copy
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import websocket
from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel from PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel
import PyQt5.QtCore as QtCore import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from btchip.btchip import *
from electrum.i18n import _ from electrum.i18n import _
from electrum_gui.qt.util import * from electrum_gui.qt.util import *
from electrum.util import print_msg from electrum.util import print_msg
from electrum import constants, bitcoin
import os, hashlib, websocket, logging, json, copy
from electrum_gui.qt.qrcodewidget import QRCodeWidget from electrum_gui.qt.qrcodewidget import QRCodeWidget
from btchip.btchip import *
DEBUG = False DEBUG = False
@ -37,7 +45,7 @@ class LedgerAuthDialog(QDialog):
self.handler = handler self.handler = handler
self.txdata = data self.txdata = data
self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else '' self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''
self.setMinimumWidth(600) self.setMinimumWidth(650)
self.setWindowTitle(_("Ledger Wallet Authentication")) self.setWindowTitle(_("Ledger Wallet Authentication"))
self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg) self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle
@ -110,17 +118,23 @@ class LedgerAuthDialog(QDialog):
card = QVBoxLayout() card = QVBoxLayout()
self.cardbox.setLayout(card) self.cardbox.setLayout(card)
self.addrtext = QTextEdit() 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.setReadOnly(True)
self.addrtext.setMaximumHeight(120) self.addrtext.setMaximumHeight(130)
card.addWidget(self.addrtext) card.addWidget(self.addrtext)
def pin_changed(s): def pin_changed(s):
if len(s) < len(self.idxs): if len(s) < len(self.idxs):
i = self.idxs[len(s)] i = self.idxs[len(s)]
addr = self.txdata['address'] addr = self.txdata['address']
addr = addr[:i] + '<u><b>' + addr[i:i+1] + '</u></b>' + addr[i+1:] if not constants.net.TESTNET:
self.addrtext.setHtml(str(addr)) text = addr[:i] + '<u><b>' + addr[i:i+1] + '</u></b>' + 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] + '<u><b>' + addr_mainnet[i:i+1] + '</u></b>' + addr_mainnet[i+1:]
text = str(addr) + '\n' + str(addr_mainnet)
self.addrtext.setHtml(str(text))
else: else:
self.addrtext.setHtml(_("Press Enter")) self.addrtext.setHtml(_("Press Enter"))
@ -179,8 +193,8 @@ class LedgerAuthDialog(QDialog):
self.pinbox.setVisible(self.cfg['mode'] == 0) self.pinbox.setVisible(self.cfg['mode'] == 0)
self.cardbox.setVisible(self.cfg['mode'] == 1) self.cardbox.setVisible(self.cfg['mode'] == 1)
self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True) self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
self.setMaximumHeight(200) self.setMaximumHeight(400)
def do_pairing(self): def do_pairing(self):
rng = os.urandom(16) rng = os.urandom(16)
pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8') pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8')
@ -338,11 +352,7 @@ class LedgerWebSocket(QThread):
ws.send( self.txreq ) ws.send( self.txreq )
debug_msg("Req Sent", self.txreq) debug_msg("Req Sent", self.txreq)
def debug_msg(*args): def debug_msg(*args):
if DEBUG: if DEBUG:
print_msg(*args) print_msg(*args)

77
plugins/ledger/ledger.py

@ -57,6 +57,13 @@ class Ledger_Client():
def i4b(self, x): def i4b(self, x):
return pack('>I', 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): def test_pin_unlocked(func):
"""Function decorator to test the Ledger for being unlocked, and if not, """Function decorator to test the Ledger for being unlocked, and if not,
raise a human-readable exception. raise a human-readable exception.
@ -180,8 +187,8 @@ class Ledger_Client():
try: try:
self.perform_hw1_preflight() self.perform_hw1_preflight()
except BTChipException as e: except BTChipException as e:
if (e.sw == 0x6d00): if (e.sw == 0x6d00 or e.sw == 0x6700):
raise BaseException("Device not in Bitcoin mode") raise BaseException(_("Device not in Bitcoin mode")) from e
raise e raise e
self.preflightDone = True self.preflightDone = True
@ -229,6 +236,16 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.client = None self.client = None
raise Exception(message) 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): def address_id_stripped(self, address):
# Strip the leading "m/" # Strip the leading "m/"
change, index = self.get_address_index(address) change, index = self.get_address_index(address)
@ -239,8 +256,8 @@ class Ledger_KeyStore(Hardware_KeyStore):
def decrypt_message(self, pubkey, message, password): def decrypt_message(self, pubkey, message, password):
raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device)) raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
@set_and_unset_signing
def sign_message(self, sequence, message, password): def sign_message(self, sequence, message, password):
self.signing = True
message = message.encode('utf8') message = message.encode('utf8')
message_hash = hashlib.sha256(message).hexdigest().upper() message_hash = hashlib.sha256(message).hexdigest().upper()
# prompt for the PIN before displaying the dialog if necessary # prompt for the PIN before displaying the dialog if necessary
@ -259,16 +276,17 @@ class Ledger_KeyStore(Hardware_KeyStore):
except BTChipException as e: except BTChipException as e:
if e.sw == 0x6a80: 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.") 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: else:
self.give_error(e, True) self.give_error(e, True)
except UserWarning: except UserWarning:
self.handler.show_error(_('Cancelled by user')) self.handler.show_error(_('Cancelled by user'))
return '' return b''
except Exception as e: except Exception as e:
self.give_error(e, True) self.give_error(e, True)
finally: finally:
self.handler.finished() self.handler.finished()
self.signing = False
# Parse the ASN.1 signature # Parse the ASN.1 signature
rLength = signature[3] rLength = signature[3]
r = signature[4 : 4 + rLength] r = signature[4 : 4 + rLength]
@ -281,12 +299,11 @@ class Ledger_KeyStore(Hardware_KeyStore):
# And convert it # And convert it
return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s
@set_and_unset_signing
def sign_transaction(self, tx, password): def sign_transaction(self, tx, password):
if tx.is_complete(): if tx.is_complete():
return return
client = self.get_client() client = self.get_client()
self.signing = True
inputs = [] inputs = []
inputsPaths = [] inputsPaths = []
pubKeys = [] pubKeys = []
@ -360,7 +377,8 @@ class Ledger_KeyStore(Hardware_KeyStore):
for _type, address, amount in tx.outputs(): for _type, address, amount in tx.outputs():
assert _type == TYPE_ADDRESS assert _type == TYPE_ADDRESS
info = tx.output_info.get(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 index, xpubs, m = info
changePath = self.get_derivation()[2:] + "/%d/%d"%index changePath = self.get_derivation()[2:] + "/%d/%d"%index
changeAmount = amount changeAmount = amount
@ -400,7 +418,12 @@ class Ledger_KeyStore(Hardware_KeyStore):
if segwitTransaction: if segwitTransaction:
self.get_client().startUntrustedTransaction(True, inputIndex, self.get_client().startUntrustedTransaction(True, inputIndex,
chipInputs, redeemScripts[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 outputData['outputData'] = txOutput
transactionOutput = outputData['outputData'] transactionOutput = outputData['outputData']
if outputData['confirmationNeeded']: if outputData['confirmationNeeded']:
@ -423,7 +446,12 @@ class Ledger_KeyStore(Hardware_KeyStore):
while inputIndex < len(inputs): while inputIndex < len(inputs):
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
chipInputs, redeemScripts[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 outputData['outputData'] = txOutput
if firstTransaction: if firstTransaction:
transactionOutput = outputData['outputData'] transactionOutput = outputData['outputData']
@ -446,6 +474,12 @@ class Ledger_KeyStore(Hardware_KeyStore):
except UserWarning: except UserWarning:
self.handler.show_error(_('Cancelled by user')) self.handler.show_error(_('Cancelled by user'))
return 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: except BaseException as e:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
self.give_error(e, True) self.give_error(e, True)
@ -456,10 +490,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
signingPos = inputs[i][4] signingPos = inputs[i][4]
txin['signatures'][signingPos] = bh2u(signatures[i]) txin['signatures'][signingPos] = bh2u(signatures[i])
tx.raw = tx.serialize() tx.raw = tx.serialize()
self.signing = False
@set_and_unset_signing
def show_address(self, sequence, txin_type): def show_address(self, sequence, txin_type):
self.signing = True
client = self.get_client() client = self.get_client()
address_path = self.get_derivation()[2:] + "/%d/%d"%sequence address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
self.handler.show_message(_("Showing address ...")) self.handler.show_message(_("Showing address ..."))
@ -478,7 +511,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.handler.show_error(e) self.handler.show_error(e)
finally: finally:
self.handler.finished() self.handler.finished()
self.signing = False
class LedgerPlugin(HW_PluginBase): class LedgerPlugin(HW_PluginBase):
libraries_available = BTCHIP libraries_available = BTCHIP
@ -499,17 +531,17 @@ class LedgerPlugin(HW_PluginBase):
if self.libraries_available: if self.libraries_available:
self.device_manager().register_devices(self.DEVICE_IDS) 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): def get_btchip_device(self, device):
ledger = False 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): if device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c:
ledger = True 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 = hid.device()
dev.open_path(device.path) dev.open_path(device.path)
dev.set_nonblocking(True) dev.set_nonblocking(True)
@ -541,7 +573,6 @@ class LedgerPlugin(HW_PluginBase):
def get_client(self, keystore, force_pair=True): def get_client(self, keystore, force_pair=True):
# All client interaction should not be in the main GUI thread # All client interaction should not be in the main GUI thread
#assert self.main_thread != threading.current_thread()
devmgr = self.device_manager() devmgr = self.device_manager()
handler = keystore.handler handler = keystore.handler
with devmgr.hid_lock: with devmgr.hid_lock:

16
plugins/ledger/qt.py

@ -1,15 +1,13 @@
import threading #from btchip.btchipPersoWizard import StartBTChipPersoDialog
from PyQt5.Qt import QInputDialog, QLineEdit, QVBoxLayout, QLabel
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import hook from electrum.plugins import hook
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum_gui.qt.util import *
from .ledger import LedgerPlugin from .ledger import LedgerPlugin
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
from electrum_gui.qt.util import *
#from btchip.btchipPersoWizard import StartBTChipPersoDialog
class Plugin(LedgerPlugin, QtPluginBase): class Plugin(LedgerPlugin, QtPluginBase):
icon_unpaired = ":icons/ledger_unpaired.png" icon_unpaired = ":icons/ledger_unpaired.png"
@ -77,11 +75,7 @@ class Ledger_Handler(QtHandlerBase):
return return
def setup_dialog(self): def setup_dialog(self):
self.show_error(_('Initialization of Ledger HW devices is currently disabled.'))
return
dialog = StartBTChipPersoDialog() dialog = StartBTChipPersoDialog()
dialog.exec_() dialog.exec_()

4
plugins/trezor/client.py

@ -3,8 +3,8 @@ from .clientbase import TrezorClientBase
class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient): class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient):
def __init__(self, transport, handler, plugin): def __init__(self, transport, handler, plugin):
BaseClient.__init__(self, transport) BaseClient.__init__(self, transport=transport)
ProtocolMixin.__init__(self, transport) ProtocolMixin.__init__(self, transport=transport)
TrezorClientBase.__init__(self, handler, plugin, proto) TrezorClientBase.__init__(self, handler, plugin, proto)

19
plugins/trezor/clientbase.py

@ -50,6 +50,9 @@ class GuiMixin(object):
else: else:
msg = _("Enter your current {} PIN:") msg = _("Enter your current {} PIN:")
pin = self.handler.get_pin(msg.format(self.device)) 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: if not pin:
return self.proto.Cancel() return self.proto.Cancel()
return self.proto.PinMatrixAck(pin=pin) return self.proto.PinMatrixAck(pin=pin)
@ -69,7 +72,13 @@ class GuiMixin(object):
if passphrase is None: if passphrase is None:
return self.proto.Cancel() return self.proto.Cancel()
passphrase = bip39_normalize_passphrase(passphrase) 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): def callback_PassphraseStateRequest(self, msg):
return self.proto.PassphraseStateAck() return self.proto.PassphraseStateAck()
@ -116,6 +125,14 @@ class TrezorClientBase(GuiMixin, PrintError):
def is_pairable(self): def is_pairable(self):
return not self.features.bootloader_mode 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): def used(self):
self.last_operation = time.time() self.last_operation = time.time()

2
plugins/trezor/qt_generic.py

@ -251,7 +251,7 @@ class QtPlugin(QtPluginBase):
vbox.addWidget(QLabel(msg)) vbox.addWidget(QLabel(msg))
vbox.addWidget(text) vbox.addWidget(text)
pin = QLineEdit() pin = QLineEdit()
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}'))) pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
pin.setMaximumWidth(100) pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout() hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))

95
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)

126
plugins/trezor/trezor.py

@ -1,5 +1,3 @@
import threading
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from electrum.util import bfh, bh2u, versiontuple from electrum.util import bfh, bh2u, versiontuple
@ -93,7 +91,6 @@ class TrezorPlugin(HW_PluginBase):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
HW_PluginBase.__init__(self, parent, config, name) HW_PluginBase.__init__(self, parent, config, name)
self.main_thread = threading.current_thread()
try: try:
# Minimal test if python-trezor is installed # Minimal test if python-trezor is installed
@ -117,6 +114,7 @@ class TrezorPlugin(HW_PluginBase):
return return
from . import client from . import client
from . import transport
import trezorlib.ckd_public import trezorlib.ckd_public
import trezorlib.messages import trezorlib.messages
self.client_class = client.TrezorClient self.client_class = client.TrezorClient
@ -124,17 +122,17 @@ class TrezorPlugin(HW_PluginBase):
self.types = trezorlib.messages self.types = trezorlib.messages
self.DEVICE_IDS = ('TREZOR',) self.DEVICE_IDS = ('TREZOR',)
self.transport_handler = transport.TrezorTransport()
self.device_manager().register_enumerate_func(self.enumerate) self.device_manager().register_enumerate_func(self.enumerate)
def enumerate(self): def enumerate(self):
from trezorlib.device import TrezorDevice devices = self.transport_handler.enumerate_devices()
return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in TrezorDevice.enumerate()] return [Device(d.get_path(), -1, d.get_path(), 'TREZOR', 0) for d in devices]
def create_client(self, device, handler): def create_client(self, device, handler):
from trezorlib.device import TrezorDevice
try: try:
self.print_error("connecting to device at", device.path) 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: except BaseException as e:
self.print_error("cannot connect at", device.path, str(e)) self.print_error("cannot connect at", device.path, str(e))
return None return None
@ -379,56 +377,86 @@ class TrezorPlugin(HW_PluginBase):
return inputs return inputs
def tx_outputs(self, derivation, tx, script_gen=SCRIPT_GEN_LEGACY): 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 = [] outputs = []
has_change = False has_change = False
any_output_on_change_branch = is_any_output_on_change_branch()
for _type, address, amount in tx.outputs(): for _type, address, amount in tx.outputs():
use_create_by_derivation = False
info = tx.output_info.get(address) info = tx.output_info.get(address)
if info is not None and not has_change: if info is not None and not has_change:
has_change = True # no more than one change address
index, xpubs, m = info index, xpubs, m = info
if len(xpubs) == 1: on_change_branch = index[0] == 1
if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: # prioritise hiding outputs on the 'change' branch from user
script_type = self.types.OutputScriptType.PAYTOWITNESS # because no more than one change address allowed
elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: # note: ^ restriction can be removed once we require fw
script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS # that has https://github.com/trezor/trezor-mcu/pull/306
else: if on_change_branch == any_output_on_change_branch:
script_type = self.types.OutputScriptType.PAYTOADDRESS use_create_by_derivation = True
address_n = self.client_class.expand_path(derivation + "/%d/%d"%index) has_change = True
txoutputtype = self.types.TxOutputType(
amount = amount, if use_create_by_derivation:
script_type = script_type, txoutputtype = create_output_by_derivation(info)
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)
else: else:
txoutputtype = self.types.TxOutputType() txoutputtype = create_output_by_address()
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
outputs.append(txoutputtype) outputs.append(txoutputtype)
return outputs return outputs

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save