electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 28dc1928a0cd3a2bfa8fa1f15fc5a6fcdf5112b3
parent a600873cf9ae9395eae1b71a81634b089205b886
Author: ThomasV <thomasv@electrum.org>
Date:   Tue, 11 Feb 2020 19:05:12 +0100

Merge pull request #5947 from SomberNight/202002_ecdsa

make libsecp256k1 a mandatory dependency
Diffstat:
MMANIFEST.in | 3+++
MREADME.rst | 34++++++++++++++++++++++++----------
Mcontrib/build-linux/appimage/build.sh | 27++++-----------------------
Mcontrib/build-wine/build-electrum-git.sh | 1-
Dcontrib/build-wine/build-secp256k1.sh | 56--------------------------------------------------------
Mcontrib/build-wine/build.sh | 15+++++++++++----
Mcontrib/build-wine/deterministic.spec | 2+-
Mcontrib/build-wine/prepare-wine.sh | 5++---
Mcontrib/build_tools_util.sh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/make_libsecp256k1.sh | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mcontrib/osx/base.sh | 4----
Mcontrib/osx/make_osx | 16+++++-----------
Melectrum/bip32.py | 2+-
Melectrum/daemon.py | 9++++-----
Melectrum/dnssec.py | 14+++++++-------
Melectrum/ecc.py | 440+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Melectrum/ecc_fast.py | 233++++++++++++++++++-------------------------------------------------------------
Melectrum/keystore.py | 6+++---
Melectrum/lnpeer.py | 6+++---
Melectrum/lnutil.py | 4++--
Melectrum/lnworker.py | 2--
Melectrum/mnemonic.py | 6++----
Delectrum/msqr.py | 94-------------------------------------------------------------------------------
Melectrum/tests/test_bitcoin.py | 59++++++-----------------------------------------------------
Melectrum/tests/test_dnssec.py | 2--
Melectrum/tests/test_transaction.py | 3---
Melectrum/tests/test_wallet_vertical.py | 40----------------------------------------
Melectrum/util.py | 7+++++++
Melectrum/wallet.py | 7-------
Melectrum/x509.py | 6++----
30 files changed, 479 insertions(+), 734 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in @@ -12,6 +12,9 @@ graft electrum prune electrum/tests graft contrib/udev +exclude electrum/*.so +exclude electrum/*.so.0 + global-exclude __pycache__ global-exclude *.py[co~] global-exclude *.py.orig diff --git a/README.rst b/README.rst @@ -26,11 +26,28 @@ Electrum - Lightweight Bitcoin client Getting started =============== -Electrum is a pure python application. If you want to use the -Qt interface, install the Qt dependencies:: +Electrum itself is pure Python, and so are most of the required dependencies. + +Non-python dependencies +----------------------- + +If you want to use the Qt interface, install the Qt dependencies:: sudo apt-get install python3-pyqt5 +For elliptic curve operations, libsecp256k1 is a required dependency:: + + sudo apt-get install libsecp256k1-0 + +Alternatively, when running from a cloned repository, a script is provided to build +libsecp256k1 yourself:: + + ./contrib/make_libsecp256k1.sh + + +Running from tar.gz +------------------- + If you downloaded the official package (tar.gz), you can run Electrum from its root directory without installing it on your system; all the python dependencies are included in the 'packages' @@ -40,22 +57,19 @@ directory. To run Electrum from its root directory, just do:: You can also install Electrum on your system, by running this command:: - sudo apt-get install python3-setuptools - python3 -m pip install .[fast] + sudo apt-get install python3-setuptools python3-pip + python3 -m pip install --user . This will download and install the Python dependencies used by Electrum instead of using the 'packages' directory. -The 'fast' extra contains some optional dependencies that we think -are often useful but they are not strictly needed. If you cloned the git repository, you need to compile extra files before you can run Electrum. Read the next section, "Development -Version". - +version". Development version -=================== +------------------- Check out the code from GitHub:: @@ -65,7 +79,7 @@ Check out the code from GitHub:: Run install (this should install dependencies):: - python3 -m pip install .[fast] + python3 -m pip install --user . Compile the protobuf description file:: diff --git a/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh @@ -10,10 +10,11 @@ BUILDDIR="$CONTRIB_APPIMAGE/build/appimage" APPDIR="$BUILDDIR/electrum.AppDir" CACHEDIR="$CONTRIB_APPIMAGE/.cache/appimage" +export GCC_STRIP_BINARIES="1" + # pinned versions PYTHON_VERSION=3.7.6 PKG2APPIMAGE_COMMIT="eb8f3acdd9f11ab19b78f5cb15daa772367daf15" -LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" SQUASHFSKIT_COMMIT="ae0d656efa2d0df2fcac795b6823b44462f19386" @@ -45,7 +46,6 @@ info "building python." tar xf "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" -C "$BUILDDIR" ( cd "$BUILDDIR/Python-$PYTHON_VERSION" - export SOURCE_DATE_EPOCH=1530212462 LC_ALL=C export BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" "+%b %d %Y") LC_ALL=C export BUILD_TIME=$(date -u -d "@$SOURCE_DATE_EPOCH" "+%H:%M:%S") # Patch taken from Ubuntu http://archive.ubuntu.com/ubuntu/pool/main/p/python3.7/python3.7_3.7.6-1.debian.tar.xz @@ -77,26 +77,8 @@ git clone "https://github.com/squashfskit/squashfskit.git" "$BUILDDIR/squashfski MKSQUASHFS="$BUILDDIR/squashfskit/squashfs-tools/mksquashfs" -info "building libsecp256k1." -( - git clone https://github.com/bitcoin-core/secp256k1 "$CACHEDIR"/secp256k1 \ - || (cd "$CACHEDIR"/secp256k1 && git reset --hard && git pull) - cd "$CACHEDIR"/secp256k1 - git reset --hard "$LIBSECP_VERSION" - git clean -f -x -q - export SOURCE_DATE_EPOCH=1530212462 - echo "LDFLAGS = -no-undefined" >> Makefile.am - ./autogen.sh - ./configure \ - --prefix="$APPDIR/usr" \ - --enable-module-recovery \ - --enable-experimental \ - --enable-module-ecdh \ - --disable-jni \ - -q - make -j4 -s || fail "Could not build libsecp" - make -s install > /dev/null || fail "Could not install libsecp" -) +"$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp" +cp -f "$PROJECT_ROOT/electrum/libsecp256k1.so.0" "$APPDIR/usr/lib/libsecp256k1.so.0" || fail "Could not copy libsecp to its destination" appdir_python() { @@ -224,7 +206,6 @@ rm -rf "$PYDIR"/site-packages/PyQt5/Qt.so # these are deleted as they were not deterministic; and are not needed anyway find "$APPDIR" -path '*/__pycache__*' -delete -rm "$APPDIR"/usr/lib/libsecp256k1.a # note that jsonschema-*.dist-info is needed by that package as it uses 'pkg_resources.get_distribution' # also, see https://gitlab.com/python-devs/importlib_metadata/issues/71 for f in "$PYDIR"/site-packages/jsonschema-*.dist-info; do mv "$f" "$(echo "$f" | sed s/\.dist-info/\.dist-info2/)"; done diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh @@ -6,7 +6,6 @@ NAME_ROOT=electrum export WINEPREFIX=/opt/wine64 export WINEDEBUG=-all export PYTHONDONTWRITEBYTECODE=1 -export PYTHONHASHSEED=22 PYHOME=c:/python3 PYTHON="wine $PYHOME/python.exe -OO -B" diff --git a/contrib/build-wine/build-secp256k1.sh b/contrib/build-wine/build-secp256k1.sh @@ -1,56 +0,0 @@ -#!/bin/bash -# heavily based on https://github.com/ofek/coincurve/blob/417e726f553460f88d7edfa5dc67bfda397c4e4a/.travis/build_windows_wheels.sh - -set -e - -here="$(dirname "$(readlink -e "$0")")" -LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" - -. "$CONTRIB"/build_tools_util.sh - -info "building libsecp256k1..." - - -build_dll() { - #sudo apt-get install -y mingw-w64 - export SOURCE_DATE_EPOCH=1530212462 - echo "LDFLAGS = -no-undefined" >> Makefile.am - ./autogen.sh - # Note: set both --build and --host when running configure - # Otherwise weird voodoo magic happens with Docker and Wine. - # https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Hosts-and-Cross_002dCompilation.html - LDFLAGS="-Wl,--no-insert-timestamp" ./configure \ - --host=$1 \ - --build=x86_64-pc-linux-gnu \ - --enable-module-recovery \ - --enable-experimental \ - --enable-module-ecdh \ - --disable-jni - make -j4 - ${1}-strip .libs/libsecp256k1-0.dll -} - - -cd "$CACHEDIR" - -if [ -f "secp256k1/libsecp256k1.dll" ]; then - info "libsecp256k1.dll already built, skipping" - exit 0 -fi - - -if [ ! -d secp256k1 ]; then - git clone https://github.com/bitcoin-core/secp256k1.git -fi - -cd secp256k1 -git reset --hard -git clean -f -x -q -git checkout $LIBSECP_VERSION - -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' {} + - -info "building libsecp256k1 finished" diff --git a/contrib/build-wine/build.sh b/contrib/build-wine/build.sh @@ -2,16 +2,19 @@ set -e -# Lucky number -export PYTHONHASHSEED=22 - here="$(dirname "$(readlink -e "$0")")" test -n "$here" -a -d "$here" || exit export CONTRIB="$here/.." +export PROJECT_ROOT="$CONTRIB/.." export CACHEDIR="$here/.cache" export PIP_CACHE_DIR="$CACHEDIR/pip_cache" +export BUILD_TYPE="wine" +export GCC_TRIPLET_HOST="i686-w64-mingw32" +export GCC_TRIPLET_BUILD="x86_64-pc-linux-gnu" +export GCC_STRIP_BINARIES="1" + . "$CONTRIB"/build_tools_util.sh info "Clearing $here/build and $here/dist..." @@ -20,7 +23,11 @@ rm "$here"/dist/* -rf mkdir -p "$CACHEDIR" "$PIP_CACHE_DIR" -$here/build-secp256k1.sh || fail "build-secp256k1 failed" +if [ -f "$PROJECT_ROOT/electrum/libsecp256k1-0.dll" ]; then + info "libsecp256k1 already built, skipping" +else + "$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp" +fi $here/prepare-wine.sh || fail "prepare-wine failed" diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec @@ -34,7 +34,7 @@ binaries = [] # Workaround for "Retro Look": binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]] -binaries += [('C:/tmp/libsecp256k1.dll', '.')] +binaries += [('C:/tmp/libsecp256k1-0.dll', '.')] binaries += [('C:/tmp/libusb-1.0.dll', '.')] datas = [ diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh @@ -89,7 +89,6 @@ info "Compiling libusb..." git remote add origin $LIBUSB_REPO git fetch --depth 1 origin $LIBUSB_COMMIT git checkout -b pinned FETCH_HEAD - export SOURCE_DATE_EPOCH=1530212462 echo "libusb_1_0_la_LDFLAGS += -Wc,-static" >> libusb/Makefile.am ./bootstrap.sh || fail "Could not bootstrap libusb" host="i686-w64-mingw32" @@ -102,8 +101,8 @@ info "Compiling libusb..." cp "$CACHEDIR/libusb/libusb/.libs/libusb-1.0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libusb to its destination" -# copy libsecp dll (already built by build-secp256k1.sh) -cp "$CACHEDIR/secp256k1/libsecp256k1.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libsecp to its destination" +# copy libsecp dll (already built) +cp "$PROJECT_ROOT/electrum/libsecp256k1-0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libsecp to its destination" info "Building PyInstaller." diff --git a/contrib/build_tools_util.sh b/contrib/build_tools_util.sh @@ -70,3 +70,64 @@ function retry() { return $result } + +function gcc_with_triplet() +{ + TRIPLET="$1" + CMD="$2" + shift 2 + if [ -n "$TRIPLET" ] ; then + "$TRIPLET-$CMD" "$@" + else + "$CMD" "$@" + fi +} + +function gcc_host() +{ + gcc_with_triplet "$GCC_TRIPLET_HOST" "$@" +} + +function gcc_build() +{ + gcc_with_triplet "$GCC_TRIPLET_BUILD" "$@" +} + +function host_strip() +{ + if [ "$GCC_STRIP_BINARIES" -ne "0" ] ; then + case "$BUILD_TYPE" in + linux|wine) + gcc_host strip "$@" + ;; + darwin) + # TODO: Strip on macOS? + ;; + esac + fi +} + +# on MacOS, there is no realpath by default +if ! [ -x "$(command -v realpath)" ]; then + function realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" + } +fi + + +export SOURCE_DATE_EPOCH=1530212462 +export PYTHONHASHSEED=22 +# Set the build type, overridden by wine build +export BUILD_TYPE="${BUILD_TYPE:-$(uname | tr '[:upper:]' '[:lower:]')}" +# No additional autoconf flags by default +export AUTOCONF_FLAGS="" +# Add host / build flags if the triplets are set +if [ -n "$GCC_TRIPLET_HOST" ] ; then + export AUTOCONF_FLAGS="$AUTOCONF_FLAGS --host=$GCC_TRIPLET_HOST" +fi +if [ -n "$GCC_TRIPLET_BUILD" ] ; then + export AUTOCONF_FLAGS="$AUTOCONF_FLAGS --build=$GCC_TRIPLET_BUILD" +fi + +export GCC_STRIP_BINARIES="${GCC_STRIP_BINARIES:-0}" + diff --git a/contrib/make_libsecp256k1.sh b/contrib/make_libsecp256k1.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" + +set -e + +. $(dirname "$0")/build_tools_util.sh || (echo "Could not source build_tools_util.sh" && exit 1) + +here=$(dirname $(realpath "$0" 2> /dev/null || grealpath "$0")) +CONTRIB="$here" +PROJECT_ROOT="$CONTRIB/.." + +pkgname="secp256k1" +info "Building $pkgname..." + +( + cd $CONTRIB + if [ ! -d secp256k1 ]; then + git clone https://github.com/bitcoin-core/secp256k1.git + fi + cd secp256k1 + git reset --hard + git clean -f -x -q + git checkout $LIBSECP_VERSION + + if ! [ -x configure ] ; then + echo "libsecp256k1_la_LDFLAGS = -no-undefined" >> Makefile.am + echo "LDFLAGS = -no-undefined" >> Makefile.am + ./autogen.sh || fail "Could not run autogen for $pkgname. Please make sure you have automake and libtool installed, and try again." + fi + if ! [ -r config.status ] ; then + ./configure \ + $AUTOCONF_FLAGS \ + --prefix="$here/$pkgname/dist" \ + --enable-module-recovery \ + --enable-experimental \ + --enable-module-ecdh \ + --disable-jni \ + --disable-tests \ + --disable-static \ + --enable-shared || fail "Could not configure $pkgname. Please make sure you have a C compiler installed and try again." + fi + make -j4 || fail "Could not build $pkgname" + make install || fail "Could not install $pkgname" + . "$here/$pkgname/dist/lib/libsecp256k1.la" + host_strip "$here/$pkgname/dist/lib/$dlname" + cp -fpv "$here/$pkgname/dist/lib/$dlname" "$PROJECT_ROOT/electrum" || fail "Could not copy the $pkgname binary to its destination" + info "$dlname has been placed in the inner 'electrum' folder." +) diff --git a/contrib/osx/base.sh b/contrib/osx/base.sh @@ -21,7 +21,3 @@ function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity info "Code signing ${infoName}..." codesign -f -v $deep -s "$identity" "$file" || fail "Could not code sign ${infoName}" } - -function realpath() { - [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" -} diff --git a/contrib/osx/make_osx b/contrib/osx/make_osx @@ -7,6 +7,8 @@ PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" +export GCC_STRIP_BINARIES="1" + . $(dirname "$0")/base.sh CONTRIB_OSX="$(dirname "$(realpath "$0")")" @@ -16,7 +18,6 @@ ROOT_FOLDER="$CONTRIB/.." 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" @@ -96,17 +97,10 @@ cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/osx echo "82c368dfd4da017ceb32b12ca885576f325503428a4966cc09302cbd62702493 contrib/osx/libusb-1.0.dylib" | \ shasum -a 256 -c || fail "libusb checksum mismatched" -info "Building libsecp256k1" +info "Preparing for 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 -j4 -popd -cp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/osx +"$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp" +cp "$ROOT_FOLDER"/electrum/libsecp256k1.0.dylib contrib/osx info "Building CalinsQRReader..." d=contrib/osx/CalinsQRReader diff --git a/electrum/bip32.py b/electrum/bip32.py @@ -65,7 +65,7 @@ def _CKD_priv(parent_privkey: bytes, parent_chaincode: bytes, child_privkey = (I_left + ecc.string_to_number(parent_privkey)) % ecc.CURVE_ORDER if I_left >= ecc.CURVE_ORDER or child_privkey == 0: raise ecc.InvalidECPointException() - child_privkey = ecc.number_to_string(child_privkey, ecc.CURVE_ORDER) + child_privkey = int.to_bytes(child_privkey, length=32, byteorder='big', signed=False) child_chaincode = I[32:] return child_privkey, child_chaincode diff --git a/electrum/daemon.py b/electrum/daemon.py @@ -30,7 +30,7 @@ import traceback import sys import threading from typing import Dict, Optional, Tuple, Iterable -from base64 import b64decode +from base64 import b64decode, b64encode from collections import defaultdict import aiohttp @@ -44,7 +44,7 @@ from aiorpcx import TaskGroup from .network import Network from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare) from .util import PR_PAID, PR_EXPIRED, get_request_status -from .util import log_exceptions, ignore_exceptions +from .util import log_exceptions, ignore_exceptions, randrange from .wallet import Wallet, Abstract_Wallet from .storage import WalletStorage from .wallet_db import WalletDB @@ -124,11 +124,10 @@ def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]: rpc_password = config.get('rpcpassword', None) if rpc_user is None or rpc_password is None: rpc_user = 'user' - import ecdsa, base64 bits = 128 nbytes = bits // 8 + (bits % 8 > 0) - pw_int = ecdsa.util.randrange(pow(2, bits)) - pw_b64 = base64.b64encode( + pw_int = randrange(pow(2, bits)) + pw_b64 = b64encode( pw_int.to_bytes(nbytes, 'big'), b'-_') rpc_password = to_string(pw_b64, 'ascii') config.set_key('rpcuser', rpc_user) diff --git a/electrum/dnssec.py b/electrum/dnssec.py @@ -101,8 +101,8 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): keyptr = keyptr[2:] rsa_e = keyptr[0:bytes] rsa_n = keyptr[bytes:] - n = ecdsa.util.string_to_number(rsa_n) - e = ecdsa.util.string_to_number(rsa_e) + n = int.from_bytes(rsa_n, byteorder='big', signed=False) + e = int.from_bytes(rsa_e, byteorder='big', signed=False) pubkey = rsakey.RSAKey(n, e) sig = rrsig.signature @@ -117,15 +117,15 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): # shouldn't happen raise ValidationFailure('unknown ECDSA curve') keyptr = candidate_key.key - x = ecdsa.util.string_to_number(keyptr[0:key_len]) - y = ecdsa.util.string_to_number(keyptr[key_len:key_len * 2]) + x = int.from_bytes(keyptr[0:key_len], byteorder='big', signed=False) + y = int.from_bytes(keyptr[key_len:key_len * 2], byteorder='big', signed=False) assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y) point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order) verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, curve) r = rrsig.signature[:key_len] s = rrsig.signature[key_len:] - sig = ecdsa.ecdsa.Signature(ecdsa.util.string_to_number(r), - ecdsa.util.string_to_number(s)) + sig = ecdsa.ecdsa.Signature(int.from_bytes(r, byteorder='big', signed=False), + int.from_bytes(s, byteorder='big', signed=False)) else: raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) @@ -156,7 +156,7 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): return elif _is_ecdsa(rrsig.algorithm): - diglong = ecdsa.util.string_to_number(digest) + diglong = int.from_bytes(digest, byteorder='big', signed=False) if verifying_key.pubkey.verifies(diglong, sig): return diff --git a/electrum/ecc.py b/electrum/ecc.py @@ -26,189 +26,148 @@ import base64 import hashlib import functools -import copy from typing import Union, Tuple, Optional +from ctypes import ( + byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, + CFUNCTYPE, POINTER, cast +) -import ecdsa -from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 -from ecdsa.curves import SECP256k1 -from ecdsa.ellipticcurve import Point -from ecdsa.util import string_to_number, number_to_string - -from .util import bfh, bh2u, assert_bytes, to_bytes, InvalidPassword, profiler +from .util import bfh, bh2u, assert_bytes, to_bytes, InvalidPassword, profiler, randrange from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) -from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1 -from . import msqr from . import constants from .logging import get_logger - +from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED _logger = get_logger(__name__) -do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() - -CURVE_ORDER = SECP256k1.order - - -def generator(): - return ECPubkey.from_point(generator_secp256k1) - - -def point_at_infinity(): - return ECPubkey(None) - - -def sig_string_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> bytes: - r, s = ecdsa.util.sigdecode_der(der_sig, order) - return ecdsa.util.sigencode_string(r, s, order) - - -def der_sig_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> bytes: - r, s = ecdsa.util.sigdecode_string(sig_string, order) - return ecdsa.util.sigencode_der_canonize(r, s, order) - - -def der_sig_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes: - return ecdsa.util.sigencode_der_canonize(r, s, order) -def get_r_and_s_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> Tuple[int, int]: - r, s = ecdsa.util.sigdecode_der(der_sig, order) +def string_to_number(b: bytes) -> int: + return int.from_bytes(b, byteorder='big', signed=False) + + +def sig_string_from_der_sig(der_sig: bytes) -> bytes: + r, s = get_r_and_s_from_der_sig(der_sig) + return sig_string_from_r_and_s(r, s) + + +def der_sig_from_sig_string(sig_string: bytes) -> bytes: + r, s = get_r_and_s_from_sig_string(sig_string) + return der_sig_from_r_and_s(r, s) + + +def der_sig_from_r_and_s(r: int, s: int) -> bytes: + sig_string = (int.to_bytes(r, length=32, byteorder="big") + + int.to_bytes(s, length=32, byteorder="big")) + sig = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + if not ret: + raise Exception("Bad signature") + ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) + der_sig = create_string_buffer(80) # this much space should be enough + der_sig_size = c_size_t(len(der_sig)) + ret = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der(_libsecp256k1.ctx, der_sig, byref(der_sig_size), sig) + if not ret: + raise Exception("failed to serialize DER sig") + der_sig_size = der_sig_size.value + return bytes(der_sig)[:der_sig_size] + + +def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]: + assert isinstance(der_sig, bytes) + sig = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig)) + if not ret: + raise Exception("Bad signature") + ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) + compact_signature = create_string_buffer(64) + _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) + r = int.from_bytes(compact_signature[:32], byteorder="big") + s = int.from_bytes(compact_signature[32:], byteorder="big") return r, s -def get_r_and_s_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> Tuple[int, int]: - r, s = ecdsa.util.sigdecode_string(sig_string, order) +def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]: + if not (isinstance(sig_string, bytes) and len(sig_string) == 64): + raise Exception("sig_string must be bytes, and 64 bytes exactly") + sig = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + if not ret: + raise Exception("Bad signature") + ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) + compact_signature = create_string_buffer(64) + _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) + r = int.from_bytes(compact_signature[:32], byteorder="big") + s = int.from_bytes(compact_signature[32:], byteorder="big") return r, s -def sig_string_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes: - return ecdsa.util.sigencode_string_canonize(r, s, order) - - -def point_to_ser(point, compressed=True) -> Optional[bytes]: - if isinstance(point, tuple): - assert len(point) == 2, f'unexpected point: {point}' - x, y = point - else: - x, y = point.x(), point.y() - if x is None or y is None: # infinity - return None - if compressed: - return bfh(('%02x' % (2+(y&1))) + ('%064x' % x)) - return bfh('04'+('%064x' % x)+('%064x' % y)) - - -def get_y_coord_from_x(x: int, *, odd: bool) -> int: - curve = curve_secp256k1 - _p = curve.p() - _a = curve.a() - _b = curve.b() - x = x % _p - y2 = (pow(x, 3, _p) + _a * x + _b) % _p - y = msqr.modular_sqrt(y2, _p) - if curve.contains_point(x, y): - if odd == bool(y & 1): - return y - return _p - y - raise InvalidECPointException() - - -def ser_to_point(ser: bytes) -> Tuple[int, int]: - if ser[0] not in (0x02, 0x03, 0x04): - raise ValueError('Unexpected first byte: {}'.format(ser[0])) - if ser[0] == 0x04: - return string_to_number(ser[1:33]), string_to_number(ser[33:]) - x = string_to_number(ser[1:]) - odd = ser[0] == 0x03 - return x, get_y_coord_from_x(x, odd=odd) - - -def _ser_to_python_ecdsa_point(ser: bytes) -> ecdsa.ellipticcurve.Point: - x, y = ser_to_point(ser) - try: - return Point(curve_secp256k1, x, y, CURVE_ORDER) - except: - raise InvalidECPointException() +def sig_string_from_r_and_s(r: int, s: int) -> bytes: + sig_string = (int.to_bytes(r, length=32, byteorder="big") + + int.to_bytes(s, length=32, byteorder="big")) + sig = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + if not ret: + raise Exception("Bad signature") + ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) + compact_signature = create_string_buffer(64) + _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) + return bytes(compact_signature) + + +def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]: + pubkey_ptr = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ec_pubkey_parse( + _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) + if not ret: + raise InvalidECPointException('public key could not be parsed or is invalid') + + pubkey_serialized = create_string_buffer(65) + pubkey_size = c_size_t(65) + _libsecp256k1.secp256k1_ec_pubkey_serialize( + _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED) + pubkey_serialized = bytes(pubkey_serialized) + assert pubkey_serialized[0] == 0x04, pubkey_serialized + x = int.from_bytes(pubkey_serialized[1:33], byteorder='big', signed=False) + y = int.from_bytes(pubkey_serialized[33:65], byteorder='big', signed=False) + return x, y class InvalidECPointException(Exception): """e.g. not on curve, or infinity""" -class _MyVerifyingKey(ecdsa.VerifyingKey): - @classmethod - def from_signature(klass, sig, recid, h, curve): # TODO use libsecp?? - """ See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """ - from ecdsa import util, numbertheory - from . import msqr - curveFp = curve.curve - G = curve.generator - order = G.order() - # extract r,s from signature - r, s = util.sigdecode_string(sig, order) - # 1.1 - x = r + (recid//2) * order - # 1.3 - alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p() - beta = msqr.modular_sqrt(alpha, curveFp.p()) - y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta - # 1.4 the constructor checks that nR is at infinity - try: - R = Point(curveFp, x, y, order) - except: - raise InvalidECPointException() - # 1.5 compute e from message: - e = string_to_number(h) - minus_e = -e % order - # 1.6 compute Q = r^-1 (sR - eG) - inv_r = numbertheory.inverse_mod(r,order) - try: - Q = inv_r * ( s * R + minus_e * G ) - except: - raise InvalidECPointException() - return klass.from_public_point( Q, curve ) - - -class _MySigningKey(ecdsa.SigningKey): - """Enforce low S values in signatures""" - - def sign_number(self, number, entropy=None, k=None): - r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k) - if s > CURVE_ORDER//2: - s = CURVE_ORDER - s - return r, s - - -class _PubkeyForPointAtInfinity: - point = ecdsa.ellipticcurve.INFINITY - - @functools.total_ordering class ECPubkey(object): def __init__(self, b: Optional[bytes]): if b is not None: assert_bytes(b) - point = _ser_to_python_ecdsa_point(b) - self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point) + self._x, self._y = _x_and_y_from_pubkey_bytes(b) else: - self._pubkey = _PubkeyForPointAtInfinity() + self._x, self._y = None, None @classmethod - def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes): + def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes) -> 'ECPubkey': assert_bytes(sig_string) if len(sig_string) != 64: - raise Exception('Wrong encoding') + raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)') if recid < 0 or recid > 3: raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid)) - ecdsa_verifying_key = _MyVerifyingKey.from_signature(sig_string, recid, msg_hash, curve=SECP256k1) - ecdsa_point = ecdsa_verifying_key.pubkey.point - return ECPubkey.from_point(ecdsa_point) + sig65 = create_string_buffer(65) + ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact( + _libsecp256k1.ctx, sig65, sig_string, recid) + if not ret: + raise Exception('failed to parse signature') + pubkey = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg_hash) + if not ret: + raise InvalidECPointException('failed to recover public key') + return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) @classmethod - def from_signature65(cls, sig: bytes, msg_hash: bytes): + def from_signature65(cls, sig: bytes, msg_hash: bytes) -> Tuple['ECPubkey', bool]: if len(sig) != 65: - raise Exception("Wrong encoding") + raise Exception(f'wrong encoding used for signature? len={len(sig)} (should be 65)') nV = sig[0] if nV < 27 or nV >= 35: raise Exception("Bad encoding") @@ -221,28 +180,70 @@ class ECPubkey(object): return cls.from_sig_string(sig[1:], recid, msg_hash), compressed @classmethod - def from_point(cls, point): - _bytes = point_to_ser(point, compressed=False) # faster than compressed + def from_x_and_y(cls, x: int, y: int) -> 'ECPubkey': + _bytes = (b'\x04' + + int.to_bytes(x, length=32, byteorder='big', signed=False) + + int.to_bytes(y, length=32, byteorder='big', signed=False)) return ECPubkey(_bytes) def get_public_key_bytes(self, compressed=True): if self.is_at_infinity(): raise Exception('point is at infinity') - return point_to_ser(self.point(), compressed) + x = int.to_bytes(self.x(), length=32, byteorder='big', signed=False) + y = int.to_bytes(self.y(), length=32, byteorder='big', signed=False) + if compressed: + header = b'\x03' if self.y() & 1 else b'\x02' + return header + x + else: + header = b'\x04' + return header + x + y def get_public_key_hex(self, compressed=True): return bh2u(self.get_public_key_bytes(compressed)) def point(self) -> Tuple[int, int]: - return self._pubkey.point.x(), self._pubkey.point.y() + return self.x(), self.y() + + def x(self) -> int: + return self._x + + def y(self) -> int: + return self._y + + def _to_libsecp256k1_pubkey_ptr(self): + pubkey = create_string_buffer(64) + public_pair_bytes = self.get_public_key_bytes(compressed=False) + ret = _libsecp256k1.secp256k1_ec_pubkey_parse( + _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) + if not ret: + raise Exception('public key could not be parsed or is invalid') + return pubkey + + @classmethod + def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> 'ECPubkey': + pubkey_serialized = create_string_buffer(65) + pubkey_size = c_size_t(65) + _libsecp256k1.secp256k1_ec_pubkey_serialize( + _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED) + return ECPubkey(bytes(pubkey_serialized)) def __repr__(self): + if self.is_at_infinity(): + return f"<ECPubkey infinity>" return f"<ECPubkey {self.get_public_key_hex()}>" def __mul__(self, other: int): if not isinstance(other, int): raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other))) - ecdsa_point = self._pubkey.point * other - return self.from_point(ecdsa_point) + + other %= CURVE_ORDER + if self.is_at_infinity() or other == 0: + return POINT_AT_INFINITY + pubkey = self._to_libsecp256k1_pubkey_ptr() + + ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big")) + if not ret: + return POINT_AT_INFINITY + return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) def __rmul__(self, other: int): return self * other @@ -250,38 +251,36 @@ class ECPubkey(object): def __add__(self, other): if not isinstance(other, ECPubkey): raise TypeError('addition not defined for ECPubkey and {}'.format(type(other))) - ecdsa_point = self._pubkey.point + other._pubkey.point - return self.from_point(ecdsa_point) - - def __eq__(self, other): - return self._pubkey.point.x() == other._pubkey.point.x() \ - and self._pubkey.point.y() == other._pubkey.point.y() + if self.is_at_infinity(): return other + if other.is_at_infinity(): return self + + pubkey1 = self._to_libsecp256k1_pubkey_ptr() + pubkey2 = other._to_libsecp256k1_pubkey_ptr() + pubkey_sum = create_string_buffer(64) + + pubkey1 = cast(pubkey1, c_char_p) + pubkey2 = cast(pubkey2, c_char_p) + array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2) + ret = _libsecp256k1.secp256k1_ec_pubkey_combine(_libsecp256k1.ctx, pubkey_sum, array_of_pubkey_ptrs, 2) + if not ret: + return POINT_AT_INFINITY + return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum) + + def __eq__(self, other) -> bool: + if not isinstance(other, ECPubkey): + return False + return self.point() == other.point() def __ne__(self, other): return not (self == other) def __hash__(self): - return hash(self._pubkey.point.x()) + return hash(self.point()) def __lt__(self, other): if not isinstance(other, ECPubkey): raise TypeError('comparison not defined for ECPubkey and {}'.format(type(other))) - return self._pubkey.point.x() < other._pubkey.point.x() - - def __deepcopy__(self, memo: dict = None): - # note: This custom deepcopy implementation needed as copy.deepcopy(self._pubkey) raises. - if memo is None: memo = {} - cls = self.__class__ - result = cls.__new__(cls) - memo[id(self)] = result - for k, v in self.__dict__.items(): - if k == '_pubkey' and not self.is_at_infinity(): - point = _ser_to_python_ecdsa_point(self.get_public_key_bytes(compressed=False)) - _pubkey_copy = ecdsa.ecdsa.Public_key(generator_secp256k1, point) - setattr(result, k, _pubkey_copy) - else: - setattr(result, k, copy.deepcopy(v, memo)) - return result + return (self.x() or 0) < (other.x() or 0) def verify_message_for_address(self, sig65: bytes, message: bytes, algo=lambda x: sha256d(msg_magic(x))) -> None: assert_bytes(message) @@ -293,13 +292,23 @@ class ECPubkey(object): # check message self.verify_message_hash(sig65[1:], h) + # TODO return bool instead of raising def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> None: assert_bytes(sig_string) if len(sig_string) != 64: - raise Exception('Wrong encoding') - ecdsa_point = self._pubkey.point - verifying_key = _MyVerifyingKey.from_public_point(ecdsa_point, curve=SECP256k1) - verifying_key.verify_digest(sig_string, msg_hash, sigdecode=ecdsa.util.sigdecode_string) + raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)') + if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): + raise Exception("msg_hash must be bytes, and 32 bytes exactly") + + sig = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + if not ret: + raise Exception("Bad signature") + ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) + + pubkey = self._to_libsecp256k1_pubkey_ptr() + if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg_hash, pubkey): + raise Exception("Bad signature") def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes: """ @@ -323,7 +332,7 @@ class ECPubkey(object): return CURVE_ORDER def is_at_infinity(self): - return self == point_at_infinity() + return self == POINT_AT_INFINITY @classmethod def is_pubkey_bytes(cls, b: bytes): @@ -334,6 +343,12 @@ class ECPubkey(object): return False +GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')) +CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 +POINT_AT_INFINITY = ECPubkey(None) + + def msg_magic(message: bytes) -> bytes: from .bitcoin import var_int length = bfh(var_int(len(message))) @@ -387,12 +402,12 @@ class ECPrivkey(ECPubkey): raise InvalidECPointException('Invalid secret scalar (not within curve order)') self.secret_scalar = secret - point = generator_secp256k1 * secret - super().__init__(point_to_ser(point)) + pubkey = GENERATOR * secret + super().__init__(pubkey.get_public_key_bytes(compressed=False)) @classmethod def from_secret_scalar(cls, secret_scalar: int): - secret_bytes = number_to_string(secret_scalar, CURVE_ORDER) + secret_bytes = int.to_bytes(secret_scalar, length=32, byteorder='big', signed=False) return ECPrivkey(secret_bytes) @classmethod @@ -408,7 +423,7 @@ class ECPrivkey(ECPubkey): scalar = string_to_number(privkey_bytes) % CURVE_ORDER if scalar == 0: raise Exception('invalid EC private key scalar: zero') - privkey_32bytes = number_to_string(scalar, CURVE_ORDER) + privkey_32bytes = int.to_bytes(scalar, length=32, byteorder='big', signed=False) return privkey_32bytes def __repr__(self): @@ -416,37 +431,49 @@ class ECPrivkey(ECPubkey): @classmethod def generate_random_key(cls): - randint = ecdsa.util.randrange(CURVE_ORDER) - ephemeral_exponent = number_to_string(randint, CURVE_ORDER) + randint = randrange(CURVE_ORDER) + ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False) return ECPrivkey(ephemeral_exponent) def get_secret_bytes(self) -> bytes: - return number_to_string(self.secret_scalar, CURVE_ORDER) + return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False) - def sign(self, data: bytes, sigencode=None, sigdecode=None) -> bytes: + def sign(self, msg_hash: bytes, sigencode=None) -> bytes: + if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): + raise Exception("msg_hash to be signed must be bytes, and 32 bytes exactly") if sigencode is None: sigencode = sig_string_from_r_and_s - if sigdecode is None: - sigdecode = get_r_and_s_from_sig_string - private_key = _MySigningKey.from_secret_exponent(self.secret_scalar, curve=SECP256k1) - def sig_encode_r_s(r, s, order): + + privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") + nonce_function = None + sig = create_string_buffer(64) + def sign_with_extra_entropy(extra_entropy): + ret = _libsecp256k1.secp256k1_ecdsa_sign( + _libsecp256k1.ctx, sig, msg_hash, privkey_bytes, + nonce_function, extra_entropy) + if not ret: + raise Exception('the nonce generation function failed, or the private key was invalid') + compact_signature = create_string_buffer(64) + _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) + r = int.from_bytes(compact_signature[:32], byteorder="big") + s = int.from_bytes(compact_signature[32:], byteorder="big") return r, s - r, s = private_key.sign_digest_deterministic(data, hashfunc=hashlib.sha256, sigencode=sig_encode_r_s) + + r, s = sign_with_extra_entropy(extra_entropy=None) counter = 0 while r >= 2**255: # grind for low R value https://github.com/bitcoin/bitcoin/pull/13666 counter += 1 - extra_entropy = int.to_bytes(counter, 32, 'little') - r, s = private_key.sign_digest_deterministic(data, hashfunc=hashlib.sha256, sigencode=sig_encode_r_s, extra_entropy=extra_entropy) - sig = sigencode(r, s, CURVE_ORDER) - public_key = private_key.get_verifying_key() - if not public_key.verify_digest(sig, data, sigdecode=sigdecode): - raise Exception('Sanity check verifying our own signature failed.') + extra_entropy = counter.to_bytes(32, byteorder="little") + r, s = sign_with_extra_entropy(extra_entropy=extra_entropy) + + sig_string = sig_string_from_r_and_s(r, s) + self.verify_message_hash(sig_string, msg_hash) + + sig = sigencode(r, s) return sig def sign_transaction(self, hashed_preimage: bytes) -> bytes: - return self.sign(hashed_preimage, - sigencode=der_sig_from_r_and_s, - sigdecode=get_r_and_s_from_der_sig) + return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s) def sign_message(self, message: bytes, is_compressed: bool, algo=lambda x: sha256d(msg_magic(x))) -> bytes: def bruteforce_recid(sig_string): @@ -462,9 +489,7 @@ class ECPrivkey(ECPubkey): message = to_bytes(message, 'utf8') msg_hash = algo(message) - sig_string = self.sign(msg_hash, - sigencode=sig_string_from_r_and_s, - sigdecode=get_r_and_s_from_sig_string) + sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s) sig65, recid = bruteforce_recid(sig_string) return sig65 @@ -479,12 +504,9 @@ class ECPrivkey(ECPubkey): if magic_found != magic: raise Exception('invalid ciphertext: invalid magic bytes') try: - ecdsa_point = _ser_to_python_ecdsa_point(ephemeral_pubkey_bytes) + ephemeral_pubkey = ECPubkey(ephemeral_pubkey_bytes) except InvalidECPointException as e: raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e - if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ecdsa_point.x(), ecdsa_point.y()): - raise Exception('invalid ciphertext: invalid ephemeral pubkey') - ephemeral_pubkey = ECPubkey.from_point(ecdsa_point) ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True) key = hashlib.sha512(ecdh_key).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py @@ -5,14 +5,11 @@ import os import sys import traceback import ctypes -from ctypes.util import find_library from ctypes import ( byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER, cast ) -import ecdsa - from .logging import get_logger @@ -36,19 +33,32 @@ SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BI SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION) +class LibModuleMissing(Exception): pass + + def load_library(): if sys.platform == 'darwin': - library_path = 'libsecp256k1.0.dylib' + library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1.0.dylib'), + 'libsecp256k1.0.dylib') elif sys.platform in ('windows', 'win32'): - library_path = 'libsecp256k1.dll' + library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1-0.dll'), + 'libsecp256k1-0.dll') elif 'ANDROID_DATA' in os.environ: - library_path = 'libsecp256k1.so' - else: - library_path = 'libsecp256k1.so.0' - - secp256k1 = ctypes.cdll.LoadLibrary(library_path) + library_paths = ('libsecp256k1.so',) + else: # desktop Linux and similar + library_paths = (os.path.join(os.path.dirname(__file__), 'libsecp256k1.so.0'), + 'libsecp256k1.so.0') + + secp256k1 = None + for libpath in library_paths: + try: + secp256k1 = ctypes.cdll.LoadLibrary(libpath) + except: + pass + else: + break if not secp256k1: - _logger.warning('libsecp256k1 library failed to load') + _logger.error('libsecp256k1 library failed to load') return None try: @@ -82,189 +92,48 @@ def load_library(): secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p] secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int + secp256k1.secp256k1_ecdsa_signature_parse_der.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t] + secp256k1.secp256k1_ecdsa_signature_parse_der.restype = c_int + + secp256k1.secp256k1_ecdsa_signature_serialize_der.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p] + secp256k1.secp256k1_ecdsa_signature_serialize_der.restype = c_int + secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p] secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int secp256k1.secp256k1_ec_pubkey_combine.argtypes = [c_void_p, c_char_p, c_void_p, c_size_t] secp256k1.secp256k1_ec_pubkey_combine.restype = c_int + # --enable-module-recovery + try: + secp256k1.secp256k1_ecdsa_recover.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] + secp256k1.secp256k1_ecdsa_recover.restype = c_int + + secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p, c_int] + secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.restype = c_int + except (OSError, AttributeError): + raise LibModuleMissing('libsecp256k1 library found but it was built ' + 'without required module (--enable-module-recovery)') + secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) - r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) - if r: - return secp256k1 - else: - _logger.warning('secp256k1_context_randomize failed') + ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) + if not ret: + _logger.error('secp256k1_context_randomize failed') return None - except (OSError, AttributeError): - _logger.warning('libsecp256k1 library was found and loaded but there was an error when using it') - return None - -class _patched_functions: - prepared_to_patch = False - monkey_patching_active = False - - -def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1(): - if not _libsecp256k1: - return - - # save original functions so that we can undo patching (needed for tests) - _patched_functions.orig_sign = staticmethod(ecdsa.ecdsa.Private_key.sign) - _patched_functions.orig_verify = staticmethod(ecdsa.ecdsa.Public_key.verifies) - _patched_functions.orig_mul = staticmethod(ecdsa.ellipticcurve.Point.__mul__) - _patched_functions.orig_add = staticmethod(ecdsa.ellipticcurve.Point.__add__) - - curve_secp256k1 = ecdsa.ecdsa.curve_secp256k1 - curve_order = ecdsa.curves.SECP256k1.order - point_at_infinity = ecdsa.ellipticcurve.INFINITY - - def _get_ptr_to_well_formed_pubkey_string_buffer_from_ecdsa_point(point: ecdsa.ellipticcurve.Point): - assert point.curve() == curve_secp256k1 - pubkey = create_string_buffer(64) - public_pair_bytes = b'\4' + point.x().to_bytes(32, byteorder="big") + point.y().to_bytes(32, byteorder="big") - r = _libsecp256k1.secp256k1_ec_pubkey_parse( - _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) - if not r: - raise Exception('public key could not be parsed or is invalid') - return pubkey - - def _get_ecdsa_point_from_libsecp256k1_pubkey_object(pubkey) -> ecdsa.ellipticcurve.Point: - pubkey_serialized = create_string_buffer(65) - pubkey_size = c_size_t(65) - _libsecp256k1.secp256k1_ec_pubkey_serialize( - _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED) - x = int.from_bytes(pubkey_serialized[1:33], byteorder="big") - y = int.from_bytes(pubkey_serialized[33:], byteorder="big") - return ecdsa.ellipticcurve.Point(curve_secp256k1, x, y, curve_order) - - def add(self: ecdsa.ellipticcurve.Point, other: ecdsa.ellipticcurve.Point) -> ecdsa.ellipticcurve.Point: - if self.curve() != curve_secp256k1: - # this operation is not on the secp256k1 curve; use original implementation - return _patched_functions.orig_add(self, other) - if self == point_at_infinity: return other - if other == point_at_infinity: return self - - pubkey1 = _get_ptr_to_well_formed_pubkey_string_buffer_from_ecdsa_point(self) - pubkey2 = _get_ptr_to_well_formed_pubkey_string_buffer_from_ecdsa_point(other) - pubkey_sum = create_string_buffer(64) - - pubkey1 = cast(pubkey1, c_char_p) - pubkey2 = cast(pubkey2, c_char_p) - array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2) - r = _libsecp256k1.secp256k1_ec_pubkey_combine(_libsecp256k1.ctx, pubkey_sum, array_of_pubkey_ptrs, 2) - if not r: - return point_at_infinity - return _get_ecdsa_point_from_libsecp256k1_pubkey_object(pubkey_sum) - - def mul(self: ecdsa.ellipticcurve.Point, other: int) -> ecdsa.ellipticcurve.Point: - if self.curve() != curve_secp256k1: - # this operation is not on the secp256k1 curve; use original implementation - return _patched_functions.orig_mul(self, other) - other %= curve_order - if self == point_at_infinity or other == 0: - return point_at_infinity - pubkey = _get_ptr_to_well_formed_pubkey_string_buffer_from_ecdsa_point(self) - r = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big")) - if not r: - return point_at_infinity - return _get_ecdsa_point_from_libsecp256k1_pubkey_object(pubkey) - - def sign(self: ecdsa.ecdsa.Private_key, hash: int, random_k: int) -> ecdsa.ecdsa.Signature: - # note: random_k is ignored - if self.public_key.curve != curve_secp256k1: - # this operation is not on the secp256k1 curve; use original implementation - return _patched_functions.orig_sign(self, hash, random_k) - secret_exponent = self.secret_multiplier - nonce_function = None - sig = create_string_buffer(64) - sig_hash_bytes = hash.to_bytes(32, byteorder="big") - def sign_with_extra_entropy(extra_entropy): - ret = _libsecp256k1.secp256k1_ecdsa_sign( - _libsecp256k1.ctx, sig, sig_hash_bytes, secret_exponent.to_bytes(32, byteorder="big"), - nonce_function, extra_entropy) - if not ret: - raise Exception('the nonce generation function failed, or the private key was invalid') - compact_signature = create_string_buffer(64) - _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) - r = int.from_bytes(compact_signature[:32], byteorder="big") - s = int.from_bytes(compact_signature[32:], byteorder="big") - return r, s - - r, s = sign_with_extra_entropy(extra_entropy=None) - counter = 0 - while r >= 2**255: # grind for low R value https://github.com/bitcoin/bitcoin/pull/13666 - counter += 1 - extra_entropy = counter.to_bytes(32, byteorder="little") - r, s = sign_with_extra_entropy(extra_entropy=extra_entropy) - return ecdsa.ecdsa.Signature(r, s) - - def verify(self: ecdsa.ecdsa.Public_key, hash: int, signature: ecdsa.ecdsa.Signature) -> bool: - if self.curve != curve_secp256k1: - # this operation is not on the secp256k1 curve; use original implementation - return _patched_functions.orig_verify(self, hash, signature) - sig = create_string_buffer(64) - input64 = signature.r.to_bytes(32, byteorder="big") + signature.s.to_bytes(32, byteorder="big") - r = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, input64) - if not r: - return False - r = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) - - public_pair_bytes = b'\4' + self.point.x().to_bytes(32, byteorder="big") + self.point.y().to_bytes(32, byteorder="big") - pubkey = create_string_buffer(64) - r = _libsecp256k1.secp256k1_ec_pubkey_parse( - _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) - if not r: - return False - - return 1 == _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, hash.to_bytes(32, byteorder="big"), pubkey) - - # save new functions so that we can (re-)do patching - _patched_functions.fast_sign = sign - _patched_functions.fast_verify = verify - _patched_functions.fast_mul = mul - _patched_functions.fast_add = add - - _patched_functions.prepared_to_patch = True - - -def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1(): - if not _libsecp256k1: - # FIXME logging 'verbosity' is not yet initialised - _logger.info('libsecp256k1 library not available, falling back to python-ecdsa. ' - 'This means signing operations will be slower.') - return - if not _patched_functions.prepared_to_patch: - raise Exception("can't patch python-ecdsa without preparations") - ecdsa.ecdsa.Private_key.sign = _patched_functions.fast_sign - ecdsa.ecdsa.Public_key.verifies = _patched_functions.fast_verify - ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.fast_mul - ecdsa.ellipticcurve.Point.__add__ = _patched_functions.fast_add - - _patched_functions.monkey_patching_active = True - - -def undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1(): - if not _libsecp256k1: - return - if not _patched_functions.prepared_to_patch: - raise Exception("can't patch python-ecdsa without preparations") - ecdsa.ecdsa.Private_key.sign = _patched_functions.orig_sign - ecdsa.ecdsa.Public_key.verifies = _patched_functions.orig_verify - ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.orig_mul - ecdsa.ellipticcurve.Point.__add__ = _patched_functions.orig_add - - _patched_functions.monkey_patching_active = False - - -def is_using_fast_ecc(): - return _patched_functions.monkey_patching_active + return secp256k1 + except (OSError, AttributeError) as e: + _logger.error(f'libsecp256k1 library was found and loaded but there was an error when using it: {repr(e)}') + return None +_libsecp256k1 = None try: _libsecp256k1 = load_library() except BaseException as e: - _logger.warning(f'failed to load libsecp256k1: {repr(e)}') - _libsecp256k1 = None + _logger.error(f'failed to load libsecp256k1: {repr(e)}') + -_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() +if _libsecp256k1 is None: + # hard fail: + sys.exit(f"Error: Failed to load libsecp256k1.") diff --git a/electrum/keystore.py b/electrum/keystore.py @@ -36,7 +36,7 @@ from .bitcoin import deserialize_privkey, serialize_privkey from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME, is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation, convert_bip32_intpath_to_strpath) -from .ecc import string_to_number, number_to_string +from .ecc import string_to_number from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST, SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160) from .util import (InvalidPassword, WalletFileException, @@ -615,7 +615,7 @@ class Old_KeyStore(MasterPublicKeyMixin, Deterministic_KeyStore): def get_pubkey_from_mpk(cls, mpk, for_change, n) -> bytes: z = cls.get_sequence(mpk, for_change, n) master_public_key = ecc.ECPubkey(bfh('04'+mpk)) - public_key = master_public_key + z*ecc.generator() + public_key = master_public_key + z*ecc.GENERATOR return public_key.get_public_key_bytes(compressed=False) @lru_cache(maxsize=None) @@ -626,7 +626,7 @@ class Old_KeyStore(MasterPublicKeyMixin, Deterministic_KeyStore): def _get_private_key_from_stretched_exponent(self, for_change, n, secexp): secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % ecc.CURVE_ORDER - pk = number_to_string(secexp, ecc.CURVE_ORDER) + pk = int.to_bytes(secexp, length=32, byteorder='big', signed=False) return pk def get_private_key(self, sequence: Sequence[int], password): diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py @@ -1039,7 +1039,7 @@ class Peer(Logger): timestamp=now.to_bytes(4, byteorder="big"), ) sighash = sha256d(chan_upd[2 + 64:]) - sig = ecc.ECPrivkey(self.privkey).sign(sighash, sig_string_from_r_and_s, get_r_and_s_from_sig_string) + sig = ecc.ECPrivkey(self.privkey).sign(sighash, sig_string_from_r_and_s) message_type, payload = decode_msg(chan_upd) payload['signature'] = sig chan_upd = encode_msg(message_type, **payload) @@ -1071,8 +1071,8 @@ class Peer(Logger): ) to_hash = chan_ann[256+2:] h = sha256d(to_hash) - bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string) - node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string) + bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s) + node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s) self.send_message("announcement_signatures", channel_id=chan.channel_id, short_channel_id=chan.short_channel_id, diff --git a/electrum/lnutil.py b/electrum/lnutil.py @@ -254,7 +254,7 @@ def privkey_to_pubkey(priv: bytes) -> bytes: return ecc.ECPrivkey(priv[:32]).get_public_key_bytes() def derive_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes: - p = ecc.ECPubkey(basepoint) + ecc.generator() * ecc.string_to_number(sha256(per_commitment_point + basepoint)) + p = ecc.ECPubkey(basepoint) + ecc.GENERATOR * ecc.string_to_number(sha256(per_commitment_point + basepoint)) return p.get_public_key_bytes() def derive_privkey(secret: int, per_commitment_point: bytes) -> int: @@ -275,7 +275,7 @@ def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes k1 = ecc.string_to_number(basepoint_secret) * ecc.string_to_number(sha256(basepoint + per_commitment_point)) k2 = ecc.string_to_number(per_commitment_secret) * ecc.string_to_number(sha256(per_commitment_point + basepoint)) sum = (k1 + k2) % ecc.CURVE_ORDER - return ecc.number_to_string(sum, CURVE_ORDER) + return int.to_bytes(sum, length=32, byteorder='big', signed=False) def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -40,7 +40,6 @@ from .lntransport import LNTransport, LNResponderTransport from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnaddr import lnencode, LnAddr, lndecode from .ecc import der_sig_from_sig_string -from .ecc_fast import is_using_fast_ecc from .lnchannel import Channel from .lnchannel import channel_states, peer_states from . import lnutil @@ -303,7 +302,6 @@ class LNGossip(LNWorker): self.localfeatures |= LnLocalFeatures.GOSSIP_QUERIES_OPT self.localfeatures |= LnLocalFeatures.GOSSIP_QUERIES_REQ self.unknown_ids = set() - assert is_using_fast_ecc(), "verifying LN gossip msgs without libsecp256k1 is hopeless" def start_network(self, network: 'Network'): assert network diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py @@ -28,9 +28,7 @@ import hashlib import unicodedata import string -import ecdsa - -from .util import resource_path, bfh, bh2u +from .util import resource_path, bfh, bh2u, randrange from .crypto import hmac_oneshot from . import version from .logging import Logger @@ -180,7 +178,7 @@ class Mnemonic(Logger): entropy = 1 while entropy < pow(2, n - bpw): # try again if seed would not contain enough words - entropy = ecdsa.util.randrange(pow(2, n)) + entropy = randrange(pow(2, n)) nonce = 0 while True: nonce += 1 diff --git a/electrum/msqr.py b/electrum/msqr.py @@ -1,94 +0,0 @@ -# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ - -def modular_sqrt(a, p): - """ Find a quadratic residue (mod p) of 'a'. p - must be an odd prime. - - Solve the congruence of the form: - x^2 = a (mod p) - And returns x. Note that p - x is also a root. - - 0 is returned is no square root exists for - these a and p. - - The Tonelli-Shanks algorithm is used (except - for some simple cases in which the solution - is known from an identity). This algorithm - runs in polynomial time (unless the - generalized Riemann hypothesis is false). - """ - # Simple cases - # - if legendre_symbol(a, p) != 1: - return 0 - elif a == 0: - return 0 - elif p == 2: - return p - elif p % 4 == 3: - return pow(a, (p + 1) // 4, p) - - # Partition p-1 to s * 2^e for an odd s (i.e. - # reduce all the powers of 2 from p-1) - # - s = p - 1 - e = 0 - while s % 2 == 0: - s //= 2 - e += 1 - - # Find some 'n' with a legendre symbol n|p = -1. - # Shouldn't take long. - # - n = 2 - while legendre_symbol(n, p) != -1: - n += 1 - - # Here be dragons! - # Read the paper "Square roots from 1; 24, 51, - # 10 to Dan Shanks" by Ezra Brown for more - # information - # - - # x is a guess of the square root that gets better - # with each iteration. - # b is the "fudge factor" - by how much we're off - # with the guess. The invariant x^2 = ab (mod p) - # is maintained throughout the loop. - # g is used for successive powers of n to update - # both a and b - # r is the exponent - decreases with each update - # - x = pow(a, (s + 1) // 2, p) - b = pow(a, s, p) - g = pow(n, s, p) - r = e - - while True: - t = b - m = 0 - for m in range(r): - if t == 1: - break - t = pow(t, 2, p) - - if m == 0: - return x - - gs = pow(g, 2 ** (r - m - 1), p) - g = (gs * gs) % p - x = (x * gs) % p - b = (b * g) % p - r = m - -def legendre_symbol(a, p): - """ Compute the Legendre symbol a|p using - Euler's criterion. p is a prime, a is - relatively prime to p (if p divides - a, then a|p = 0) - - Returns 1 if a has a square root modulo - p, -1 otherwise. - """ - ls = pow(a, (p - 1) // 2, p) - return -1 if ls == p - 1 else ls diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py @@ -15,8 +15,7 @@ from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath, normalize_bip32_derivation, is_all_public_derivation) from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS from electrum import ecc, crypto, constants -from electrum.ecc import number_to_string, string_to_number -from electrum.util import bfh, bh2u, InvalidPassword +from electrum.util import bfh, bh2u, InvalidPassword, randrange from electrum.storage import WalletStorage from electrum.keystore import xtype_from_derivation @@ -33,31 +32,6 @@ except ImportError: sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo python3 -m pip install ecdsa'") -def needs_test_with_all_ecc_implementations(func): - """Function decorator to run a unit test twice: - once when libsecp256k1 is not available, once when it is. - - NOTE: this is inherently sequential; - tests running in parallel would break things - """ - def run_test(*args, **kwargs): - if FAST_TESTS: # if set, only run tests once, using fastest implementation - func(*args, **kwargs) - return - ecc_fast.undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() - try: - # first test without libsecp - func(*args, **kwargs) - finally: - ecc_fast.do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() - # if libsecp is not available, we are done - if not ecc_fast._libsecp256k1: - return - # if libsecp is available, test again now - func(*args, **kwargs) - return run_test - - def needs_test_with_all_aes_implementations(func): """Function decorator to run a unit test twice: once when pycryptodomex is not available, once when it is. @@ -95,15 +69,14 @@ class Test_bitcoin(ElectrumTestCase): self.assertTrue(bool(crypto.AES)) @needs_test_with_all_aes_implementations - @needs_test_with_all_ecc_implementations def test_crypto(self): for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]: self._do_test_crypto(message) def _do_test_crypto(self, message): - G = ecc.generator() + G = ecc.GENERATOR _r = G.order() - pvk = ecdsa.util.randrange(_r) + pvk = randrange(_r) Pub = pvk*G pubkey_c = Pub.get_public_key_bytes(True) @@ -111,7 +84,7 @@ class Test_bitcoin(ElectrumTestCase): addr_c = public_key_to_p2pkh(pubkey_c) #print "Private key ", '%064x'%pvk - eck = ecc.ECPrivkey(number_to_string(pvk,_r)) + eck = ecc.ECPrivkey.from_secret_scalar(pvk) #print "Compressed public key ", pubkey_c.encode('hex') enc = ecc.ECPubkey(pubkey_c).encrypt_message(message) @@ -127,13 +100,12 @@ class Test_bitcoin(ElectrumTestCase): #print signature eck.verify_message_for_address(signature, message) - @needs_test_with_all_ecc_implementations def test_ecc_sanity(self): - G = ecc.generator() + G = ecc.GENERATOR n = G.order() self.assertEqual(ecc.CURVE_ORDER, n) inf = n * G - self.assertEqual(ecc.point_at_infinity(), inf) + self.assertEqual(ecc.POINT_AT_INFINITY, inf) self.assertTrue(inf.is_at_infinity()) self.assertFalse(G.is_at_infinity()) self.assertEqual(11 * G, 7 * G + 4 * G) @@ -155,7 +127,6 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(2 * G, inf + 2 * G) self.assertEqual(inf, 3 * G + (-3 * G)) - @needs_test_with_all_ecc_implementations def test_msg_signing(self): msg1 = b'Chancellor on brink of second bailout for banks' msg2 = b'Electrum' @@ -185,7 +156,6 @@ class Test_bitcoin(ElectrumTestCase): self.assertFalse(ecc.verify_message_with_address(addr1, sig2, msg1)) @needs_test_with_all_aes_implementations - @needs_test_with_all_ecc_implementations def test_decrypt_message(self): key = WalletStorage.get_eckey_from_password('pw123') self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQMDFtgT3zWSQsa+Uie8H/WvfUjlu9UN9OJtTt3KlgKeSTi6SQfuhcg1uIz9hp3WIUOFGTLr4RNQBdjPNqzXwhkcPi2Xsbiw6UCNJncVPJ6QBg==')) @@ -193,7 +163,6 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(b'hey_there' * 100, key.decrypt_message(b'QklFMQLOOsabsXtGQH8edAa6VOUa5wX8/DXmxX9NyHoAx1a5bWgllayGRVPeI2bf0ZdWK0tfal0ap0ZIVKbd2eOJybqQkILqT6E1/Syzq0Zicyb/AA1eZNkcX5y4gzloxinw00ubCA8M7gcUjJpOqbnksATcJ5y2YYXcHMGGfGurWu6uJ/UyrNobRidWppRMW5yR9/6utyNvT6OHIolCMEf7qLcmtneoXEiz51hkRdZS7weNf9mGqSbz9a2NL3sdh1A0feHIjAZgcCKcAvksNUSauf0/FnIjzTyPRpjRDMeDC8Ci3sGiuO3cvpWJwhZfbjcS26KmBv2CHWXfRRNFYOInHZNIXWNAoBB47Il5bGSMd+uXiGr+SQ9tNvcu+BiJNmFbxYqg+oQ8dGAl1DtvY2wJVY8k7vO9BIWSpyIxfGw7EDifhc5vnOmGe016p6a01C3eVGxgl23UYMrP7+fpjOcPmTSF4rk5U5ljEN3MSYqlf1QEv0OqlI9q1TwTK02VBCjMTYxDHsnt04OjNBkNO8v5uJ4NR+UUDBEp433z53I59uawZ+dbk4v4ZExcl8EGmKm3Gzbal/iJ/F7KQuX2b/ySEhLOFVYFWxK73X1nBvCSK2mC2/8fCw8oI5pmvzJwQhcCKTdEIrz3MMvAHqtPScDUOjzhXxInQOCb3+UBj1PPIdqkYLvZss1TEaBwYZjLkVnK2MBj7BaqT6Rp6+5A/fippUKHsnB6eYMEPR2YgDmCHL+4twxHJG6UWdP3ybaKiiAPy2OHNP6PTZ0HrqHOSJzBSDD+Z8YpaRg29QX3UEWlqnSKaan0VYAsV1VeaN0XFX46/TWO0L5tjhYVXJJYGqo6tIQJymxATLFRF6AZaD1Mwd27IAL04WkmoQoXfO6OFfwdp/shudY/1gBkDBvGPICBPtnqkvhGF+ZF3IRkuPwiFWeXmwBxKHsRx/3+aJu32Ml9+za41zVk2viaxcGqwTc5KMexQFLAUwqhv+aIik7U+5qk/gEVSuRoVkihoweFzKolNF+BknH2oB4rZdPixag5Zje3DvgjsSFlOl69W/67t/Gs8htfSAaHlsB8vWRQr9+v/lxTbrAw+O0E+sYGoObQ4qQMyQshNZEHbpPg63eWiHtJJnrVBvOeIbIHzoLDnMDsWVWZSMzAQ1vhX1H5QLgSEbRlKSliVY03kDkh/Nk/KOn+B2q37Ialq4JcRoIYFGJ8AoYEAD0tRuTqFddIclE75HzwaNG7NyKW1plsa72ciOPwsPJsdd5F0qdSQ3OSKtooTn7uf6dXOc4lDkfrVYRlZ0PX')) @needs_test_with_all_aes_implementations - @needs_test_with_all_ecc_implementations def test_encrypt_message(self): key = WalletStorage.get_eckey_from_password('secret_password77') msgs = [ @@ -207,7 +176,6 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(plaintext, key.decrypt_message(ciphertext2)) self.assertNotEqual(ciphertext1, ciphertext2) - @needs_test_with_all_ecc_implementations def test_sign_transaction(self): eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94')) @@ -423,7 +391,6 @@ class Test_xprv_xpub(ElectrumTestCase): return xpub, xprv - @needs_test_with_all_ecc_implementations def test_bip32(self): # see https://en.bitcoin.it/wiki/BIP_0032_TestVectors xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000") @@ -434,14 +401,12 @@ class Test_xprv_xpub(ElectrumTestCase): self.assertEqual("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", xpub) self.assertEqual("xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", xprv) - @needs_test_with_all_ecc_implementations def test_xpub_from_xprv(self): """We can derive the xpub key from a xprv.""" for xprv_details in self.xprv_xpub: result = xpub_from_xprv(xprv_details['xprv']) self.assertEqual(result, xprv_details['xpub']) - @needs_test_with_all_ecc_implementations def test_is_xpub(self): for xprv_details in self.xprv_xpub: xpub = xprv_details['xpub'] @@ -449,13 +414,11 @@ class Test_xprv_xpub(ElectrumTestCase): self.assertFalse(is_xpub('xpub1nval1d')) self.assertFalse(is_xpub('xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG')) - @needs_test_with_all_ecc_implementations def test_xpub_type(self): for xprv_details in self.xprv_xpub: xpub = xprv_details['xpub'] self.assertEqual(xprv_details['xtype'], xpub_type(xpub)) - @needs_test_with_all_ecc_implementations def test_is_xprv(self): for xprv_details in self.xprv_xpub: xprv = xprv_details['xprv'] @@ -680,7 +643,6 @@ class Test_keyImport(ElectrumTestCase): 'scripthash': '5b07ddfde826f5125ee823900749103cea37808038ecead5505a766a07c34445'}, ) - @needs_test_with_all_ecc_implementations def test_public_key_from_private_key(self): for priv_details in self.priv_pub_addr: txin_type, privkey, compressed = deserialize_privkey(priv_details['priv']) @@ -689,13 +651,11 @@ class Test_keyImport(ElectrumTestCase): self.assertEqual(priv_details['txin_type'], txin_type) self.assertEqual(priv_details['compressed'], compressed) - @needs_test_with_all_ecc_implementations def test_address_from_private_key(self): for priv_details in self.priv_pub_addr: addr2 = address_from_private_key(priv_details['priv']) self.assertEqual(priv_details['address'], addr2) - @needs_test_with_all_ecc_implementations def test_is_valid_address(self): for priv_details in self.priv_pub_addr: addr = priv_details['address'] @@ -721,7 +681,6 @@ class Test_keyImport(ElectrumTestCase): self.assertTrue(is_address('bc1qxq64lrwt02hm7tu25lr3hm9tgzh58snfe67yt6')) self.assertFalse(is_address('bc1qxq64lrwt02hm7tu25lr3hm9tgzh58snfe67yt5')) - @needs_test_with_all_ecc_implementations def test_is_private_key(self): for priv_details in self.priv_pub_addr: self.assertTrue(is_private_key(priv_details['priv'])) @@ -730,39 +689,33 @@ class Test_keyImport(ElectrumTestCase): self.assertFalse(is_private_key(priv_details['address'])) self.assertFalse(is_private_key("not a privkey")) - @needs_test_with_all_ecc_implementations def test_serialize_privkey(self): for priv_details in self.priv_pub_addr: txin_type, privkey, compressed = deserialize_privkey(priv_details['priv']) priv2 = serialize_privkey(privkey, compressed, txin_type) self.assertEqual(priv_details['exported_privkey'], priv2) - @needs_test_with_all_ecc_implementations def test_address_to_scripthash(self): for priv_details in self.priv_pub_addr: sh = address_to_scripthash(priv_details['address']) self.assertEqual(priv_details['scripthash'], sh) - @needs_test_with_all_ecc_implementations def test_is_minikey(self): for priv_details in self.priv_pub_addr: minikey = priv_details['minikey'] priv = priv_details['priv'] self.assertEqual(minikey, is_minikey(priv)) - @needs_test_with_all_ecc_implementations def test_is_compressed_privkey(self): for priv_details in self.priv_pub_addr: self.assertEqual(priv_details['compressed'], is_compressed_privkey(priv_details['priv'])) - @needs_test_with_all_ecc_implementations def test_segwit_uncompressed_pubkey(self): with self.assertRaises(BitcoinException): is_private_key("p2wpkh-p2sh:5JKXxT3wAZHcybJ9YNkuHur9vou6uuAnorBV9A8vVxGNFH5wvTW", raise_on_error=True) - @needs_test_with_all_ecc_implementations def test_wif_with_invalid_magic_byte_for_compressed_pubkey(self): with self.assertRaises(BitcoinException): is_private_key("KwFAa6AumokBD2dVqQLPou42jHiVsvThY1n25HJ8Ji8REf1wxAQb", diff --git a/electrum/tests/test_dnssec.py b/electrum/tests/test_dnssec.py @@ -3,12 +3,10 @@ import dns from electrum import dnssec from . import ElectrumTestCase -from .test_bitcoin import needs_test_with_all_ecc_implementations class TestDnsSec(ElectrumTestCase): - @needs_test_with_all_ecc_implementations def test_python_validate_rrsig_ecdsa(self): rrset = dns.rrset.from_text("getmonero.org.", 3599, 1, 48, "257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d xCjjnopKl+GqJxpVXckHAeF+KkxLbxIL fDLUT0rAK9iUzy1L53eKGQ==", diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py @@ -13,7 +13,6 @@ from electrum.plugins.trustedcoin import trustedcoin from electrum.plugins.trustedcoin.legacy_tx_format import serialize_tx_in_legacy_format from . import ElectrumTestCase, TestCaseForTestnet -from .test_bitcoin import needs_test_with_all_ecc_implementations signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700" @@ -64,7 +63,6 @@ class TestBCDataStream(ElectrumTestCase): class TestTransaction(ElectrumTestCase): - @needs_test_with_all_ecc_implementations def test_tx_update_signatures(self): tx = tx_from_any("cHNidP8BAFUBAAAAASpcmpT83pj1WBzQAWLGChOTbOt1OJ6mW/OGM7Qk60AxAAAAAAD/////AUBCDwAAAAAAGXapFCMKw3g0BzpCFG8R74QUrpKf6q/DiKwAAAAAAAAA") tx.inputs()[0].script_type = 'p2pkh' @@ -73,7 +71,6 @@ class TestTransaction(ElectrumTestCase): tx.update_signatures(signed_blob_signatures) self.assertEqual(tx.serialize(), signed_blob) - @needs_test_with_all_ecc_implementations def test_tx_deserialize_for_signed_network_tx(self): tx = transaction.Transaction(signed_blob) tx.deserialize() diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py @@ -18,7 +18,6 @@ from electrum.plugins.trustedcoin import trustedcoin from . import TestCaseForTestnet from . import ElectrumTestCase -from .test_bitcoin import needs_test_with_all_ecc_implementations UNICODE_HORROR_HEX = 'e282bf20f09f988020f09f98882020202020e3818620e38191e3819fe381be20e3828fe3828b2077cda2cda2cd9d68cda16fcda2cda120ccb8cda26bccb5cd9f6eccb4cd98c7ab77ccb8cc9b73cd9820cc80cc8177cd98cda2e1b8a9ccb561d289cca1cda27420cca7cc9568cc816fccb572cd8fccb5726f7273cca120ccb6cda1cda06cc4afccb665cd9fcd9f20ccb6cd9d696ecda220cd8f74cc9568ccb7cca1cd9f6520cd9fcd9f64cc9b61cd9c72cc95cda16bcca2cca820cda168ccb465cd8f61ccb7cca2cca17274cc81cd8f20ccb4ccb7cda0c3b2ccb5ccb666ccb82075cca7cd986ec3adcc9bcd9c63cda2cd8f6fccb7cd8f64ccb8cda265cca1cd9d3fcd9e' @@ -83,7 +82,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): super().setUp() self.config = SimpleConfig({'electrum_path': self.electrum_path}) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_standard(self, mock_save_db): seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' @@ -103,7 +101,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf') self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_segwit(self, mock_save_db): seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver' @@ -123,7 +120,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af') self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_segwit_passphrase(self, mock_save_db): seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver' @@ -143,7 +139,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], 'bc1qx94dutas7ysn2my645cyttujrms5d9p57f6aam') self.assertEqual(w.get_change_addresses()[0], 'bc1qcywwsy87sdp8vz5rfjh3sxdv6rt95kujdqq38g') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_old(self, mock_save_db): seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over' @@ -162,7 +157,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo') self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_2fa_legacy(self, mock_save_db): seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove' @@ -197,7 +191,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV') self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_seed_2fa_segwit(self, mock_save_db): seed_words = 'universe topic remind silver february ranch shine worth innocent cattle enhance wise' @@ -232,7 +225,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], 'bc1qpmufh0zjp5prfsrk2yskcy82sa26srqkd97j0457andc6m0gh5asw7kqd2') self.assertEqual(w.get_change_addresses()[0], 'bc1qd4q50nft7kxm9yglfnpup9ed2ukj3tkxp793y0zya8dc9m39jcwq308dxz') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_seed_bip44_standard(self, mock_save_db): seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' @@ -251,7 +243,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo') self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_seed_bip44_standard_passphrase(self, mock_save_db): seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' @@ -270,7 +261,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '1F88g2naBMhDB7pYFttPWGQgryba3hPevM') self.assertEqual(w.get_change_addresses()[0], '1H4QD1rg2zQJ4UjuAVJr5eW1fEM8WMqyxh') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_seed_bip49_p2sh_segwit(self, mock_save_db): seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' @@ -289,7 +279,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_seed_bip84_native_segwit(self, mock_save_db): # test case from bip84 @@ -309,7 +298,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_multisig_seed_standard(self, mock_save_db): seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure' @@ -332,7 +320,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN') self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_electrum_multisig_seed_segwit(self, mock_save_db): seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun' @@ -355,7 +342,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc') self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_multisig_seed_bip45_standard(self, mock_save_db): seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' @@ -378,7 +364,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN') self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip39_multisig_seed_p2sh_segwit(self, mock_save_db): # bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor @@ -400,7 +385,6 @@ class TestWalletKeystoreAddressIntegrityForMainnet(ElectrumTestCase): self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns') self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip32_extended_version_bytes(self, mock_save_db): seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant' @@ -488,7 +472,6 @@ class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet): self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt') self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7') - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_bip32_extended_version_bytes(self, mock_save_db): seed_words = 'crouch dumb relax small truck age shine pink invite spatial object tenant' @@ -559,7 +542,6 @@ class TestWalletSending(TestCaseForTestnet): ks = keystore.from_seed(seed_words, '', False) return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2, config=self.config) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_save_db): wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver') @@ -616,7 +598,6 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 250000 - 5000 + 100000, 0), wallet1.get_balance()) self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_save_db): wallet1a = WalletIntegrityHelper.create_multisig_wallet( @@ -697,7 +678,6 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 370000 - 5000 + 100000, 0), wallet1a.get_balance()) self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_save_db): wallet1a = WalletIntegrityHelper.create_multisig_wallet( @@ -807,7 +787,6 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 165000 - 5000 + 100000, 0), wallet1a.get_balance()) self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_save_db): wallet1a = WalletIntegrityHelper.create_multisig_wallet( @@ -877,7 +856,6 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 1000000 - 5000 + 300000, 0), wallet1a.get_balance()) self.assertEqual((0, 1000000 - 5000 - 300000, 0), wallet2.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_rbf(self, mock_save_db): self.maxDiff = None @@ -958,7 +936,6 @@ class TestWalletSending(TestCaseForTestnet): wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) self.assertEqual((0, 7484320, 0), wallet.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_cpfp_p2pkh(self, mock_save_db): wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean') @@ -1360,7 +1337,6 @@ class TestWalletSending(TestCaseForTestnet): wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) self.assertEqual((0, 3_900_000, 0), wallet.get_balance()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_cpfp_p2wpkh(self, mock_save_db): wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage') @@ -1394,7 +1370,6 @@ class TestWalletSending(TestCaseForTestnet): wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) self.assertEqual((0, funding_output_value - 50000, 0), wallet.get_balance()) - @needs_test_with_all_ecc_implementations def test_sweep_p2pk(self): class NetworkMock: @@ -1419,7 +1394,6 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual('7f827fc5256c274fd1094eb7e020c8ded0baf820356f61aa4f14a9093b0ea0ee', tx_copy.txid()) self.assertEqual('7f827fc5256c274fd1094eb7e020c8ded0baf820356f61aa4f14a9093b0ea0ee', tx_copy.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_coinjoin_between_two_p2wpkh_electrum_seeds(self, mock_save_db): wallet1 = WalletIntegrityHelper.create_standard_wallet( @@ -1511,7 +1485,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): super().setUp() self.config = SimpleConfig({'electrum_path': self.electrum_path}) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_old_electrum_seed_online_mpk(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1558,7 +1531,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('06032230d0bf6a277bc4f8c39e3311a712e0e614626d0dea7cc9f592abfae5d8', tx.txid()) self.assertEqual('06032230d0bf6a277bc4f8c39e3311a712e0e614626d0dea7cc9f592abfae5d8', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_xpub_p2pkh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1604,7 +1576,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('d9c21696eca80321933e7444ca928aaf25eeda81aaa2f4e5c085d4d0a9cf7aa7', tx.txid()) self.assertEqual('d9c21696eca80321933e7444ca928aaf25eeda81aaa2f4e5c085d4d0a9cf7aa7', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_xpub_p2wpkh_p2sh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1651,7 +1622,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('3f0d188519237478258ad2bf881643618635d11c2bb95512e830fcf2eda3c522', tx.txid()) self.assertEqual('27b78ec072a403b0545258e7a1a8d494e4b6fd48bf77f4251a12160c92207cbc', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_xpub_p2wpkh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1698,7 +1668,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('ee76c0c6da87f0eb5ab4d1ae05d3942512dcd3c4c42518f9d3619e74400cfc1f', tx.txid()) self.assertEqual('484e350beaa722a744bb3e2aa38de005baa8526d86536d6143e5814355acf775', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_offline_signing_beyond_gap_limit(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1745,7 +1714,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('ee76c0c6da87f0eb5ab4d1ae05d3942512dcd3c4c42518f9d3619e74400cfc1f', tx.txid()) self.assertEqual('484e350beaa722a744bb3e2aa38de005baa8526d86536d6143e5814355acf775', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_wif_online_addr_p2pkh(self, mock_save_db): # compressed pubkey wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config) @@ -1784,7 +1752,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid()) self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config) @@ -1823,7 +1790,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid()) self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_wif_online_addr_p2wpkh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config) @@ -1862,7 +1828,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid()) self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_addr_p2pkh(self, mock_save_db): # compressed pubkey wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1905,7 +1870,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid()) self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1948,7 +1912,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid()) self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_save_db): wallet_offline = WalletIntegrityHelper.create_standard_wallet( @@ -1991,7 +1954,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid()) self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_hd_multisig_online_addr_p2sh(self, mock_save_db): # 2-of-3 legacy p2sh multisig @@ -2058,7 +2020,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('0e8fdc8257a85ebe7eeab14a53c2c258c61a511f64176b7f8fc016bc2263d307', tx.txid()) self.assertEqual('0e8fdc8257a85ebe7eeab14a53c2c258c61a511f64176b7f8fc016bc2263d307', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_hd_multisig_online_addr_p2wsh_p2sh(self, mock_save_db): # 2-of-2 p2sh-embedded segwit multisig @@ -2129,7 +2090,6 @@ class TestWalletOfflineSigning(TestCaseForTestnet): self.assertEqual('6a58a51591142429203b62b6ddf6b799a6926882efac229998c51bee6c3573eb', tx.txid()) self.assertEqual('96d0bca1001778c54e4c3a07929fab5562c5b5a23fd1ca3aa3870cc5df2bf97d', tx.wtxid()) - @needs_test_with_all_ecc_implementations @mock.patch.object(wallet.Abstract_Wallet, 'save_db') def test_sending_offline_hd_multisig_online_addr_p2wsh(self, mock_save_db): # 2-of-3 p2wsh multisig diff --git a/electrum/util.py b/electrum/util.py @@ -47,6 +47,7 @@ from aiohttp_socks import SocksConnector, SocksVer from aiorpcx import TaskGroup import certifi import dns.resolver +import ecdsa from .i18n import _ from .logging import get_logger, Logger @@ -1266,3 +1267,9 @@ def resolve_dns_srv(host: str): 'port': srv.port, } return [dict_from_srv_record(srv) for srv in srv_records] + + +def randrange(bound: int) -> int: + """Return a random integer k such that 1 <= k < bound, uniformly + distributed across that range.""" + return ecdsa.util.randrange(bound) diff --git a/electrum/wallet.py b/electrum/wallet.py @@ -70,7 +70,6 @@ from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL, from .util import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT from .contacts import Contacts from .interface import NetworkException -from .ecc_fast import is_using_fast_ecc from .mnemonic import Mnemonic from .logging import get_logger from .lnworker import LNWallet @@ -270,9 +269,6 @@ class Abstract_Wallet(AddressSynchronizer, ABC): def init_lightning(self): if self.db.get('lightning_privkey2'): return - if not is_using_fast_ecc(): - raise Exception('libsecp256k1 library not available. ' - 'Verifying Lightning channels is too computationally expensive without libsecp256k1, aborting.') # TODO derive this deterministically from wallet.keystore at keystore generation time # probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ ) seed = os.urandom(32) @@ -2085,9 +2081,6 @@ class Deterministic_Wallet(Abstract_Wallet): @profiler def try_detecting_internal_addresses_corruption(self): - if not is_using_fast_ecc(): - self.logger.info("internal address corruption test skipped due to missing libsecp256k1") - return addresses_all = self.get_addresses() # sample 1: first few addresses_sample1 = addresses_all[:10] diff --git a/electrum/x509.py b/electrum/x509.py @@ -27,8 +27,6 @@ import hashlib import time from datetime import datetime -import ecdsa - from . import util from .util import profiler, bh2u from .logging import get_logger @@ -250,8 +248,8 @@ class X509(object): exponent = spk.next_node(modulus) rsa_n = spk.get_value_of_type(modulus, 'INTEGER') rsa_e = spk.get_value_of_type(exponent, 'INTEGER') - self.modulus = ecdsa.util.string_to_number(rsa_n) - self.exponent = ecdsa.util.string_to_number(rsa_e) + self.modulus = int.from_bytes(rsa_n, byteorder='big', signed=False) + self.exponent = int.from_bytes(rsa_e, byteorder='big', signed=False) else: subject_public_key = der.next_node(public_key_algo) spk = der.get_value_of_type(subject_public_key, 'BIT STRING')