diff --git a/contrib/build-osx/README.md b/contrib/build-osx/README.md
new file mode 100644
index 000000000..c1e96d90b
--- /dev/null
+++ b/contrib/build-osx/README.md
@@ -0,0 +1,36 @@
+Building Mac OS binaries
+========================
+
+This guide explains how to build Electrum binaries for macOS systems.
+
+The build process consists of two steps:
+
+## 1. Building the binary
+
+This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it on High Sierra
+makes the binaries incompatible with older versions.
+
+Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`).
+
+
+ cd electrum
+ ./contrib/build-osx/make_osx
+
+This creates a folder named Electrum.app.
+
+## 2. Building the image
+The usual way to distribute macOS applications is to use image files containing the
+application. Although these images can be created on a Mac with the built-in `hdiutil`,
+they are not deterministic.
+
+Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus.
+These tools do not work on macOS, so you need a separate Linux machine (or VM).
+
+Copy the Electrum.app directory over and install the dependencies, e.g.:
+
+ apt install libcap-dev cmake make gcc faketime
+
+Then you can just invoke `package.sh` with the path to the app:
+
+ cd electrum
+ ./contrib/build-osx/package.sh ~/Electrum.app/
\ No newline at end of file
diff --git a/contrib/build-osx/base.sh b/contrib/build-osx/base.sh
new file mode 100644
index 000000000..c5a5c0d69
--- /dev/null
+++ b/contrib/build-osx/base.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+RED='\033[0;31m'
+BLUE='\033[0,34m'
+NC='\033[0m' # No Color
+function info {
+ printf "\rđź’¬ ${BLUE}INFO:${NC} ${1}\n"
+}
+function fail {
+ printf "\rđź—Ż ${RED}ERROR:${NC} ${1}\n"
+ exit 1
+}
diff --git a/contrib/build-osx/cdrkit-deterministic.patch b/contrib/build-osx/cdrkit-deterministic.patch
new file mode 100644
index 000000000..d01e5b75e
--- /dev/null
+++ b/contrib/build-osx/cdrkit-deterministic.patch
@@ -0,0 +1,86 @@
+--- cdrkit-1.1.11.old/genisoimage/tree.c 2008-10-21 19:57:47.000000000 -0400
++++ cdrkit-1.1.11/genisoimage/tree.c 2013-12-06 00:23:18.489622668 -0500
+@@ -1139,8 +1139,9 @@
+ scan_directory_tree(struct directory *this_dir, char *path,
+ struct directory_entry *de)
+ {
+- DIR *current_dir;
++ int current_file;
+ char whole_path[PATH_MAX];
++ struct dirent **d_list;
+ struct dirent *d_entry;
+ struct directory *parent;
+ int dflag;
+@@ -1164,7 +1165,8 @@
+ this_dir->dir_flags |= DIR_WAS_SCANNED;
+
+ errno = 0; /* Paranoia */
+- current_dir = opendir(path);
++ //current_dir = opendir(path);
++ current_file = scandir(path, &d_list, NULL, alphasort);
+ d_entry = NULL;
+
+ /*
+@@ -1173,12 +1175,12 @@
+ */
+ old_path = path;
+
+- if (current_dir) {
++ if (current_file >= 0) {
+ errno = 0;
+- d_entry = readdir(current_dir);
++ d_entry = d_list[0];
+ }
+
+- if (!current_dir || !d_entry) {
++ if (current_file < 0 || !d_entry) {
+ int ret = 1;
+
+ #ifdef USE_LIBSCHILY
+@@ -1191,8 +1193,8 @@
+ de->isorec.flags[0] &= ~ISO_DIRECTORY;
+ ret = 0;
+ }
+- if (current_dir)
+- closedir(current_dir);
++ if(d_list)
++ free(d_list);
+ return (ret);
+ }
+ #ifdef ABORT_DEEP_ISO_ONLY
+@@ -1208,7 +1210,7 @@
+ errmsgno(EX_BAD, "use Rock Ridge extensions via -R or -r,\n");
+ errmsgno(EX_BAD, "or allow deep ISO9660 directory nesting via -D.\n");
+ }
+- closedir(current_dir);
++ free(d_list);
+ return (1);
+ }
+ #endif
+@@ -1250,13 +1252,13 @@
+ * The first time through, skip this, since we already asked
+ * for the first entry when we opened the directory.
+ */
+- if (dflag)
+- d_entry = readdir(current_dir);
++ if (dflag && current_file >= 0)
++ d_entry = d_list[current_file];
+ dflag++;
+
+- if (!d_entry)
++ if (current_file < 0)
+ break;
+-
++ current_file--;
+ /* OK, got a valid entry */
+
+ /* If we do not want all files, then pitch the backups. */
+@@ -1348,7 +1350,7 @@
+ insert_file_entry(this_dir, whole_path, d_entry->d_name);
+ #endif /* APPLE_HYB */
+ }
+- closedir(current_dir);
++ free(d_list);
+
+ #ifdef APPLE_HYB
+ /*
\ No newline at end of file
diff --git a/contrib/build-osx/make_osx b/contrib/build-osx/make_osx
new file mode 100644
index 000000000..599480e23
--- /dev/null
+++ b/contrib/build-osx/make_osx
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+
+# Parameterize
+PYTHON_VERSION=3.6.4
+BUILDDIR=/tmp/electrum-build
+PACKAGE=Electrum
+GIT_REPO=https://github.com/spesmilo/electrum
+LIBSECP_VERSION=452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
+
+. $(dirname "$0")/base.sh
+
+src_dir=$(dirname "$0")
+cd $src_dir/../..
+
+export PYTHONHASHSEED=22
+VERSION=`git describe --tags --dirty --always`
+
+which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
+
+info "Installing Python $PYTHON_VERSION"
+export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH"
+if [ -d "~/.pyenv" ]; then
+ pyenv update
+else
+ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1
+fi
+PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -s $PYTHON_VERSION && \
+pyenv global $PYTHON_VERSION || \
+fail "Unable to use Python $PYTHON_VERSION"
+
+
+info "Installing pyinstaller"
+python3 -m pip install -I --user pyinstaller==3.4 || fail "Could not install pyinstaller"
+
+info "Using these versions for building $PACKAGE:"
+sw_vers
+python3 --version
+echo -n "Pyinstaller "
+pyinstaller --version
+
+rm -rf ./dist
+
+git submodule init
+git submodule update
+
+rm -rf $BUILDDIR > /dev/null 2>&1
+mkdir $BUILDDIR
+
+cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/
+cp ./contrib/deterministic-build/electrum-icons/icons_rc.py ./electrum/gui/qt/
+
+
+info "Downloading libusb..."
+curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \
+tar xz --directory $BUILDDIR
+cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx
+
+info "Building libsecp256k1"
+brew install autoconf automake libtool
+git clone https://github.com/bitcoin-core/secp256k1 $BUILDDIR/secp256k1
+pushd $BUILDDIR/secp256k1
+git reset --hard $LIBSECP_VERSION
+git clean -f -x -q
+./autogen.sh
+./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --disable-jni
+make
+popd
+cp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/build-osx
+
+
+info "Installing requirements..."
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \
+fail "Could not install requirements"
+
+info "Installing hardware wallet requirements..."
+python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \
+fail "Could not install hardware wallet requirements"
+
+info "Building $PACKAGE..."
+python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE"
+
+info "Faking timestamps..."
+for d in ~/Library/Python/ ~/.pyenv .; do
+ pushd $d
+ find . -exec touch -t '200101220000' {} +
+ popd
+done
+
+info "Building binary"
+pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary"
+
+info "Adding bitcoin URI types to Info.plist"
+plutil -insert 'CFBundleURLTypes' \
+ -xml ' CFBundleURLName bitcoin CFBundleURLSchemes bitcoin ' \
+ -- dist/$PACKAGE.app/Contents/Info.plist \
+ || fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed."
+
+info "Creating .DMG"
+hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"
diff --git a/contrib/build-osx/osx.spec b/contrib/build-osx/osx.spec
new file mode 100644
index 000000000..501df88ec
--- /dev/null
+++ b/contrib/build-osx/osx.spec
@@ -0,0 +1,104 @@
+# -*- mode: python -*-
+
+from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
+
+import sys
+import os
+
+PACKAGE='Electrum'
+PYPKG='electrum'
+MAIN_SCRIPT='run_electrum'
+ICONS_FILE='electrum.icns'
+
+for i, x in enumerate(sys.argv):
+ if x == '--name':
+ VERSION = sys.argv[i+1]
+ break
+else:
+ raise Exception('no version')
+
+electrum = os.path.abspath(".") + "/"
+block_cipher = None
+
+# see https://github.com/pyinstaller/pyinstaller/issues/2005
+hiddenimports = []
+hiddenimports += collect_submodules('trezorlib')
+hiddenimports += collect_submodules('safetlib')
+hiddenimports += collect_submodules('btchip')
+hiddenimports += collect_submodules('keepkeylib')
+hiddenimports += collect_submodules('websocket')
+hiddenimports += collect_submodules('ckcc')
+
+datas = [
+ (electrum + PYPKG + '/*.json', PYPKG),
+ (electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
+ (electrum + PYPKG + '/locale', PYPKG + '/locale'),
+ (electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
+]
+datas += collect_data_files('trezorlib')
+datas += collect_data_files('safetlib')
+datas += collect_data_files('btchip')
+datas += collect_data_files('keepkeylib')
+datas += collect_data_files('ckcc')
+
+# Add libusb so Trezor and Safe-T mini will work
+binaries = [(electrum + "contrib/build-osx/libusb-1.0.dylib", ".")]
+binaries += [(electrum + "contrib/build-osx/libsecp256k1.0.dylib", ".")]
+
+# Workaround for "Retro Look":
+binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
+
+# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
+a = Analysis([electrum+ MAIN_SCRIPT,
+ electrum+'electrum/gui/qt/main_window.py',
+ electrum+'electrum/gui/text.py',
+ electrum+'electrum/util.py',
+ electrum+'electrum/wallet.py',
+ electrum+'electrum/simple_config.py',
+ electrum+'electrum/bitcoin.py',
+ electrum+'electrum/dnssec.py',
+ electrum+'electrum/commands.py',
+ electrum+'electrum/plugins/cosigner_pool/qt.py',
+ electrum+'electrum/plugins/email_requests/qt.py',
+ electrum+'electrum/plugins/trezor/client.py',
+ electrum+'electrum/plugins/trezor/qt.py',
+ electrum+'electrum/plugins/safe_t/client.py',
+ electrum+'electrum/plugins/safe_t/qt.py',
+ electrum+'electrum/plugins/keepkey/qt.py',
+ electrum+'electrum/plugins/ledger/qt.py',
+ electrum+'electrum/plugins/coldcard/qt.py',
+ ],
+ binaries=binaries,
+ datas=datas,
+ hiddenimports=hiddenimports,
+ hookspath=[])
+
+# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
+for d in a.datas:
+ if 'pyconfig' in d[0]:
+ a.datas.remove(d)
+ break
+
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+exe = EXE(pyz,
+ a.scripts,
+ a.binaries,
+ a.datas,
+ name=PACKAGE,
+ debug=False,
+ strip=False,
+ upx=True,
+ icon=electrum+ICONS_FILE,
+ console=False)
+
+app = BUNDLE(exe,
+ version = VERSION,
+ name=PACKAGE + '.app',
+ icon=electrum+ICONS_FILE,
+ bundle_identifier=None,
+ info_plist={
+ 'NSHighResolutionCapable': 'True',
+ 'NSSupportsAutomaticGraphicsSwitching': 'True'
+ }
+)
diff --git a/contrib/build-osx/package.sh b/contrib/build-osx/package.sh
new file mode 100644
index 000000000..dcbc29388
--- /dev/null
+++ b/contrib/build-osx/package.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+cdrkit_version=1.1.11
+cdrkit_download_path=http://distro.ibiblio.org/fatdog/source/600/c
+cdrkit_file_name=cdrkit-${cdrkit_version}.tar.bz2
+cdrkit_sha256_hash=b50d64c214a65b1a79afe3a964c691931a4233e2ba605d793eb85d0ac3652564
+cdrkit_patches=cdrkit-deterministic.patch
+genisoimage=genisoimage-$cdrkit_version
+
+libdmg_url=https://github.com/theuni/libdmg-hfsplus
+
+
+export LD_PRELOAD=$(locate libfaketime.so.1)
+export FAKETIME="2000-01-22 00:00:00"
+export PATH=$PATH:~/bin
+
+. $(dirname "$0")/base.sh
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 Electrum.app"
+ exit -127
+fi
+
+mkdir -p ~/bin
+
+if ! which ${genisoimage} > /dev/null 2>&1; then
+ mkdir -p /tmp/electrum-macos
+ cd /tmp/electrum-macos
+ info "Downloading cdrkit $cdrkit_version"
+ wget -nc ${cdrkit_download_path}/${cdrkit_file_name}
+ tar xvf ${cdrkit_file_name}
+
+ info "Patching genisoimage"
+ cd cdrkit-${cdrkit_version}
+ patch -p1 < ../cdrkit-deterministic.patch
+
+ info "Building genisoimage"
+ cmake . -Wno-dev
+ make genisoimage
+ cp genisoimage/genisoimage ~/bin/${genisoimage}
+fi
+
+if ! which dmg > /dev/null 2>&1; then
+ mkdir -p /tmp/electrum-macos
+ cd /tmp/electrum-macos
+ info "Downloading libdmg"
+ LD_PRELOAD= git clone ${libdmg_url}
+ cd libdmg-hfsplus
+ info "Building libdmg"
+ cmake .
+ make
+ cp dmg/dmg ~/bin
+fi
+
+${genisoimage} -version || fail "Unable to install genisoimage"
+dmg -|| fail "Unable to install libdmg"
+
+plist=$1/Contents/Info.plist
+test -f "$plist" || fail "Info.plist not found"
+VERSION=$(grep -1 ShortVersionString $plist |tail -1|gawk 'match($0, /(.*)<\/string>/, a) {print a[1]}')
+echo $VERSION
+
+rm -rf /tmp/electrum-macos/image > /dev/null 2>&1
+mkdir /tmp/electrum-macos/image/
+cp -r $1 /tmp/electrum-macos/image/
+
+build_dir=$(dirname "$1")
+test -n "$build_dir" -a -d "$build_dir" || exit
+cd $build_dir
+
+${genisoimage} \
+ -no-cache-inodes \
+ -D \
+ -l \
+ -probe \
+ -V "Electrum" \
+ -no-pad \
+ -r \
+ -dir-mode 0755 \
+ -apple \
+ -o Electrum_uncompressed.dmg \
+ /tmp/electrum-macos/image || fail "Unable to create uncompressed dmg"
+
+dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to create compressed dmg"
+rm Electrum_uncompressed.dmg
+
+echo "Done."
+md5sum electrum-$VERSION.dmg
diff --git a/contrib/build-wine/build-secp256k1.sh b/contrib/build-wine/build-secp256k1.sh
new file mode 100644
index 000000000..30d4a598b
--- /dev/null
+++ b/contrib/build-wine/build-secp256k1.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# heavily based on https://github.com/ofek/coincurve/blob/417e726f553460f88d7edfa5dc67bfda397c4e4a/.travis/build_windows_wheels.sh
+
+set -e
+
+build_dll() {
+ #sudo apt-get install -y mingw-w64
+ export SOURCE_DATE_EPOCH=1530212462
+ ./autogen.sh
+ echo "LDFLAGS = -no-undefined" >> Makefile.am
+ LDFLAGS="-Wl,--no-insert-timestamp" ./configure \
+ --host=$1 \
+ --enable-module-recovery \
+ --enable-experimental \
+ --enable-module-ecdh \
+ --disable-jni
+ make
+ ${1}-strip .libs/libsecp256k1-0.dll
+}
+
+
+cd /tmp/electrum-build
+
+if [ ! -d secp256k1 ]; then
+ git clone https://github.com/bitcoin-core/secp256k1.git
+ cd secp256k1;
+else
+ cd secp256k1
+ git pull
+fi
+
+git reset --hard 452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
+git clean -f -x -q
+
+build_dll i686-w64-mingw32 # 64-bit would be: x86_64-w64-mingw32
+mv .libs/libsecp256k1-0.dll libsecp256k1.dll
+
+find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
+
+echo "building libsecp256k1 finished"
diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec
index 79d86067d..a5c225678 100644
--- a/contrib/build-wine/deterministic.spec
+++ b/contrib/build-wine/deterministic.spec
@@ -118,7 +118,7 @@ exe_standalone = EXE(
a.scripts,
a.binaries,
a.datas,
- name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + ".exe"),
+ name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name + ".exe"),
debug=False,
strip=None,
upx=False,
@@ -131,7 +131,7 @@ exe_portable = EXE(
a.scripts,
a.binaries,
a.datas + [ ('is_portable', 'README.md', 'DATA' ) ],
- name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + "-portable.exe"),
+ name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name + "-portable.exe"),
debug=False,
strip=None,
upx=False,
@@ -145,7 +145,7 @@ exe_dependent = EXE(
pyz,
a.scripts,
exclude_binaries=True,
- name=os.path.join('build\\pyi.win32\\electrum', cmdline_name),
+ name=os.path.join('build\\pyi.win32\\electrum-lbry', cmdline_name),
debug=False,
strip=None,
upx=False,
diff --git a/contrib/build-wine/docker/Dockerfile b/contrib/build-wine/docker/Dockerfile
new file mode 100644
index 000000000..13d335228
--- /dev/null
+++ b/contrib/build-wine/docker/Dockerfile
@@ -0,0 +1,34 @@
+FROM ubuntu:18.04@sha256:5f4bdc3467537cbbe563e80db2c3ec95d548a9145d64453b06939c4592d67b6d
+
+ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
+
+RUN dpkg --add-architecture i386 && \
+ apt-get update -q && \
+ apt-get install -qy \
+ wget=1.19.4-1ubuntu2.1 \
+ gnupg2=2.2.4-1ubuntu1.1 \
+ dirmngr=2.2.4-1ubuntu1.1 \
+ python3-software-properties=0.96.24.32.1 \
+ software-properties-common=0.96.24.32.1 \
+ && \
+ wget -nc https://dl.winehq.org/wine-builds/Release.key && \
+ apt-key add Release.key && \
+ apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
+ apt-get update -q && \
+ apt-get install -qy \
+ wine-stable-amd64:amd64=3.0.1~bionic \
+ wine-stable-i386:i386=3.0.1~bionic \
+ wine-stable:amd64=3.0.1~bionic \
+ winehq-stable:amd64=3.0.1~bionic \
+ git \
+ p7zip-full=16.02+dfsg-6 \
+ make=4.1-9.1ubuntu1 \
+ mingw-w64=5.0.3-1 \
+ autotools-dev=20180224.1 \
+ autoconf=2.69-11 \
+ libtool=2.4.6-2 \
+ gettext=0.19.8.1-6 \
+ && \
+ rm -rf /var/lib/apt/lists/* && \
+ apt-get autoremove -y && \
+ apt-get clean
diff --git a/contrib/build-wine/docker/README.md b/contrib/build-wine/docker/README.md
new file mode 100644
index 000000000..aba87018f
--- /dev/null
+++ b/contrib/build-wine/docker/README.md
@@ -0,0 +1,103 @@
+Deterministic Windows binaries with Docker
+==========================================
+
+Produced binaries are deterministic, so you should be able to generate
+binaries that match the official releases.
+
+This assumes an Ubuntu host, but it should not be too hard to adapt to another
+similar system. The docker commands should be executed in the project's root
+folder.
+
+1. Install Docker
+
+ ```
+ $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+ $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+ $ sudo apt-get update
+ $ sudo apt-get install -y docker-ce
+ ```
+
+2. Build image
+
+ ```
+ $ sudo docker build --no-cache -t electrum-wine-builder-img contrib/build-wine/docker
+ ```
+
+ Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems
+
+3. Build Windows binaries
+
+ It's recommended to build from a fresh clone
+ (but you can skip this if reproducibility is not necessary).
+
+ ```
+ $ FRESH_CLONE=contrib/build-wine/fresh_clone && \
+ rm -rf $FRESH_CLONE && \
+ mkdir -p $FRESH_CLONE && \
+ cd $FRESH_CLONE && \
+ git clone https://github.com/spesmilo/electrum.git && \
+ cd electrum
+ ```
+
+ And then build from this directory:
+ ```
+ $ git checkout $REV
+ $ sudo docker run \
+ --name electrum-wine-builder-cont \
+ -v $PWD:/opt/wine64/drive_c/electrum \
+ --rm \
+ --workdir /opt/wine64/drive_c/electrum/contrib/build-wine \
+ electrum-wine-builder-img \
+ ./build.sh
+ ```
+4. The generated binaries are in `./contrib/build-wine/dist`.
+
+
+
+Note: the `setup` binary (NSIS installer) is not deterministic yet.
+
+
+Code Signing
+============
+
+Electrum Windows builds are signed with a Microsoft Authenticode™ code signing
+certificate in addition to the GPG-based signatures.
+
+The advantage of using Authenticode is that Electrum users won't receive a
+Windows SmartScreen warning when starting it.
+
+The release signing procedure involves a signer (the holder of the
+certificate/key) and one or multiple trusted verifiers:
+
+
+| Signer | Verifier |
+|-----------------------------------------------------------|-----------------------------------|
+| Build .exe files using `build.sh` | |
+| Sign .exe with `./sign.sh` | |
+| Upload signed files to download server | |
+| | Build .exe files using `build.sh` |
+| | Compare files using `unsign.sh` |
+| | Sign .exe file using `gpg -b` |
+
+| Signer and verifiers: |
+|-----------------------------------------------------------------------------------------------|
+| Upload signatures to 'electrum-signatures' repo, as `$version/$filename.$builder.asc` |
+
+
+
+Verify Integrity of signed binary
+=================================
+
+Every user can verify that the official binary was created from the source code in this
+repository. To do so, the Authenticode signature needs to be stripped since the signature
+is not reproducible.
+
+This procedure removes the differences between the signed and unsigned binary:
+
+1. Remove the signature from the signed binary using osslsigncode or signtool.
+2. Set the COFF image checksum for the signed binary to 0x0. This is necessary
+ because pyinstaller doesn't generate a checksum.
+3. Append null bytes to the _unsigned_ binary until the byte count is a multiple
+ of 8.
+
+The script `unsign.sh` performs these steps.
diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi
index 1946c7749..f5ea8eaf6 100644
--- a/contrib/build-wine/electrum.nsi
+++ b/contrib/build-wine/electrum.nsi
@@ -2,11 +2,11 @@
;Include Modern UI
!include "TextFunc.nsh" ;Needed for the $GetSize function. I know, doesn't sound logical, it isn't.
!include "MUI2.nsh"
-
+
;--------------------------------
;Variables
- !define PRODUCT_NAME "Electrum"
+ !define PRODUCT_NAME "LBRY_Wallet"
!define PRODUCT_WEB_SITE "https://github.com/spesmilo/electrum"
!define PRODUCT_PUBLISHER "Electrum Technologies GmbH"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
@@ -16,7 +16,7 @@
;Name and file
Name "${PRODUCT_NAME}"
- OutFile "dist/electrum-setup.exe"
+ OutFile "dist/electrum-lbry-setup.exe"
;Default installation folder
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
@@ -29,31 +29,31 @@
;Specifies whether or not the installer will perform a CRC on itself before allowing an install
CRCCheck on
-
+
;Sets whether or not the details of the install are shown. Can be 'hide' (the default) to hide the details by default, allowing the user to view them, or 'show' to show them by default, or 'nevershow', to prevent the user from ever seeing them.
ShowInstDetails show
-
+
;Sets whether or not the details of the uninstall are shown. Can be 'hide' (the default) to hide the details by default, allowing the user to view them, or 'show' to show them by default, or 'nevershow', to prevent the user from ever seeing them.
ShowUninstDetails show
-
+
;Sets the colors to use for the install info screen (the default is 00FF00 000000. Use the form RRGGBB (in hexadecimal, as in HTML, only minus the leading '#', since # can be used for comments). Note that if "/windows" is specified as the only parameter, the default windows colors will be used.
InstallColors /windows
-
+
;This command sets the compression algorithm used to compress files/data in the installer. (http://nsis.sourceforge.net/Reference/SetCompressor)
SetCompressor /SOLID lzma
-
+
;Sets the dictionary size in megabytes (MB) used by the LZMA compressor (default is 8 MB).
SetCompressorDictSize 64
-
+
;Sets the text that is shown (by default it is 'Nullsoft Install System vX.XX') in the bottom of the install window. Setting this to an empty string ("") uses the default; to set the string to blank, use " " (a space).
- BrandingText "${PRODUCT_NAME} Installer v${PRODUCT_VERSION}"
-
+ BrandingText "${PRODUCT_NAME} Installer v${PRODUCT_VERSION}"
+
;Sets what the titlebars of the installer will display. By default, it is 'Name Setup', where Name is specified with the Name command. You can, however, override it with 'MyApp Installer' or whatever. If you specify an empty string (""), the default will be used (you can however specify " " to achieve a blank string)
Caption "${PRODUCT_NAME}"
;Adds the Product Version on top of the Version Tab in the Properties of the file.
VIProductVersion 1.0.0.0
-
+
;VIAddVersionKey - Adds a field in the Version Tab of the File Properties. This can either be a field provided by the system or a user defined field.
VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
VIAddVersionKey Comments "The installer for ${PRODUCT_NAME}"
@@ -63,7 +63,7 @@
VIAddVersionKey FileVersion ${PRODUCT_VERSION}
VIAddVersionKey ProductVersion ${PRODUCT_VERSION}
VIAddVersionKey InternalName "${PRODUCT_NAME} Installer"
- VIAddVersionKey LegalTrademarks "${PRODUCT_NAME} is a trademark of ${PRODUCT_PUBLISHER}"
+ VIAddVersionKey LegalTrademarks "${PRODUCT_NAME} is a trademark of ${PRODUCT_PUBLISHER}"
VIAddVersionKey OriginalFilename "${PRODUCT_NAME}.exe"
;--------------------------------
@@ -71,9 +71,9 @@
!define MUI_ABORTWARNING
!define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?"
-
+
!define MUI_ICON "c:\electrum\electrum\gui\icons\electrum.ico"
-
+
;--------------------------------
;Pages
@@ -108,7 +108,7 @@ Section
RMDir /r "$INSTDIR\*.*"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
-
+
;Files to pack into the installer
File /r "dist\electrum\*.*"
File "c:\electrum\electrum\gui\icons\electrum.ico"
@@ -122,21 +122,23 @@ Section
;Create desktop shortcut
DetailPrint "Creating desktop shortcut..."
- CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" ""
+ CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-lbry-${PRODUCT_VERSION}.exe" ""
;Create start-menu items
DetailPrint "Creating start-menu items..."
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
- CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" 0
- CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME} Testnet.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "--testnet" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" 0
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "" "$INSTDIR\electrum-lbry-${PRODUCT_VERSION}.exe" 0
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME} Testnet.lnk" "$INSTDIR\electrum-${PRODUCT_VERSION}.exe" "--testnet" "$INSTDIR\electrum-lbry-${PRODUCT_VERSION}.exe" 0
+
+
;Links bitcoin: URI's to Electrum
WriteRegStr HKCU "Software\Classes\bitcoin" "" "URL:bitcoin Protocol"
WriteRegStr HKCU "Software\Classes\bitcoin" "URL Protocol" ""
WriteRegStr HKCU "Software\Classes\bitcoin" "DefaultIcon" "$\"$INSTDIR\electrum.ico, 0$\""
- WriteRegStr HKCU "Software\Classes\bitcoin\shell\open\command" "" "$\"$INSTDIR\electrum-${PRODUCT_VERSION}.exe$\" $\"%1$\""
+ WriteRegStr HKCU "Software\Classes\bitcoin\shell\open\command" "" "$\"$INSTDIR\electrum-lbry-${PRODUCT_VERSION}.exe$\" $\"%1$\""
;Adds an uninstaller possibility to Windows Uninstall or change a program section
WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
@@ -166,8 +168,8 @@ Section "Uninstall"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
-
- DeleteRegKey HKCU "Software\Classes\bitcoin"
+
+ DeleteRegKey HKCU "Software\Classes\lbc"
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}"
SectionEnd
diff --git a/contrib/make_locale b/contrib/make_locale
new file mode 100644
index 000000000..3c28d5702
--- /dev/null
+++ b/contrib/make_locale
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+import os
+import subprocess
+import io
+import zipfile
+import requests
+
+os.chdir(os.path.dirname(os.path.realpath(__file__)))
+os.chdir('..')
+
+cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
+
+files = subprocess.check_output(cmd, shell=True)
+
+with open("app.fil", "wb") as f:
+ f.write(files)
+
+print("Found {} files to translate".format(len(files.splitlines())))
+
+# Generate fresh translation template
+if not os.path.exists('electrum/locale'):
+ os.mkdir('electrum/locale')
+cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
+print('Generate template')
+os.system(cmd)
+
+os.chdir('electrum')
+
+crowdin_identifier = 'electrum'
+crowdin_file_name = 'files[electrum-client/messages.pot]'
+locale_file_name = 'locale/messages.pot'
+crowdin_api_key = None
+
+filename = os.path.expanduser('~/.crowdin_api_key')
+if os.path.exists(filename):
+ with open(filename) as f:
+ crowdin_api_key = f.read().strip()
+
+if "crowdin_api_key" in os.environ:
+ crowdin_api_key = os.environ["crowdin_api_key"]
+
+if crowdin_api_key:
+ # Push to Crowdin
+ print('Push to Crowdin')
+ url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key)
+ with open(locale_file_name, 'rb') as f:
+ files = {crowdin_file_name: f}
+ response = requests.request('POST', url, files=files)
+ print("", "update-file:", "-"*20, response.text, "-"*20, sep="\n")
+ # Build translations
+ print('Build translations')
+ response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key)
+ print("", "export:", "-" * 20, response.text, "-" * 20, sep="\n")
+
+# Download & unzip
+print('Download translations')
+s = requests.request('GET', 'https://crowdin.com/backend/download/project/' + crowdin_identifier + '.zip').content
+zfobj = zipfile.ZipFile(io.BytesIO(s))
+
+print('Unzip translations')
+for name in zfobj.namelist():
+ if not name.startswith('electrum-client/locale'):
+ continue
+ if name.endswith('/'):
+ if not os.path.exists(name[16:]):
+ os.mkdir(name[16:])
+ else:
+ with open(name[16:], 'wb') as output:
+ output.write(zfobj.read(name))
+
+# Convert .po to .mo
+print('Installing')
+for lang in os.listdir('locale'):
+ if lang.startswith('messages'):
+ continue
+ # Check LC_MESSAGES folder
+ mo_dir = 'locale/%s/LC_MESSAGES' % lang
+ if not os.path.exists(mo_dir):
+ os.mkdir(mo_dir)
+ cmd = 'msgfmt --output-file="%s/electrum.mo" "locale/%s/electrum.po"' % (mo_dir,lang)
+ print('Installing', lang)
+ os.system(cmd)
diff --git a/electrum.icns b/electrum.icns
new file mode 100644
index 000000000..977b124d0
Binary files /dev/null and b/electrum.icns differ
diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py
index 7f1f5e1a5..93de6f91f 100644
--- a/electrum/base_wizard.py
+++ b/electrum/base_wizard.py
@@ -139,7 +139,7 @@ class BaseWizard(Logger):
('standard', _("Standard wallet")),
('2fa', _("Wallet with two-factor authentication")),
('multisig', _("Multi-signature wallet")),
- ('imported', _("Import Bitcoin addresses or private keys")),
+ ('imported', _("Import LBRY Credits addresses or private keys")),
]
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
@@ -211,8 +211,8 @@ class BaseWizard(Logger):
def import_addresses_or_keys(self):
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True)
- title = _("Import Bitcoin Addresses")
- message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.")
+ title = _("Import LBRY Credits Addresses")
+ message = _("Enter a list of LBRY Credits addresses (this will create a watching-only wallet), or a list of private keys.")
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
is_valid=v, allow_multi=True, show_wif_help=True)
@@ -393,14 +393,13 @@ class BaseWizard(Logger):
# There is no general standard for HD multisig.
# For legacy, this is partially compatible with BIP45; assumes index=0
# For segwit, a custom path is used, as there is no standard at all.
- default_choice_idx = 2
+ default_choice_idx = 0
choices = [
('standard', 'legacy multisig (p2sh)', normalize_bip32_derivation("m/45'/0")),
- ('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
- ('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
+
]
else:
- default_choice_idx = 2
+ default_choice_idx = 0
choices = [
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
@@ -625,11 +624,11 @@ class BaseWizard(Logger):
_("The type of addresses used by your wallet will depend on your seed."),
_("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Please note that websites and other wallets may not support these addresses yet."),
- _("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
+ _("Thus, you might want to keep using a non-segwit wallet in order to be able to receive LBRY Credits during the transition period.")
])
if choices is None:
choices = [
- ('create_segwit_seed', _('Segwit')),
+
('create_standard_seed', _('Legacy')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
diff --git a/electrum/blockchain.py b/electrum/blockchain.py
index 259941bcd..96e21b0db 100644
--- a/electrum/blockchain.py
+++ b/electrum/blockchain.py
@@ -24,6 +24,9 @@ import os
import threading
from typing import Optional, Dict, Mapping, Sequence
+import hashlib
+import hmac
+
from . import util
from .bitcoin import hash_encode, int_to_hex, rev_hex
from .crypto import sha256d
@@ -35,9 +38,10 @@ from .logging import get_logger, Logger
_logger = get_logger(__name__)
-HEADER_SIZE = 80 # bytes
-MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
-
+HEADER_SIZE = 112 # bytes
+MAX_TARGET = 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+GENESIS_BITS = 0x1f00ffff
+N_TARGET_TIMESPAN = 150
class MissingHeader(Exception):
pass
@@ -49,6 +53,7 @@ def serialize_header(header_dict: dict) -> str:
s = int_to_hex(header_dict['version'], 4) \
+ rev_hex(header_dict['prev_block_hash']) \
+ rev_hex(header_dict['merkle_root']) \
+ + rev_hex(header_dict['claim_trie_root']) \
+ int_to_hex(int(header_dict['timestamp']), 4) \
+ int_to_hex(int(header_dict['bits']), 4) \
+ int_to_hex(int(header_dict['nonce']), 4)
@@ -64,9 +69,10 @@ def deserialize_header(s: bytes, height: int) -> dict:
h['version'] = hex_to_int(s[0:4])
h['prev_block_hash'] = hash_encode(s[4:36])
h['merkle_root'] = hash_encode(s[36:68])
- h['timestamp'] = hex_to_int(s[68:72])
- h['bits'] = hex_to_int(s[72:76])
- h['nonce'] = hex_to_int(s[76:80])
+ h['claim_trie_root'] = hash_encode(s[68:100])
+ h['timestamp'] = hex_to_int(s[100:104])
+ h['bits'] = hex_to_int(s[104:108])
+ h['nonce'] = hex_to_int(s[108:112])
h['block_height'] = height
return h
@@ -77,10 +83,35 @@ def hash_header(header: dict) -> str:
header['prev_block_hash'] = '00'*32
return hash_raw_header(serialize_header(header))
+def pow_hash_header(header: dict) -> str:
+ if header is None:
+ return '0' * 64
+ return hash_encode(PoWHash(bfh(serialize_header(header))))
+
+def sha256(x):
+ return hashlib.sha256(x).digest()
+
+def sha512(x):
+ return hashlib.sha512(x).digest()
+
+def ripemd160(x):
+ h = hashlib.new('ripemd160')
+ h.update(x)
+ return h.digest()
+
+def Hash(x):
+ return sha256(sha256(x))
def hash_raw_header(header: str) -> str:
return hash_encode(sha256d(bfh(header)))
+def PoWHash(x):
+
+ r = sha512(Hash(x))
+ r1 = ripemd160(r[:len(r) // 2])
+ r2 = ripemd160(r[len(r) // 2:])
+ r3 = Hash(r1 + r2)
+ return r3
# key: blockhash hex at forkpoint
# the chain at some key is the best chain that includes the given hash
@@ -282,35 +313,37 @@ class Blockchain(Logger):
self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0
@classmethod
- def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
- _hash = hash_header(header)
- if expected_header_hash and expected_header_hash != _hash:
- raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
+ def verify_header(self, header: dict, prev_hash: str, target: int, bits: int, expected_header_hash: str=None) -> None:
+ _hash = pow_hash_header(header)
+ if expected_header_hash:
+ _hash2 = hash_header(header)
+ if expected_header_hash != _hash2:
+ raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash2))
if prev_hash != header.get('prev_block_hash'):
raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
if constants.net.TESTNET:
return
- bits = cls.target_to_bits(target)
- if bits != header.get('bits'):
- raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
- block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big')
- if block_hash_as_num > target:
- raise Exception(f"insufficient proof of work: {block_hash_as_num} vs target {target}")
+
+ #if bits != header.get('bits'):
+ # raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
+ #if int('0x' + _hash, 16) > target:
+ # raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
def verify_chunk(self, index: int, data: bytes) -> None:
num = len(data) // HEADER_SIZE
start_height = index * 2016
prev_hash = self.get_hash(start_height - 1)
- target = self.get_target(index-1)
for i in range(num):
height = start_height + i
+ header = self.read_header(height - 1)
+ #bits, target = self.get_target2(height - 1, header)
try:
expected_header_hash = self.get_hash(height)
except MissingHeader:
expected_header_hash = None
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
header = deserialize_header(raw_header, index*2016 + i)
- self.verify_header(header, prev_hash, target, expected_header_hash)
+ self.verify_header(header, prev_hash, 0, 0, expected_header_hash)
prev_hash = hash_header(header)
@with_lock
@@ -507,18 +540,73 @@ class Blockchain(Logger):
bits = last.get('bits')
target = self.bits_to_target(bits)
nActualTimespan = last.get('timestamp') - first.get('timestamp')
- nTargetTimespan = 14 * 24 * 60 * 60
- nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
- nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
- new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
- # not any target can be represented in 32 bits:
- new_target = self.bits_to_target(self.target_to_bits(new_target))
- return new_target
+ nTargetTimespan = 150
+ nModulatedTimespan = nTargetTimespan - (nActualTimespan - nTargetTimespan) / 8
+ nMinTimespan = nTargetTimespan - (nTargetTimespan / 8)
+ nMaxTimespan = nTargetTimespan + (nTargetTimespan / 2)
+ if nModulatedTimespan < nMinTimespan:
+ nModulatedTimespan = nMinTimespan
+ elif nModulatedTimespan > nMaxTimespan:
+ nModulatedTimespan = nMaxTimespan
+
+ bnOld = ArithUint256.SetCompact(bits)
+ bnNew = bnOld * nModulatedTimespan
+ # this doesn't work if it is nTargetTimespan even though that
+ # is what it looks like it should be based on reading the code
+ # in lbry.cpp
+ bnNew /= nModulatedTimespan
+ if bnNew > MAX_TARGET:
+ bnNew = ArithUint256(MAX_TARGET)
+ return bnNew.compact(), bnNew._value
+
+ def get_target2(self, index, last, chain='main'):
+
+ if index == -1:
+ return GENESIS_BITS, MAX_TARGET
+ if index == 0:
+ return GENESIS_BITS, MAX_TARGET
+ first = self.read_header(index-1)
+ assert last is not None, "Last shouldn't be none"
+ # bits to target
+ bits = last.get('bits')
+ # print_error("Last bits: ", bits)
+ self.check_bits(bits)
+
+ # new target
+ nActualTimespan = last.get('timestamp') - first.get('timestamp')
+ nTargetTimespan = N_TARGET_TIMESPAN
+ nModulatedTimespan = nTargetTimespan - (nActualTimespan - nTargetTimespan) / 8
+ nMinTimespan = nTargetTimespan - (nTargetTimespan / 8)
+ nMaxTimespan = nTargetTimespan + (nTargetTimespan / 2)
+ if nModulatedTimespan < nMinTimespan:
+ nModulatedTimespan = nMinTimespan
+ elif nModulatedTimespan > nMaxTimespan:
+ nModulatedTimespan = nMaxTimespan
+
+ bnOld = ArithUint256.SetCompact(bits)
+ bnNew = bnOld * nModulatedTimespan
+ # this doesn't work if it is nTargetTimespan even though that
+ # is what it looks like it should be based on reading the code
+ # in lbry.cpp
+ bnNew /= nModulatedTimespan
+ if bnNew > MAX_TARGET:
+ bnNew = ArithUint256(MAX_TARGET)
+ return bnNew.compact, bnNew._value
+
+ def check_bits(self, bits):
+ bitsN = (bits >> 24) & 0xff
+ assert 0x03 <= bitsN <= 0x1f, \
+ "First part of bits should be in [0x03, 0x1d], but it was {}".format(hex(bitsN))
+ bitsBase = bits & 0xffffff
+ assert 0x8000 <= bitsBase <= 0x7fffff, \
+ "Second part of bits should be in [0x8000, 0x7fffff] but it was {}".format(bitsBase)
+
+
@classmethod
def bits_to_target(cls, bits: int) -> int:
bitsN = (bits >> 24) & 0xff
- if not (0x03 <= bitsN <= 0x1d):
+ if not (0x03 <= bitsN <= 0x1f):
raise Exception("First part of bits should be in [0x03, 0x1d]")
bitsBase = bits & 0xffffff
if not (0x8000 <= bitsBase <= 0x7fffff):
@@ -573,25 +661,33 @@ class Blockchain(Logger):
def can_connect(self, header: dict, check_height: bool=True) -> bool:
if header is None:
return False
+
height = header['block_height']
if check_height and self.height() != height - 1:
+ print("cannot connect at height", height)
return False
if height == 0:
return hash_header(header) == constants.net.GENESIS
+
try:
prev_hash = self.get_hash(height - 1)
except:
return False
+
if prev_hash != header.get('prev_block_hash'):
return False
+
try:
- target = self.get_target(height // 2016 - 1)
+ bits, target = self.get_target2(height, header)
except MissingHeader:
return False
+
try:
- self.verify_header(header, prev_hash, target)
+ self.verify_header(header, prev_hash, target, bits)
except BaseException as e:
+ print(e)
return False
+
return True
def connect_chunk(self, idx: int, hexdata: str) -> bool:
@@ -632,3 +728,83 @@ def can_connect(header: dict) -> Optional[Blockchain]:
if b.can_connect(header):
return b
return None
+
+class ArithUint256:
+ # https://github.com/bitcoin/bitcoin/blob/master/src/arith_uint256.cpp
+
+ __slots__ = '_value', '_compact'
+
+ def __init__(self, value: int) -> None:
+ self._value = value
+ self._compact: Optional[int] = None
+
+ @classmethod
+ def SetCompact(cls, nCompact):
+ return (ArithUint256.from_compact(nCompact))
+
+ @classmethod
+ def from_compact(cls, compact) -> 'ArithUint256':
+ size = compact >> 24
+ word = compact & 0x007fffff
+ if size <= 3:
+ return cls(word >> 8 * (3 - size))
+ else:
+ return cls(word << 8 * (size - 3))
+
+ @property
+ def value(self) -> int:
+ return self._value
+
+ @property
+ def compact(self) -> int:
+ if self._compact is None:
+ self._compact = self._calculate_compact()
+ return self._compact
+
+ @property
+ def negative(self) -> int:
+ return self._calculate_compact(negative=True)
+
+ @property
+ def bits(self) -> int:
+ """ Returns the position of the highest bit set plus one. """
+ bits = bin(self._value)[2:]
+ for i, d in enumerate(bits):
+ if d:
+ return (len(bits) - i) + 1
+ return 0
+
+ @property
+ def low64(self) -> int:
+ return self._value & 0xffffffffffffffff
+
+ def _calculate_compact(self, negative=False) -> int:
+ size = (self.bits + 7) // 8
+ if size <= 3:
+ compact = self.low64 << 8 * (3 - size)
+ else:
+ compact = ArithUint256(self._value >> 8 * (size - 3)).low64
+ # The 0x00800000 bit denotes the sign.
+ # Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
+ if compact & 0x00800000:
+ compact >>= 8
+ size += 1
+ assert (compact & ~0x007fffff) == 0
+ assert size < 256
+ compact |= size << 24
+ if negative and compact & 0x007fffff:
+ compact |= 0x00800000
+ return compact
+
+ def __mul__(self, x):
+ # Take the mod because we are limited to an unsigned 256 bit number
+ return ArithUint256((self._value * x) % 2 ** 256)
+
+ def __truediv__(self, x):
+ return ArithUint256(int(self._value / x))
+
+ def __gt__(self, other):
+ return self._value > other
+
+ def __lt__(self, other):
+ return self._value < other
diff --git a/electrum/commands.py b/electrum/commands.py
index 95c065e4f..396099788 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -239,7 +239,7 @@ class Commands:
@command('')
async def restore(self, text, passphrase=None, password=None, encrypt_file=True, wallet_path=None):
"""Restore a wallet from text. Text can be a seed phrase, a master
- public key, a master private key, a list of bitcoin addresses
+ public key, a master private key, a list of LBRY Credits addresses
or bitcoin private keys.
If you want to be prompted for an argument, type '?' or ':' (concealed)
"""
@@ -667,7 +667,7 @@ class Commands:
@command('w')
async def setlabel(self, key, label, wallet: Abstract_Wallet = None):
- """Assign a label to an item. Item may be a bitcoin address or a
+ """Assign a label to an item. Item may be a LBRY Credits address or a
transaction ID"""
wallet.set_label(key, label)
@@ -1012,8 +1012,8 @@ def eval_bool(x: str) -> bool:
param_descriptions = {
'privkey': 'Private key. Type \'?\' to get a prompt.',
- 'destination': 'Bitcoin address, contact or alias',
- 'address': 'Bitcoin address',
+ 'destination': 'LBRY Credits address, contact or alias',
+ 'address': 'LBRY Credits address',
'seed': 'Seed phrase',
'txid': 'Transaction ID',
'pos': 'Position',
@@ -1023,8 +1023,8 @@ param_descriptions = {
'pubkey': 'Public key',
'message': 'Clear text message. Use quotes if it contains spaces.',
'encrypted': 'Encrypted message',
- 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
- 'requested_amount': 'Requested amount (in BTC).',
+ 'amount': 'Amount to be sent (in LBC). Type \'!\' to send the maximum available.',
+ 'requested_amount': 'Requested amount (in LBC).',
'outputs': 'list of ["address", amount]',
'redeem_script': 'redeem script (hexadecimal)',
}
@@ -1042,7 +1042,7 @@ command_options = {
'labels': ("-l", "Show the labels of listed addresses"),
'nocheck': (None, "Do not verify aliases"),
'imax': (None, "Maximum number of inputs"),
- 'fee': ("-f", "Transaction fee (absolute, in BTC)"),
+ 'fee': ("-f", "Transaction fee (absolute, in LBC)"),
'feerate': (None, "Transaction fee rate (in sat/byte)"),
'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."),
'from_coins': (None, "Source coins (must be in wallet; use sweep to spend from non-wallet address)."),
@@ -1062,7 +1062,7 @@ command_options = {
'timeout': (None, "Timeout in seconds"),
'force': (None, "Create new address beyond gap limit, if no more addresses are available."),
'pending': (None, "Show only pending requests."),
- 'push_amount': (None, 'Push initial amount (in BTC)'),
+ 'push_amount': (None, 'Push initial amount (in LBC)'),
'expired': (None, "Show only expired requests."),
'paid': (None, "Show only paid requests."),
'show_addresses': (None, "Show input and output addresses"),
@@ -1106,10 +1106,10 @@ config_variables = {
'addrequest': {
'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
- 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
+ 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of lbry credits: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
},
'listrequests':{
- 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
+ 'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of lbry credits: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
}
}
@@ -1194,7 +1194,7 @@ def get_parser():
subparsers = parser.add_subparsers(dest='cmd', metavar='')
# gui
parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
- parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
+ parser_gui.add_argument("url", nargs='?', default=None, help="lbry credits URI (or bip70 file)")
parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio'])
parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
diff --git a/electrum/constants.py b/electrum/constants.py
index f049e4f76..77bada886 100644
--- a/electrum/constants.py
+++ b/electrum/constants.py
@@ -26,6 +26,7 @@
import os
import json
+BLOCKS_PER_CHUNK = 96
from .util import inv_dict
from . import bitcoin
@@ -60,14 +61,14 @@ class AbstractNet:
class BitcoinMainnet(AbstractNet):
TESTNET = False
- WIF_PREFIX = 0x80
- ADDRTYPE_P2PKH = 0
- ADDRTYPE_P2SH = 5
- SEGWIT_HRP = "bc"
- GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
+ WIF_PREFIX = 0x1c
+ ADDRTYPE_P2PKH = 0x55
+ ADDRTYPE_P2SH = 0x7A
+ SEGWIT_HRP = "lbc"
+ GENESIS = "9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463"
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
DEFAULT_SERVERS = read_json('servers.json', {})
- CHECKPOINTS = read_json('checkpoints.json', [])
+ CHECKPOINTS = read_json('bullshit.json', [])
BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS = 497000
XPRV_HEADERS = {
@@ -86,7 +87,7 @@ class BitcoinMainnet(AbstractNet):
'p2wsh': 0x02aa7ed3, # Zpub
}
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
- BIP44_COIN_TYPE = 0
+ BIP44_COIN_TYPE = 140
LN_REALM_BYTE = 0
LN_DNS_SEEDS = [
'nodes.lightning.directory.',
@@ -100,7 +101,7 @@ class BitcoinTestnet(AbstractNet):
WIF_PREFIX = 0xef
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2SH = 196
- SEGWIT_HRP = "tb"
+ SEGWIT_HRP = "tlbc"
GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
@@ -132,7 +133,7 @@ class BitcoinTestnet(AbstractNet):
class BitcoinRegtest(BitcoinTestnet):
- SEGWIT_HRP = "bcrt"
+ SEGWIT_HRP = "blbc"
GENESIS = "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
DEFAULT_SERVERS = read_json('servers_regtest.json', {})
CHECKPOINTS = []
@@ -144,7 +145,7 @@ class BitcoinSimnet(BitcoinTestnet):
WIF_PREFIX = 0x64
ADDRTYPE_P2PKH = 0x3f
ADDRTYPE_P2SH = 0x7b
- SEGWIT_HRP = "sb"
+ SEGWIT_HRP = "slbc"
GENESIS = "683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6"
DEFAULT_SERVERS = read_json('servers_regtest.json', {})
CHECKPOINTS = []
diff --git a/electrum/contacts.py b/electrum/contacts.py
index a4eaadbed..705af3dbd 100644
--- a/electrum/contacts.py
+++ b/electrum/contacts.py
@@ -93,7 +93,7 @@ class Contacts(dict, Logger):
'type': 'openalias',
'validated': validated
}
- raise Exception("Invalid Bitcoin address or alias", k)
+ raise Exception("Invalid LBRY Credits address or alias", k)
def resolve_openalias(self, url):
# support email-style addresses, per the OA standard
@@ -103,7 +103,7 @@ class Contacts(dict, Logger):
except DNSException as e:
self.logger.info(f'Error resolving openalias: {repr(e)}')
return None
- prefix = 'btc'
+ prefix = 'lbc'
for record in records:
string = to_string(record.strings[0], 'utf8')
if string.startswith('oa1:' + prefix):
@@ -121,7 +121,7 @@ class Contacts(dict, Logger):
return regex.search(haystack).groups()[0]
except AttributeError:
return None
-
+
def _validate(self, data):
for k, v in list(data.items()):
if k == 'contacts':
@@ -133,4 +133,3 @@ class Contacts(dict, Logger):
if _type != 'address':
data.pop(k)
return data
-
diff --git a/electrum/currencies.json b/electrum/currencies.json
index ee04e65b7..854b19c8c 100644
--- a/electrum/currencies.json
+++ b/electrum/currencies.json
@@ -28,6 +28,7 @@
"BTC",
"BTN",
"BWP",
+ "BYN",
"BZD",
"CAD",
"CDF",
@@ -46,6 +47,7 @@
"DZD",
"EGP",
"ETB",
+ "ETH",
"EUR",
"FJD",
"FKP",
@@ -160,6 +162,7 @@
"XCD",
"XOF",
"XPF",
+ "XRP",
"YER",
"ZAR",
"ZMW",
@@ -172,175 +175,6 @@
"Bitbank": [
"JPY"
],
- "BitcoinAverage": [
- "AED",
- "AFN",
- "ALL",
- "AMD",
- "ANG",
- "AOA",
- "ARS",
- "AUD",
- "AWG",
- "AZN",
- "BAM",
- "BBD",
- "BDT",
- "BGN",
- "BHD",
- "BIF",
- "BMD",
- "BND",
- "BOB",
- "BRL",
- "BSD",
- "BTN",
- "BWP",
- "BYN",
- "BZD",
- "CAD",
- "CDF",
- "CHF",
- "CLF",
- "CLP",
- "CNH",
- "CNY",
- "COP",
- "CRC",
- "CUC",
- "CUP",
- "CVE",
- "CZK",
- "DJF",
- "DKK",
- "DOP",
- "DZD",
- "EGP",
- "ERN",
- "ETB",
- "EUR",
- "FJD",
- "FKP",
- "GBP",
- "GEL",
- "GGP",
- "GHS",
- "GIP",
- "GMD",
- "GNF",
- "GTQ",
- "GYD",
- "HKD",
- "HNL",
- "HRK",
- "HTG",
- "HUF",
- "IDR",
- "ILS",
- "IMP",
- "INR",
- "IQD",
- "IRR",
- "ISK",
- "JEP",
- "JMD",
- "JOD",
- "JPY",
- "KES",
- "KGS",
- "KHR",
- "KMF",
- "KPW",
- "KRW",
- "KWD",
- "KYD",
- "KZT",
- "LAK",
- "LBP",
- "LKR",
- "LRD",
- "LSL",
- "LYD",
- "MAD",
- "MDL",
- "MGA",
- "MKD",
- "MMK",
- "MNT",
- "MOP",
- "MRO",
- "MUR",
- "MVR",
- "MWK",
- "MXN",
- "MYR",
- "MZN",
- "NAD",
- "NGN",
- "NIO",
- "NOK",
- "NPR",
- "NZD",
- "OMR",
- "PAB",
- "PEN",
- "PGK",
- "PHP",
- "PKR",
- "PLN",
- "PYG",
- "QAR",
- "RON",
- "RSD",
- "RUB",
- "RWF",
- "SAR",
- "SBD",
- "SCR",
- "SDG",
- "SEK",
- "SGD",
- "SHP",
- "SLL",
- "SOS",
- "SRD",
- "SSP",
- "STD",
- "SVC",
- "SYP",
- "SZL",
- "THB",
- "TJS",
- "TMT",
- "TND",
- "TOP",
- "TRY",
- "TTD",
- "TWD",
- "TZS",
- "UAH",
- "UGX",
- "USD",
- "UYU",
- "UZS",
- "VES",
- "VND",
- "VUV",
- "WST",
- "XAF",
- "XAG",
- "XAU",
- "XCD",
- "XDR",
- "XOF",
- "XPD",
- "XPF",
- "XPT",
- "YER",
- "ZAR",
- "ZMW",
- "ZWL"
- ],
"BitcoinVenezuela": [
"ARS",
"ETH",
@@ -350,9 +184,6 @@
"VEF",
"XMR"
],
- "Bitcointoyou": [
- "BRL"
- ],
"Bitso": [
"MXN"
],
@@ -604,6 +435,7 @@
"THB",
"TRY",
"TWD",
+ "UAH",
"USD",
"VEF",
"VND",
@@ -657,12 +489,14 @@
"CUC",
"CVE",
"CZK",
+ "DAI",
"DJF",
"DKK",
"DOP",
"DZD",
"EEK",
"EGP",
+ "EOS",
"ERN",
"ETB",
"ETC",
@@ -733,6 +567,7 @@
"NPR",
"NZD",
"OMR",
+ "OXT",
"PAB",
"PEN",
"PGK",
@@ -741,10 +576,12 @@
"PLN",
"PYG",
"QAR",
+ "REP",
"RON",
"RSD",
"RUB",
"RWF",
+ "SAI",
"SAR",
"SBD",
"SCR",
@@ -773,6 +610,7 @@
"UYU",
"UZS",
"VEF",
+ "VES",
"VND",
"VUV",
"WST",
@@ -781,10 +619,13 @@
"XAU",
"XCD",
"XDR",
+ "XLM",
"XOF",
"XPD",
"XPF",
"XPT",
+ "XRP",
+ "XTZ",
"YER",
"ZAR",
"ZEC",
@@ -802,14 +643,14 @@
],
"LocalBitcoins": [
"AED",
+ "AMD",
+ "AOA",
"ARS",
"AUD",
- "BAM",
"BDT",
"BGN",
"BOB",
"BRL",
- "BWP",
"BYN",
"CAD",
"CHF",
@@ -828,7 +669,6 @@
"GHS",
"GTQ",
"HKD",
- "HNL",
"HRK",
"HUF",
"IDR",
@@ -836,25 +676,26 @@
"INR",
"IRR",
"JOD",
- "JPY",
"KES",
"KRW",
+ "KWD",
"KZT",
- "LKR",
"LTC",
"MAD",
+ "MDL",
+ "MUR",
"MXN",
"MYR",
"NGN",
+ "NIO",
"NOK",
"NZD",
- "OMR",
"PAB",
"PEN",
"PHP",
"PKR",
"PLN",
- "QAR",
+ "PYG",
"RON",
"RSD",
"RUB",
@@ -865,7 +706,6 @@
"SZL",
"THB",
"TRY",
- "TTD",
"TWD",
"TZS",
"UAH",
@@ -875,21 +715,15 @@
"VES",
"VND",
"XAF",
- "XMR",
+ "XOF",
"XRP",
- "ZAR"
- ],
- "MercadoBitcoin": [
- "BRL"
- ],
- "NegocieCoins": [
- "BRL"
+ "ZAR",
+ "ZMW"
],
"TheRockTrading": [
"EUR"
],
"Zaif": [
"JPY"
- ],
- "itBit": []
-}
+ ]
+}
\ No newline at end of file
diff --git a/electrum/ecc.py b/electrum/ecc.py
index 4dabe5629..28e66db97 100644
--- a/electrum/ecc.py
+++ b/electrum/ecc.py
@@ -352,7 +352,7 @@ POINT_AT_INFINITY = ECPubkey(None)
def msg_magic(message: bytes) -> bytes:
from .bitcoin import var_int
length = bfh(var_int(len(message)))
- return b"\x18Bitcoin Signed Message:\n" + length + message
+ return b"\x18LBRYcrd Signed Message:\n" + length + message
def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool:
diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
index b7b3b57af..90a962ffd 100644
--- a/electrum/gui/kivy/main_window.py
+++ b/electrum/gui/kivy/main_window.py
@@ -184,7 +184,7 @@ class ElectrumWindow(App):
def on_new_intent(self, intent):
data = intent.getDataString()
- if intent.getScheme() == 'bitcoin':
+ if intent.getScheme() == 'lbrycredits':
self.set_URI(data)
elif intent.getScheme() == 'lightning':
self.set_ln_invoice(data)
diff --git a/electrum/gui/kivy/uix/context_menu.py b/electrum/gui/kivy/uix/context_menu.py
new file mode 100644
index 000000000..84d5ba647
--- /dev/null
+++ b/electrum/gui/kivy/uix/context_menu.py
@@ -0,0 +1,56 @@
+#!python
+#!/usr/bin/env python
+from kivy.app import App
+from kivy.uix.bubble import Bubble
+from kivy.animation import Animation
+from kivy.uix.floatlayout import FloatLayout
+from kivy.lang import Builder
+from kivy.factory import Factory
+from kivy.clock import Clock
+
+from electrum.gui.kivy.i18n import _
+
+Builder.load_string('''
+