From 004acb906d33ea3a13c87b7b05c67a33e1d3e3d9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 4 Feb 2020 18:17:12 +0100 Subject: [PATCH 01/11] ecc: abstract away some usage of python-ecdsa: randrange --- electrum/daemon.py | 9 ++++----- electrum/ecc.py | 8 ++++---- electrum/mnemonic.py | 6 ++---- electrum/tests/test_bitcoin.py | 4 ++-- electrum/util.py | 7 +++++++ 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index ab9bb67f1..65e95e9c1 100644 --- 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/ecc.py b/electrum/ecc.py index 9fd0154bb..e7f27c05e 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -35,7 +35,7 @@ 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 @@ -145,7 +145,7 @@ class _MyVerifyingKey(ecdsa.VerifyingKey): G = curve.generator order = G.order() # extract r,s from signature - r, s = util.sigdecode_string(sig, order) + r, s = get_r_and_s_from_sig_string(sig, order) # 1.1 x = r + (recid//2) * order # 1.3 @@ -299,7 +299,7 @@ class ECPubkey(object): 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) + verifying_key.verify_digest(sig_string, msg_hash, sigdecode=get_r_and_s_from_sig_string) def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes: """ @@ -416,7 +416,7 @@ class ECPrivkey(ECPubkey): @classmethod def generate_random_key(cls): - randint = ecdsa.util.randrange(CURVE_ORDER) + randint = randrange(CURVE_ORDER) ephemeral_exponent = number_to_string(randint, CURVE_ORDER) return ECPrivkey(ephemeral_exponent) diff --git a/electrum/mnemonic.py b/electrum/mnemonic.py index 6acc427cd..ea37d0a2c 100644 --- 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/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index dac854eea..d182372c4 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -16,7 +16,7 @@ from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath, 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 @@ -103,7 +103,7 @@ class Test_bitcoin(ElectrumTestCase): def _do_test_crypto(self, message): 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) diff --git a/electrum/util.py b/electrum/util.py index a2a80b90f..9555be489 100644 --- 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) From 2cf213552822b0feda4775ed94719679bcb1efb4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 4 Feb 2020 19:41:06 +0100 Subject: [PATCH 02/11] ecc: abstract away some usage of python-ecdsa: bytes<->int conversions --- electrum/bip32.py | 2 +- electrum/dnssec.py | 14 +++++++------- electrum/ecc.py | 13 ++++++++----- electrum/keystore.py | 4 ++-- electrum/lnutil.py | 2 +- electrum/tests/test_bitcoin.py | 3 +-- electrum/x509.py | 6 ++---- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/electrum/bip32.py b/electrum/bip32.py index 4f9a82041..d37386632 100644 --- 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/dnssec.py b/electrum/dnssec.py index 77671eb23..12fe3224a 100644 --- 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 index e7f27c05e..a513f00a3 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -33,7 +33,6 @@ 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, randrange from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) @@ -57,6 +56,10 @@ def point_at_infinity(): return ECPubkey(None) +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, order=CURVE_ORDER) -> bytes: r, s = ecdsa.util.sigdecode_der(der_sig, order) return ecdsa.util.sigencode_string(r, s, order) @@ -392,7 +395,7 @@ class ECPrivkey(ECPubkey): @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 +411,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): @@ -417,11 +420,11 @@ class ECPrivkey(ECPubkey): @classmethod def generate_random_key(cls): randint = randrange(CURVE_ORDER) - ephemeral_exponent = number_to_string(randint, 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: if sigencode is None: diff --git a/electrum/keystore.py b/electrum/keystore.py index ffe880671..05305dd63 100644 --- 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, @@ -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/lnutil.py b/electrum/lnutil.py index 38676a935..3390655ed 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -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/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index d182372c4..c4a90795d 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -15,7 +15,6 @@ 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, randrange from electrum.storage import WalletStorage from electrum.keystore import xtype_from_derivation @@ -111,7 +110,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) diff --git a/electrum/x509.py b/electrum/x509.py index c38edc77a..78ae26cf0 100644 --- 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') From ad408ea832c55458b730945832c826894dfa9386 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 5 Feb 2020 21:47:22 +0100 Subject: [PATCH 03/11] ecc: use libsecp256k1 for sign/verify/mul/add --- electrum/ecc.py | 230 ++++++++++++++++++++++++++----------------- electrum/ecc_fast.py | 168 +------------------------------ 2 files changed, 141 insertions(+), 257 deletions(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index a513f00a3..e459512a3 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -30,20 +30,25 @@ import copy from typing import Union, Tuple, Optional import ecdsa -from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 +from ecdsa.ecdsa import generator_secp256k1 from ecdsa.curves import SECP256k1 from ecdsa.ellipticcurve import Point 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 +# TODO -->>> +import ctypes +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 +) +from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED +# TODO <<<-- _logger = get_logger(__name__) -do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() CURVE_ORDER = SECP256k1.order @@ -60,7 +65,7 @@ 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, order=CURVE_ORDER) -> bytes: +def sig_string_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> bytes: # TODO use libsecp? r, s = ecdsa.util.sigdecode_der(der_sig, order) return ecdsa.util.sigencode_string(r, s, order) @@ -88,7 +93,7 @@ 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]: +def point_to_ser(point, compressed=True) -> Optional[bytes]: # TODO rm? if isinstance(point, tuple): assert len(point) == 2, f'unexpected point: {point}' x, y = point @@ -101,37 +106,22 @@ def point_to_ser(point, compressed=True) -> Optional[bytes]: 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 _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): @@ -182,23 +172,18 @@ class _MySigningKey(ecdsa.SigningKey): 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') @@ -209,9 +194,9 @@ class ECPubkey(object): return ECPubkey.from_point(ecdsa_point) @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") @@ -224,10 +209,17 @@ class ECPubkey(object): return cls.from_sig_string(sig[1:], recid, msg_hash), compressed @classmethod - def from_point(cls, point): + def from_point(cls, point) -> 'ECPubkey': _bytes = point_to_ser(point, compressed=False) # faster than compressed return ECPubkey(_bytes) + @classmethod + 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) @@ -236,16 +228,49 @@ class ECPubkey(object): 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"" return f"" 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 @@ -253,38 +278,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) @@ -296,13 +319,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=get_r_and_s_from_sig_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: """ @@ -391,7 +424,7 @@ class ECPrivkey(ECPubkey): self.secret_scalar = secret point = generator_secp256k1 * secret - super().__init__(point_to_ser(point)) + super().__init__(point_to_ser(point)) # TODO @classmethod def from_secret_scalar(cls, secret_scalar: int): @@ -426,24 +459,40 @@ class ECPrivkey(ECPubkey): def get_secret_bytes(self) -> bytes: 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, sigdecode=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) + extra_entropy = counter.to_bytes(32, byteorder="little") + r, s = sign_with_extra_entropy(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.') + # public_key = private_key.get_verifying_key() # TODO + # if not public_key.verify_digest(sig, data, sigdecode=sigdecode): + # raise Exception('Sanity check verifying our own signature failed.') return sig def sign_transaction(self, hashed_preimage: bytes) -> bytes: @@ -482,12 +531,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 index a96424d70..3fb63b3c1 100644 --- 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 @@ -89,8 +86,8 @@ def load_library(): secp256k1.secp256k1_ec_pubkey_combine.restype = c_int secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) - r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) - if r: + ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) + if ret: return secp256k1 else: _logger.warning('secp256k1_context_randomize failed') @@ -100,165 +97,8 @@ def load_library(): 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 True # TODO rm try: @@ -266,5 +106,3 @@ try: except BaseException as e: _logger.warning(f'failed to load libsecp256k1: {repr(e)}') _libsecp256k1 = None - -_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() From ab0c70e2918778a44a33089ab86729c6b25a9556 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 6 Feb 2020 19:13:21 +0100 Subject: [PATCH 04/11] ecc: use libsecp256k1 for signature conversions (instead of python-ecdsa) --- electrum/ecc.py | 59 +++++++++++++++++++++++++++++++++++++------- electrum/ecc_fast.py | 6 +++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index e459512a3..5531309de 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -65,32 +65,73 @@ 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, order=CURVE_ORDER) -> bytes: # TODO use libsecp? - r, s = ecdsa.util.sigdecode_der(der_sig, order) - return ecdsa.util.sigencode_string(r, s, order) +def sig_string_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> 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, order=CURVE_ORDER) -> bytes: - r, s = ecdsa.util.sigdecode_string(sig_string, order) - return ecdsa.util.sigencode_der_canonize(r, s, order) + 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, order=CURVE_ORDER) -> bytes: - return ecdsa.util.sigencode_der_canonize(r, s, order) + 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, order=CURVE_ORDER) -> Tuple[int, int]: - r, s = ecdsa.util.sigdecode_der(der_sig, order) + 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) + 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) + 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 point_to_ser(point, compressed=True) -> Optional[bytes]: # TODO rm? diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index 3fb63b3c1..ee7b3d40a 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -79,6 +79,12 @@ 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 From 288d793893e65466ae60bf8b60963b58b849d48e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 6 Feb 2020 20:44:46 +0100 Subject: [PATCH 05/11] ecc: use libsecp256k1 for pubkey recovery (from sig and msg) --- electrum/ecc.py | 124 +++++++++++++------------------------------ electrum/ecc_fast.py | 6 +++ electrum/msqr.py | 94 -------------------------------- 3 files changed, 42 insertions(+), 182 deletions(-) delete mode 100644 electrum/msqr.py diff --git a/electrum/ecc.py b/electrum/ecc.py index 5531309de..b05d165c8 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -29,11 +29,6 @@ import functools import copy from typing import Union, Tuple, Optional -import ecdsa -from ecdsa.ecdsa import generator_secp256k1 -from ecdsa.curves import SECP256k1 -from ecdsa.ellipticcurve import Point - 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 . import constants @@ -50,11 +45,9 @@ from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED _logger = get_logger(__name__) -CURVE_ORDER = SECP256k1.order - def generator(): - return ECPubkey.from_point(generator_secp256k1) + return GENERATOR def point_at_infinity(): @@ -65,17 +58,17 @@ 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, order=CURVE_ORDER) -> bytes: +def sig_string_from_der_sig(der_sig: bytes, order=None) -> 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, order=CURVE_ORDER) -> bytes: +def der_sig_from_sig_string(sig_string: bytes, order=None) -> 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, order=CURVE_ORDER) -> bytes: +def der_sig_from_r_and_s(r: int, s: int, order=None) -> bytes: sig_string = (int.to_bytes(r, length=32, byteorder="big") + int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) @@ -92,7 +85,7 @@ def der_sig_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes: return bytes(der_sig)[:der_sig_size] -def get_r_and_s_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> Tuple[int, int]: +def get_r_and_s_from_der_sig(der_sig: bytes, order=None) -> 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)) @@ -106,7 +99,7 @@ def get_r_and_s_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> Tuple[int, in return r, s -def get_r_and_s_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> Tuple[int, int]: +def get_r_and_s_from_sig_string(sig_string: bytes, order=None) -> 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) @@ -121,7 +114,7 @@ def get_r_and_s_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> Tuple[i return r, s -def sig_string_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes: +def sig_string_from_r_and_s(r: int, s: int, order=None) -> bytes: sig_string = (int.to_bytes(r, length=32, byteorder="big") + int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) @@ -134,19 +127,6 @@ def sig_string_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes: return bytes(compact_signature) -def point_to_ser(point, compressed=True) -> Optional[bytes]: # TODO rm? - 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 _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]: pubkey_ptr = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( @@ -169,50 +149,6 @@ 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 = get_r_and_s_from_sig_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 - - @functools.total_ordering class ECPubkey(object): @@ -227,12 +163,19 @@ class ECPubkey(object): 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) -> Tuple['ECPubkey', bool]: @@ -249,11 +192,6 @@ class ECPubkey(object): recid = nV - 27 return cls.from_sig_string(sig[1:], recid, msg_hash), compressed - @classmethod - def from_point(cls, point) -> 'ECPubkey': - _bytes = point_to_ser(point, compressed=False) # faster than compressed - return ECPubkey(_bytes) - @classmethod def from_x_and_y(cls, x: int, y: int) -> 'ECPubkey': _bytes = (b'\x04' @@ -263,7 +201,14 @@ class ECPubkey(object): 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)) @@ -411,6 +356,11 @@ class ECPubkey(object): return False +GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')) +CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 + + def msg_magic(message: bytes) -> bytes: from .bitcoin import var_int length = bfh(var_int(len(message))) @@ -464,8 +414,8 @@ 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)) # TODO + pubkey = generator() * secret + super().__init__(pubkey.get_public_key_bytes(compressed=False)) @classmethod def from_secret_scalar(cls, secret_scalar: int): @@ -505,8 +455,6 @@ class ECPrivkey(ECPubkey): 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 privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") nonce_function = None @@ -530,10 +478,10 @@ class ECPrivkey(ECPubkey): 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, CURVE_ORDER) - # public_key = private_key.get_verifying_key() # TODO - # if not public_key.verify_digest(sig, data, sigdecode=sigdecode): - # raise Exception('Sanity check verifying our own signature failed.') return sig def sign_transaction(self, hashed_preimage: bytes) -> bytes: diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index ee7b3d40a..d886494e1 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -91,6 +91,12 @@ def load_library(): 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 + 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 + secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) if ret: diff --git a/electrum/msqr.py b/electrum/msqr.py deleted file mode 100644 index 76629fc68..000000000 --- a/electrum/msqr.py +++ /dev/null @@ -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 From 0a5ad9fda472eac258d7a56450a06c92be292c6e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 6 Feb 2020 20:59:57 +0100 Subject: [PATCH 06/11] ecc: small API clean-up --- electrum/ecc.py | 56 ++++++++++++---------------------- electrum/ecc_fast.py | 4 --- electrum/keystore.py | 2 +- electrum/lnpeer.py | 6 ++-- electrum/lnutil.py | 2 +- electrum/lnworker.py | 2 -- electrum/tests/test_bitcoin.py | 6 ++-- electrum/wallet.py | 7 ----- 8 files changed, 28 insertions(+), 57 deletions(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index b05d165c8..4dabe5629 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -26,49 +26,36 @@ 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 +) 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 . import constants from .logging import get_logger - -# TODO -->>> -import ctypes -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 -) from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED -# TODO <<<-- _logger = get_logger(__name__) -def generator(): - return GENERATOR - - -def point_at_infinity(): - return ECPubkey(None) - - 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, order=None) -> bytes: +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, order=None) -> bytes: +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, order=None) -> bytes: +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) @@ -85,7 +72,7 @@ def der_sig_from_r_and_s(r: int, s: int, order=None) -> bytes: return bytes(der_sig)[:der_sig_size] -def get_r_and_s_from_der_sig(der_sig: bytes, order=None) -> Tuple[int, int]: +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)) @@ -99,7 +86,7 @@ def get_r_and_s_from_der_sig(der_sig: bytes, order=None) -> Tuple[int, int]: return r, s -def get_r_and_s_from_sig_string(sig_string: bytes, order=None) -> Tuple[int, int]: +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) @@ -114,7 +101,7 @@ def get_r_and_s_from_sig_string(sig_string: bytes, order=None) -> Tuple[int, int return r, s -def sig_string_from_r_and_s(r: int, s: int, order=None) -> bytes: +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) @@ -250,12 +237,12 @@ class ECPubkey(object): other %= CURVE_ORDER if self.is_at_infinity() or other == 0: - return point_at_infinity() + 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 POINT_AT_INFINITY return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) def __rmul__(self, other: int): @@ -276,7 +263,7 @@ class ECPubkey(object): 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 POINT_AT_INFINITY return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum) def __eq__(self, other) -> bool: @@ -345,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): @@ -359,6 +346,7 @@ class ECPubkey(object): 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: @@ -414,7 +402,7 @@ class ECPrivkey(ECPubkey): raise InvalidECPointException('Invalid secret scalar (not within curve order)') self.secret_scalar = secret - pubkey = generator() * secret + pubkey = GENERATOR * secret super().__init__(pubkey.get_public_key_bytes(compressed=False)) @classmethod @@ -450,7 +438,7 @@ class ECPrivkey(ECPubkey): def get_secret_bytes(self) -> bytes: return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False) - def sign(self, msg_hash: 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: @@ -481,13 +469,11 @@ class ECPrivkey(ECPubkey): sig_string = sig_string_from_r_and_s(r, s) self.verify_message_hash(sig_string, msg_hash) - sig = sigencode(r, s, CURVE_ORDER) + 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): @@ -503,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 diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index d886494e1..4f6149375 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -109,10 +109,6 @@ def load_library(): return None -def is_using_fast_ecc(): - return True # TODO rm - - try: _libsecp256k1 = load_library() except BaseException as e: diff --git a/electrum/keystore.py b/electrum/keystore.py index 05305dd63..0f940333b 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -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) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index e04c31a55..1a0855878 100644 --- 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 index 3390655ed..4ef9a747b 100644 --- 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: diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 81eb6d3b4..29093cc78 100644 --- 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/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index c4a90795d..8f7eba196 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -100,7 +100,7 @@ class Test_bitcoin(ElectrumTestCase): self._do_test_crypto(message) def _do_test_crypto(self, message): - G = ecc.generator() + G = ecc.GENERATOR _r = G.order() pvk = randrange(_r) @@ -128,11 +128,11 @@ class Test_bitcoin(ElectrumTestCase): @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) diff --git a/electrum/wallet.py b/electrum/wallet.py index 3b740867e..963722f70 100644 --- 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] From de1ca27d636d2a00d910e4038f3299de4ab72300 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 6 Feb 2020 21:08:37 +0100 Subject: [PATCH 07/11] tests: rm "needs_test_with_all_ecc_implementations" decorator now libsecp256k1 is the only implementation --- electrum/tests/test_bitcoin.py | 46 -------------------------- electrum/tests/test_dnssec.py | 2 -- electrum/tests/test_transaction.py | 3 -- electrum/tests/test_wallet_vertical.py | 40 ---------------------- 4 files changed, 91 deletions(-) diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py index 8f7eba196..c942a027f 100644 --- a/electrum/tests/test_bitcoin.py +++ b/electrum/tests/test_bitcoin.py @@ -32,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. @@ -94,7 +69,6 @@ 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) @@ -126,7 +100,6 @@ 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 n = G.order() @@ -154,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' @@ -184,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==')) @@ -192,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 = [ @@ -206,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')) @@ -422,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") @@ -433,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'] @@ -448,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'] @@ -679,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']) @@ -688,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'] @@ -720,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'])) @@ -729,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 index e6c51900b..f45e80f41 100644 --- 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 index c350125c3..8ec291e17 100644 --- 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 index c2d6f5a0b..c93efb46e 100644 --- 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 From 1d72585b7d660e35502fabf4853ab307b7e75a5b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 6 Feb 2020 21:31:58 +0100 Subject: [PATCH 08/11] ecc: hard fail if libsecp256k1 is not found/usable --- electrum/ecc_fast.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index 4f6149375..6b3d470ff 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -33,6 +33,9 @@ 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' @@ -45,7 +48,7 @@ def load_library(): secp256k1 = ctypes.cdll.LoadLibrary(library_path) if not secp256k1: - _logger.warning('libsecp256k1 library failed to load') + _logger.error('libsecp256k1 library failed to load') return None try: @@ -91,26 +94,36 @@ def load_library(): 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 - secp256k1.secp256k1_ecdsa_recover.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_recover.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 + 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) ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) - if ret: - return secp256k1 - else: - _logger.warning('secp256k1_context_randomize failed') + 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 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)}') + + +if _libsecp256k1 is None: + # hard fail: + sys.exit(f"Error: Failed to load libsecp256k1.") From 4cec098d2dcd9a2f85eb3fb88f913e3990e5cd74 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 10 Feb 2020 19:20:23 +0100 Subject: [PATCH 09/11] build: create a standalone build script for libsecp256k1 heavily based on Electron-Cash/Electron-Cash@eda015908e9d6ea9a0adfbda9db55b929c0926ba --- MANIFEST.in | 3 ++ contrib/build-linux/appimage/build.sh | 27 ++---------- contrib/build-wine/build-electrum-git.sh | 1 - contrib/build-wine/build-secp256k1.sh | 56 ------------------------ contrib/build-wine/build.sh | 15 +++++-- contrib/build-wine/deterministic.spec | 2 +- contrib/build-wine/prepare-wine.sh | 5 +-- contrib/build_tools_util.sh | 55 +++++++++++++++++++++++ contrib/make_libsecp256k1.sh | 49 +++++++++++++++++++++ contrib/osx/make_osx | 16 +++---- electrum/ecc_fast.py | 22 +++++++--- 11 files changed, 146 insertions(+), 105 deletions(-) delete mode 100755 contrib/build-wine/build-secp256k1.sh create mode 100755 contrib/make_libsecp256k1.sh diff --git a/MANIFEST.in b/MANIFEST.in index 9a0bfbdc6..f39e32025 100644 --- 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/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh index 585add685..f69512d69 100755 --- 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 index b810e2c26..82c5c0d67 100755 --- 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 deleted file mode 100755 index 8c7f1c40e..000000000 --- a/contrib/build-wine/build-secp256k1.sh +++ /dev/null @@ -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 index 8b79402fd..358941eec 100755 --- 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 index ce8fcb73c..79d86067d 100644 --- 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 index 666aadecb..555c657e9 100755 --- 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 index 9cba7cb4f..5210f0f3a 100755 --- a/contrib/build_tools_util.sh +++ b/contrib/build_tools_util.sh @@ -70,3 +70,58 @@ 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 +} + + + +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 new file mode 100755 index 000000000..06d63cb9e --- /dev/null +++ b/contrib/make_libsecp256k1.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7" + +set -e + +here=$(dirname $(realpath "$0" 2> /dev/null || grealpath "$0")) +CONTRIB="$here" +PROJECT_ROOT="$CONTRIB/.." + +. "$here"/build_tools_util.sh || (echo "Could not source build_tools_util.sh" && exit 1) + +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/make_osx b/contrib/osx/make_osx index defb6a5e3..a5f5a5802 100755 --- 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/ecc_fast.py b/electrum/ecc_fast.py index 6b3d470ff..5e2411f64 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -38,15 +38,25 @@ 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' + 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 = ctypes.cdll.LoadLibrary(library_path) + secp256k1 = None + for libpath in library_paths: + try: + secp256k1 = ctypes.cdll.LoadLibrary(libpath) + except: + pass + else: + break if not secp256k1: _logger.error('libsecp256k1 library failed to load') return None From 5b84e714f25ce90846b7a69b74e7516b8e94ac6a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 11 Feb 2020 16:06:32 +0100 Subject: [PATCH 10/11] build: workaround for 'realpath' missing on macOS --- contrib/build_tools_util.sh | 6 ++++++ contrib/make_libsecp256k1.sh | 4 ++-- contrib/osx/base.sh | 4 ---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/contrib/build_tools_util.sh b/contrib/build_tools_util.sh index 5210f0f3a..be25fd2f6 100755 --- a/contrib/build_tools_util.sh +++ b/contrib/build_tools_util.sh @@ -107,6 +107,12 @@ function host_strip() 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 diff --git a/contrib/make_libsecp256k1.sh b/contrib/make_libsecp256k1.sh index 06d63cb9e..8b602f34f 100755 --- a/contrib/make_libsecp256k1.sh +++ b/contrib/make_libsecp256k1.sh @@ -4,12 +4,12 @@ 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/.." -. "$here"/build_tools_util.sh || (echo "Could not source build_tools_util.sh" && exit 1) - pkgname="secp256k1" info "Building $pkgname..." diff --git a/contrib/osx/base.sh b/contrib/osx/base.sh index c11e270a4..c2e3527c0 100644 --- 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#./}" -} From f78589ec773702b2673095f5448acebcfa6ff278 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 11 Feb 2020 17:33:06 +0100 Subject: [PATCH 11/11] update README to mention libsecp --- README.rst | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index f1343407e..2a340dbd4 100644 --- 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::