From 3721f04ac8a131bd2297e4cdc5b6092b564c67c1 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 17 Jun 2024 13:38:54 +0200 Subject: [PATCH 1/6] replace electrum/ecc with electrum_ecc package --- contrib/requirements/requirements.txt | 1 + electrum/bip32.py | 3 +- electrum/bitcoin.py | 5 +- electrum/channel_db.py | 4 +- electrum/commands.py | 6 +- electrum/crypto.py | 3 +- electrum/descriptor.py | 3 +- electrum/ecc.py | 553 -------------------- electrum/ecc_fast.py | 194 ------- electrum/gui/qt/main_window.py | 4 +- electrum/gui/qt/new_channel_dialog.py | 4 +- electrum/keystore.py | 6 +- electrum/lnaddr.py | 3 +- electrum/lnchannel.py | 5 +- electrum/lnonion.py | 3 +- electrum/lnpeer.py | 5 +- electrum/lnsweep.py | 3 +- electrum/lntransport.py | 3 +- electrum/lnutil.py | 5 +- electrum/lnverifier.py | 4 +- electrum/lnworker.py | 2 +- electrum/paymentrequest.py | 3 +- electrum/plugins/cosigner_pool/qt.py | 3 +- electrum/plugins/ledger/ledger.py | 4 +- electrum/plugins/trezor/clientbase.py | 3 +- electrum/plugins/trustedcoin/trustedcoin.py | 4 +- electrum/storage.py | 4 +- electrum/submarine_swaps.py | 2 +- electrum/transaction.py | 4 +- electrum/wallet.py | 3 +- tests/test_bitcoin.py | 8 +- tests/test_descriptor.py | 3 +- tests/test_ecc.py | 8 +- tests/test_lnpeer.py | 2 +- tests/test_lntransport.py | 3 +- tests/test_lnutil.py | 3 +- tests/test_transaction.py | 3 +- 37 files changed, 84 insertions(+), 795 deletions(-) delete mode 100644 electrum/ecc.py delete mode 100644 electrum/ecc_fast.py diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 2c85276bf..8ccf307b3 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -7,6 +7,7 @@ aiohttp_socks>=0.8.4 certifi attrs>=20.1.0 jsonpatch +electrum_ecc # Note that we also need the dnspython[DNSSEC] extra which pulls in cryptography, # but as that is not pure-python it cannot be listed in this file! diff --git a/electrum/bip32.py b/electrum/bip32.py index 834b64155..d03792e5e 100644 --- a/electrum/bip32.py +++ b/electrum/bip32.py @@ -7,9 +7,10 @@ import hashlib import struct from typing import List, Tuple, NamedTuple, Union, Iterable, Sequence, Optional +import electrum_ecc as ecc + from .util import bfh, BitcoinException from . import constants -from . import ecc from .crypto import hash_160, hmac_oneshot from .bitcoin import EncodeBase58Check, DecodeBase58Check from .logging import get_logger diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index dd4944a5f..4a84a850e 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -28,11 +28,12 @@ from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence, Any import enum from enum import IntEnum, Enum +import electrum_ecc as ecc + from .util import bfh, BitcoinException, assert_bytes, to_bytes, inv_dict, is_hex_str, classproperty from . import version from . import segwit_addr from . import constants -from . import ecc from .crypto import sha256d, sha256, hash_160, hmac_oneshot if TYPE_CHECKING: @@ -854,7 +855,7 @@ def ecdsa_sign_usermessage(ec_privkey, message: Union[bytes, str], *, is_compres return ec_privkey.ecdsa_sign_recoverable(msg32, is_compressed=is_compressed) def verify_usermessage_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool: - from .ecc import ECPubkey + from electrum_ecc import ECPubkey assert_bytes(sig65, message) if net is None: net = constants.net h = sha256d(usermessage_magic(message)) diff --git a/electrum/channel_db.py b/electrum/channel_db.py index 7b10d3e4c..94fc622d5 100644 --- a/electrum/channel_db.py +++ b/electrum/channel_db.py @@ -36,6 +36,8 @@ from enum import IntEnum import functools from aiorpcx import NetAddress +import electrum_ecc as ecc +from electrum_ecc import ECPubkey from .sql_db import SqlDB, sql from . import constants, util @@ -45,8 +47,6 @@ from .lnutil import (LNPeerAddr, format_short_channel_id, ShortChannelID, validate_features, IncompatibleOrInsaneFeatures, InvalidGossipMsg) from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update from .lnmsg import decode_msg -from . import ecc -from .ecc import ECPubkey from .crypto import sha256d from .lnmsg import FailedToParseMsg diff --git a/electrum/commands.py b/electrum/commands.py index 8663536d3..25a49c176 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -40,7 +40,9 @@ from decimal import Decimal, InvalidOperation from typing import Optional, TYPE_CHECKING, Dict, List import os -from . import util, ecc +import electrum_ecc as ecc + +from . import util from . import keystore from .util import (bfh, format_satoshis, json_decode, json_normalize, is_hash256_str, is_hex_str, to_bytes, parse_max_spend, to_decimal, @@ -627,7 +629,7 @@ class Commands: # Add shared libs (.so/.dll), and non-pure-python dependencies. # Such deps can be installed in various ways - often via the Linux distro's pkg manager, # instead of using pip, hence it is useful to list them for debugging. - from . import ecc_fast + from electrum_ecc import ecc_fast ret.update(ecc_fast.version_info()) from . import qrscanner ret.update(qrscanner.version_info()) diff --git a/electrum/crypto.py b/electrum/crypto.py index 53c1a5d78..3b0e3100c 100644 --- a/electrum/crypto.py +++ b/electrum/crypto.py @@ -31,10 +31,11 @@ import hashlib import hmac from typing import Union, Mapping, Optional +import electrum_ecc as ecc + from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException, versiontuple from .i18n import _ from .logging import get_logger -from . import ecc _logger = get_logger(__name__) diff --git a/electrum/descriptor.py b/electrum/descriptor.py index 6abf5bacc..3a278c45e 100644 --- a/electrum/descriptor.py +++ b/electrum/descriptor.py @@ -28,12 +28,13 @@ from typing import ( Union, ) +import electrum_ecc as ecc + from .bip32 import convert_bip32_strpath_to_intpath, BIP32Node, KeyOriginInfo, BIP32_PRIME from . import bitcoin from .bitcoin import construct_script, opcodes, construct_witness, taproot_output_script from . import constants from .crypto import hash_160, sha256 -from . import ecc from . import segwit_addr diff --git a/electrum/ecc.py b/electrum/ecc.py deleted file mode 100644 index f7a0b5b0f..000000000 --- a/electrum/ecc.py +++ /dev/null @@ -1,553 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2018-2024 The Electrum developers -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import base64 -import hashlib -import functools -import secrets -from typing import Union, Tuple, Optional -from ctypes import ( - byref, c_char_p, c_size_t, create_string_buffer, cast, -) - -from . import ecc_fast -from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED, LibModuleMissing - -def assert_bytes(x): - assert isinstance(x, (bytes, bytearray)) - -# Some unit tests need to create ECDSA sigs without grinding the R value (and just use RFC6979). -# see https://github.com/bitcoin/bitcoin/pull/13666 -ENABLE_ECDSA_R_VALUE_GRINDING = True - - -def string_to_number(b: bytes) -> int: - return int.from_bytes(b, byteorder='big', signed=False) - - -def ecdsa_sig64_from_der_sig(der_sig: bytes) -> bytes: - r, s = get_r_and_s_from_ecdsa_der_sig(der_sig) - return ecdsa_sig64_from_r_and_s(r, s) - - -def ecdsa_der_sig_from_ecdsa_sig64(sig64: bytes) -> bytes: - r, s = get_r_and_s_from_ecdsa_sig64(sig64) - return ecdsa_der_sig_from_r_and_s(r, s) - - -def ecdsa_der_sig_from_r_and_s(r: int, s: int) -> bytes: - sig64 = ( - 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, sig64) - if 1 != 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 1 != 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_ecdsa_der_sig(der_sig: bytes) -> Tuple[int, int]: - assert isinstance(der_sig, bytes) - sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig)) - if 1 != 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_ecdsa_sig64(sig64: bytes) -> Tuple[int, int]: - if not (isinstance(sig64, bytes) and len(sig64) == 64): - raise Exception("sig64 must be bytes, and 64 bytes exactly") - sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) - if 1 != 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 ecdsa_sig64_from_r_and_s(r: int, s: int) -> bytes: - sig64 = ( - 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, sig64) - if 1 != ret: - raise Exception("Bad signature") - ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) - compact_signature = create_string_buffer(64) - _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) - return bytes(compact_signature) - - -def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]: - assert isinstance(pubkey, bytes), f'pubkey must be bytes, not {type(pubkey)}' - pubkey_ptr = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ec_pubkey_parse( - _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) - if 1 != ret: - raise InvalidECPointException( - f'public key could not be parsed or is invalid: {pubkey.hex()!r}') - - pubkey_serialized = create_string_buffer(65) - pubkey_size = c_size_t(65) - _libsecp256k1.secp256k1_ec_pubkey_serialize( - _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED) - pubkey_serialized = bytes(pubkey_serialized) - assert pubkey_serialized[0] == 0x04, pubkey_serialized - x = int.from_bytes(pubkey_serialized[1:33], byteorder='big', signed=False) - y = int.from_bytes(pubkey_serialized[33:65], byteorder='big', signed=False) - return x, y - - -class InvalidECPointException(Exception): - """e.g. not on curve, or infinity""" - - -@functools.total_ordering -class ECPubkey(object): - - def __init__(self, b: Optional[bytes]): - if b is not None: - assert isinstance(b, (bytes, bytearray)), f'pubkey must be bytes-like, not {type(b)}' - if isinstance(b, bytearray): - b = bytes(b) - self._x, self._y = _x_and_y_from_pubkey_bytes(b) - else: - self._x, self._y = None, None - - @classmethod - def from_ecdsa_sig64(cls, sig64: bytes, recid: int, msg32: bytes) -> 'ECPubkey': - assert_bytes(sig64) - if len(sig64) != 64: - raise Exception(f'wrong encoding used for signature? len={len(sig64)} (should be 64)') - if not (0 <= recid <= 3): - raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid)) - assert isinstance(msg32, (bytes, bytearray)), type(msg32) - assert len(msg32) == 32, len(msg32) - sig65 = create_string_buffer(65) - ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact( - _libsecp256k1.ctx, sig65, sig64, recid) - if 1 != ret: - raise Exception('failed to parse signature') - pubkey = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg32) - if 1 != ret: - raise InvalidECPointException('failed to recover public key') - return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) - - @classmethod - def from_ecdsa_sig65(cls, sig65: bytes, msg32: bytes) -> Tuple['ECPubkey', bool, Optional[str]]: - assert_bytes(sig65) - if len(sig65) != 65: - raise Exception(f'wrong encoding used for signature? len={len(sig65)} (should be 65)') - nV = sig65[0] - # as per BIP-0137: - # 27-30: p2pkh (uncompressed) - # 31-34: p2pkh (compressed) - # 35-38: p2wpkh-p2sh - # 39-42: p2wpkh - # However, the signatures we create do not respect this, and we instead always use 27-34, - # only distinguishing between compressed/uncompressed, so we treat those values as "any". - if not (27 <= nV <= 42): - raise Exception("Bad encoding") - txin_type_guess = None - compressed = True - if nV >= 39: - nV -= 12 - txin_type_guess = "p2wpkh" - elif nV >= 35: - nV -= 8 - txin_type_guess = "p2wpkh-p2sh" - elif nV >= 31: - nV -= 4 - else: - compressed = False - recid = nV - 27 - pubkey = cls.from_ecdsa_sig64(sig65[1:], recid, msg32) - return pubkey, compressed, txin_type_guess - - @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) -> bytes: - if self.is_at_infinity(): raise Exception('point is at infinity') - 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) -> str: - return self.get_public_key_bytes(compressed).hex() - - def point(self) -> Tuple[Optional[int], Optional[int]]: - x = self.x() - y = self.y() - assert (x is None) == (y is None), f"either both x and y, or neither should be None. {(x, y)=}" - return x, y - - def x(self) -> Optional[int]: - return self._x - - def y(self) -> Optional[int]: - return self._y - - def _to_libsecp256k1_pubkey_ptr(self): - """pointer to `secp256k1_pubkey` C struct""" - pubkey_ptr = create_string_buffer(64) - pk_bytes = self.get_public_key_bytes(compressed=False) - ret = _libsecp256k1.secp256k1_ec_pubkey_parse( - _libsecp256k1.ctx, pubkey_ptr, pk_bytes, len(pk_bytes)) - if 1 != ret: - raise Exception(f'public key could not be parsed or is invalid: {pk_bytes.hex()!r}') - return pubkey_ptr - - def _to_libsecp256k1_xonly_pubkey_ptr(self): - """pointer to `secp256k1_xonly_pubkey` C struct""" - if not ecc_fast.HAS_SCHNORR: - raise LibModuleMissing( - 'libsecp256k1 library found but it was built ' - 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') - pubkey_ptr = create_string_buffer(64) - pk_bytes = self.get_public_key_bytes(compressed=True)[1:] - ret = _libsecp256k1.secp256k1_xonly_pubkey_parse( - _libsecp256k1.ctx, pubkey_ptr, pk_bytes) - if 1 != ret: - raise Exception(f'public key could not be parsed or is invalid: {pk_bytes.hex()!r}') - return pubkey_ptr - - @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))) - - 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 1 != ret: - return POINT_AT_INFINITY - return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) - - def __rmul__(self, other: int): - return self * other - - def __add__(self, other): - if not isinstance(other, ECPubkey): - raise TypeError('addition not defined for ECPubkey and {}'.format(type(other))) - 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 1 != 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.point()) - - def __lt__(self, other): - if not isinstance(other, ECPubkey): - raise TypeError('comparison not defined for ECPubkey and {}'.format(type(other))) - p1 = ((self.x() or 0), (self.y() or 0)) - p2 = ((other.x() or 0), (other.y() or 0)) - return p1 < p2 - - def ecdsa_verify_recoverable(self, sig65: bytes, msg32: bytes) -> bool: - try: - public_key, _compressed, _txin_type_guess = self.from_ecdsa_sig65(sig65, msg32) - except Exception: - return False - # check public key - if public_key != self: - return False - # check message - return self.ecdsa_verify(sig65[1:], msg32) - - def ecdsa_verify( - self, - sig64: bytes, - msg32: bytes, - *, - enforce_low_s: bool = True, # policy/standardness rule - ) -> bool: - assert_bytes(sig64) - if len(sig64) != 64: - return False - if not (isinstance(msg32, bytes) and len(msg32) == 32): - return False - - sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) - if 1 != ret: - return False - if not enforce_low_s: - 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, msg32, pubkey): - return False - return True - - def schnorr_verify(self, sig64: bytes, msg32: bytes) -> bool: - assert isinstance(sig64, bytes), type(sig64) - assert len(sig64) == 64, len(sig64) - assert isinstance(msg32, bytes), type(msg32) - assert len(msg32) == 32, len(msg32) - if not ecc_fast.HAS_SCHNORR: - raise LibModuleMissing( - 'libsecp256k1 library found but it was built ' - 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') - msglen = 32 - pubkey = self._to_libsecp256k1_xonly_pubkey_ptr() - if 1 != _libsecp256k1.secp256k1_schnorrsig_verify(_libsecp256k1.ctx, sig64, msg32, msglen, pubkey): - return False - return True - - @classmethod - def order(cls) -> int: - return CURVE_ORDER - - def is_at_infinity(self) -> bool: - return self == POINT_AT_INFINITY - - @classmethod - def is_pubkey_bytes(cls, b: bytes) -> bool: - try: - ECPubkey(b) - return True - except Exception: - return False - - def has_even_y(self) -> bool: - return self.y() % 2 == 0 - - -GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' - '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8')) -CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 -POINT_AT_INFINITY = ECPubkey(None) - - - -def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool: - if isinstance(secret, bytes): - secret = string_to_number(secret) - return 0 < secret < CURVE_ORDER - - -class ECPrivkey(ECPubkey): - - def __init__(self, privkey_bytes: bytes): - assert_bytes(privkey_bytes) - if len(privkey_bytes) != 32: - raise Exception('unexpected size for secret. should be 32 bytes, not {}'.format(len(privkey_bytes))) - secret = string_to_number(privkey_bytes) - if not is_secret_within_curve_range(secret): - raise InvalidECPointException('Invalid secret scalar (not within curve order)') - self.secret_scalar = secret - - pubkey = GENERATOR * secret - super().__init__(pubkey.get_public_key_bytes(compressed=False)) - - @classmethod - def from_secret_scalar(cls, secret_scalar: int) -> 'ECPrivkey': - secret_bytes = int.to_bytes(secret_scalar, length=32, byteorder='big', signed=False) - return ECPrivkey(secret_bytes) - - @classmethod - def from_arbitrary_size_secret(cls, privkey_bytes: bytes) -> 'ECPrivkey': - """This method is only for legacy reasons. Do not introduce new code that uses it. - Unlike the default constructor, this method does not require len(privkey_bytes) == 32, - and the secret does not need to be within the curve order either. - """ - return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes)) - - @classmethod - def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes: - scalar = string_to_number(privkey_bytes) % CURVE_ORDER - if scalar == 0: - raise Exception('invalid EC private key scalar: zero') - privkey_32bytes = int.to_bytes(scalar, length=32, byteorder='big', signed=False) - return privkey_32bytes - - def __repr__(self): - return f"" - - @classmethod - def generate_random_key(cls) -> 'ECPrivkey': - randint = secrets.randbelow(CURVE_ORDER - 1) + 1 - ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False) - return ECPrivkey(ephemeral_exponent) - - def get_secret_bytes(self) -> bytes: - return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False) - - def ecdsa_sign(self, msg32: bytes, *, sigencode=None) -> bytes: - if not (isinstance(msg32, bytes) and len(msg32) == 32): - raise Exception("msg32 to be signed must be bytes, and 32 bytes exactly") - if sigencode is None: - sigencode = ecdsa_sig64_from_r_and_s - - 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, msg32, privkey_bytes, - nonce_function, extra_entropy) - if 1 != 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) - if ENABLE_ECDSA_R_VALUE_GRINDING: - 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) - - sig64 = ecdsa_sig64_from_r_and_s(r, s) - if not self.ecdsa_verify(sig64, msg32): - raise Exception("sanity check failed: signature we just created does not verify!") - - sig = sigencode(r, s) - return sig - - def schnorr_sign(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes: - """Creates a BIP-340 schnorr signature for the given message (hash) - and using the optional auxiliary random data. - - note: msg32 is supposed to be a 32 byte hash of the message to be signed. - The BIP recommends using bip340_tagged_hash for hashing the message. - """ - assert isinstance(msg32, bytes), type(msg32) - assert len(msg32) == 32, len(msg32) - if aux_rand32 is None: - aux_rand32 = bytes(32) - assert isinstance(aux_rand32, bytes), type(aux_rand32) - assert len(aux_rand32) == 32, len(aux_rand32) - if not ecc_fast.HAS_SCHNORR: - raise LibModuleMissing( - 'libsecp256k1 library found but it was built ' - 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') - # construct "keypair" obj - privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") - keypair = create_string_buffer(96) - ret = _libsecp256k1.secp256k1_keypair_create(_libsecp256k1.ctx, keypair, privkey_bytes) - if 1 != ret: - raise Exception('secret key was invalid') - # sign msg and verify sig - sig64 = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_schnorrsig_sign32( - _libsecp256k1.ctx, sig64, msg32, keypair, aux_rand32) - sig64 = bytes(sig64) - if 1 != ret: - raise Exception('signing failure') - if not self.schnorr_verify(sig64, msg32): - raise Exception("sanity check failed: signature we just created does not verify!") - return sig64 - - def ecdsa_sign_recoverable(self, msg32: bytes, *, is_compressed: bool) -> bytes: - assert len(msg32) == 32, len(msg32) - - def bruteforce_recid(sig64: bytes): - for recid in range(4): - sig65 = construct_ecdsa_sig65(sig64, recid, is_compressed=is_compressed) - if not self.ecdsa_verify_recoverable(sig65, msg32): - continue - return sig65, recid - else: - raise Exception("error: cannot sign message. no recid fits..") - - sig64 = self.ecdsa_sign(msg32, sigencode=ecdsa_sig64_from_r_and_s) - sig65, recid = bruteforce_recid(sig64) - return sig65 - - - -def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> bytes: - comp = 4 if is_compressed else 0 - return bytes([27 + recid + comp]) + sig64 - - diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py deleted file mode 100644 index b4ba9f8fe..000000000 --- a/electrum/ecc_fast.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) 2013-2018 Richard Kiss -# Copyright (c) 2018-2024 The Electrum developers -# Distributed under the MIT software license, see the accompanying -# file LICENCE or http://www.opensource.org/licenses/mit-license.php -# -# Originally based on pycoin: -# https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py - -import os -import sys -import traceback -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 .logging import get_logger - - -_logger = get_logger(__name__) - - -SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1) -SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0) -SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1) -# /** The higher bits contain the actual data. Do not use directly. */ -SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8) -SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9) -SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8) - -# /** Flags to pass to secp256k1_context_create. */ -SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) -SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) -SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT) - -SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) -SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION) - - -class LibModuleMissing(Exception): pass - - -def load_library(): - global HAS_SCHNORR - - # note: for a mapping between bitcoin-core/secp256k1 git tags and .so.V libtool version numbers, - # see https://github.com/bitcoin-core/secp256k1/pull/1055#issuecomment-1227505189 - tested_libversions = [2, 1, 0, ] # try latest version first - libnames = [] - if sys.platform == 'darwin': - for v in tested_libversions: - libnames.append(f"libsecp256k1.{v}.dylib") - elif sys.platform in ('windows', 'win32'): - for v in tested_libversions: - libnames.append(f"libsecp256k1-{v}.dll") - elif 'ANDROID_DATA' in os.environ: - libnames = ['libsecp256k1.so', ] # don't care about version number. we built w/e is available. - else: # desktop Linux and similar - for v in tested_libversions: - libnames.append(f"libsecp256k1.so.{v}") - # maybe we could fall back to trying "any" version? maybe guarded with an env var? - #libnames.append(f"libsecp256k1.so") - library_paths = [] - for libname in libnames: # try local files in repo dir first - library_paths.append(os.path.join(os.path.dirname(__file__), libname)) - for libname in libnames: - library_paths.append(libname) - - exceptions = [] - secp256k1 = None - for libpath in library_paths: - try: - secp256k1 = ctypes.cdll.LoadLibrary(libpath) - except BaseException as e: - exceptions.append(e) - else: - break - if not secp256k1: - _logger.error(f'libsecp256k1 library failed to load. exceptions: {repr(exceptions)}') - return None - - try: - secp256k1.secp256k1_context_create.argtypes = [c_uint] - secp256k1.secp256k1_context_create.restype = c_void_p - - secp256k1.secp256k1_context_randomize.argtypes = [c_void_p, c_char_p] - secp256k1.secp256k1_context_randomize.restype = c_int - - secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p] - secp256k1.secp256k1_ec_pubkey_create.restype = c_int - - secp256k1.secp256k1_ecdsa_sign.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_void_p, c_void_p] - secp256k1.secp256k1_ecdsa_sign.restype = c_int - - secp256k1.secp256k1_ecdsa_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_verify.restype = c_int - - secp256k1.secp256k1_ec_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t] - secp256k1.secp256k1_ec_pubkey_parse.restype = c_int - - secp256k1.secp256k1_ec_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p, c_uint] - secp256k1.secp256k1_ec_pubkey_serialize.restype = c_int - - secp256k1.secp256k1_ecdsa_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_signature_parse_compact.restype = c_int - - secp256k1.secp256k1_ecdsa_signature_normalize.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_signature_normalize.restype = c_int - - secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int - - secp256k1.secp256k1_ecdsa_signature_parse_der.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t] - secp256k1.secp256k1_ecdsa_signature_parse_der.restype = c_int - - secp256k1.secp256k1_ecdsa_signature_serialize_der.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p] - secp256k1.secp256k1_ecdsa_signature_serialize_der.restype = c_int - - secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int - - secp256k1.secp256k1_ec_pubkey_combine.argtypes = [c_void_p, c_char_p, c_void_p, c_size_t] - secp256k1.secp256k1_ec_pubkey_combine.restype = c_int - - # --enable-module-recovery - try: - secp256k1.secp256k1_ecdsa_recover.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] - secp256k1.secp256k1_ecdsa_recover.restype = c_int - - secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p, c_int] - secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.restype = c_int - except (OSError, AttributeError): - raise LibModuleMissing('libsecp256k1 library found but it was built ' - 'without required module (--enable-module-recovery)') - - # --enable-module-schnorrsig - try: - secp256k1.secp256k1_schnorrsig_sign32.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_char_p] - secp256k1.secp256k1_schnorrsig_sign32.restype = c_int - - secp256k1.secp256k1_schnorrsig_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p] - secp256k1.secp256k1_schnorrsig_verify.restype = c_int - except (OSError, AttributeError): - _logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-schnorrsig)") - HAS_SCHNORR = False - # raise LibModuleMissing('libsecp256k1 library found but it was built ' - # 'without required module (--enable-module-schnorrsig)') - - # --enable-module-extrakeys - try: - secp256k1.secp256k1_xonly_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_xonly_pubkey_parse.restype = c_int - - secp256k1.secp256k1_xonly_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_xonly_pubkey_serialize.restype = c_int - - secp256k1.secp256k1_keypair_create.argtypes = [c_void_p, c_char_p, c_char_p] - secp256k1.secp256k1_keypair_create.restype = c_int - except (OSError, AttributeError): - _logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-extrakeys)") - HAS_SCHNORR = False - # raise LibModuleMissing('libsecp256k1 library found but it was built ' - # 'without required module (--enable-module-extrakeys)') - - secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) - ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) - if not ret: - _logger.error('secp256k1_context_randomize failed') - return None - - 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 -HAS_SCHNORR = True -try: - _libsecp256k1 = load_library() -except BaseException as e: - _logger.error(f'failed to load libsecp256k1: {repr(e)}') - - -if _libsecp256k1 is None: - # hard fail: - raise ImportError("Failed to load libsecp256k1") - - -def version_info() -> dict: - return { - "libsecp256k1.path": _libsecp256k1._name if _libsecp256k1 else None, - } diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index ed6beef45..ba5ab98ef 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -47,9 +47,11 @@ from PyQt6.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget, QWidget, QSizePolicy, QStatusBar, QToolTip, QMenu, QToolButton) +import electrum_ecc as ecc + import electrum from electrum.gui import messages -from electrum import (keystore, ecc, constants, util, bitcoin, commands, +from electrum import (keystore, constants, util, bitcoin, commands, paymentrequest, lnutil) from electrum.bitcoin import COIN, is_address, DummyAddress from electrum.plugin import run_hook, BasePlugin diff --git a/electrum/gui/qt/new_channel_dialog.py b/electrum/gui/qt/new_channel_dialog.py index 5e98796d7..a6688d4b7 100644 --- a/electrum/gui/qt/new_channel_dialog.py +++ b/electrum/gui/qt/new_channel_dialog.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING, Optional - from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout +import electrum_ecc as ecc + from electrum.i18n import _ from electrum.transaction import PartialTxOutput, PartialTransaction from electrum.lnutil import MIN_FUNDING_SAT from electrum.lnworker import hardcoded_trampoline_nodes -from electrum import ecc from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates from electrum.gui import messages diff --git a/electrum/keystore.py b/electrum/keystore.py index a93ed5e97..f066a9185 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -32,7 +32,10 @@ from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List, from functools import lru_cache, wraps from abc import ABC, abstractmethod -from . import bitcoin, ecc, constants, bip32 +import electrum_ecc as ecc +from electrum_ecc import string_to_number + +from . import bitcoin, constants, bip32 from .bitcoin import deserialize_privkey, serialize_privkey, BaseDecodeError from .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, TxInput from .bip32 import (convert_bip32_strpath_to_intpath, BIP32_PRIME, @@ -40,7 +43,6 @@ from .bip32 import (convert_bip32_strpath_to_intpath, BIP32_PRIME, convert_bip32_intpath_to_strpath, is_xkey_consistent_with_key_origin_info, KeyOriginInfo) from .descriptor import PubkeyProvider -from .ecc import string_to_number from . import crypto from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST, SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160, diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index 5c9a07cde..2e504aa8c 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -10,12 +10,13 @@ from decimal import Decimal from typing import Optional, TYPE_CHECKING, Type, Dict, Any, Union, Sequence, List, Tuple import random +import electrum_ecc as ecc + from .bitcoin import hash160_to_b58_address, b58_address_to_hash160, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC from .segwit_addr import bech32_encode, bech32_decode, CHARSET, CHARSET_INVERSE, convertbits from . import segwit_addr from . import constants from .constants import AbstractNet -from . import ecc from .bitcoin import COIN if TYPE_CHECKING: diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 18c5156ca..b013a48b5 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -33,8 +33,9 @@ import itertools from aiorpcx import NetAddress import attr -from . import ecc -from .ecc import ECPubkey +import electrum_ecc as ecc +from electrum_ecc import ECPubkey + from . import constants, util from .util import bfh, chunks, TxMinedInfo from .invoices import PR_PAID diff --git a/electrum/lnonion.py b/electrum/lnonion.py index d30e5ad14..a1b92fc29 100644 --- a/electrum/lnonion.py +++ b/electrum/lnonion.py @@ -28,7 +28,8 @@ import hashlib from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING, Dict, Any, Optional, Union from enum import IntEnum -from . import ecc +import electrum_ecc as ecc + from .crypto import sha256, hmac_oneshot, chacha20_encrypt from .util import profiler, xor_bytes, bfh from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH, diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index f1639f75b..93c0a4c0f 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -13,13 +13,14 @@ from typing import Tuple, Dict, TYPE_CHECKING, Optional, Union, Set, Callable, A from datetime import datetime import functools +import electrum_ecc as ecc +from electrum_ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig64, ECPubkey + import aiorpcx from aiorpcx import ignore_after from .crypto import sha256, sha256d from . import bitcoin, util -from . import ecc -from .ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig64, ECPubkey from . import constants from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup, UnrelatedTransactionException, error_text_bytes_to_safe_str, AsyncHangDetector) diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index e4744468b..74825e455 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -5,11 +5,12 @@ from typing import Optional, Dict, List, Tuple, TYPE_CHECKING, NamedTuple, Callable from enum import Enum, auto +import electrum_ecc as ecc + from .util import bfh from .bitcoin import redeem_script_to_address, dust_threshold, construct_witness from .invoices import PR_PAID from . import descriptor -from . import ecc from .lnutil import (make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey, make_htlc_tx_witness, make_htlc_tx_with_open_channel, UpdateAddHtlc, diff --git a/electrum/lntransport.py b/electrum/lntransport.py index 494554406..1bf1851d5 100644 --- a/electrum/lntransport.py +++ b/electrum/lntransport.py @@ -11,10 +11,11 @@ from asyncio import StreamReader, StreamWriter from typing import Optional from functools import cached_property +import electrum_ecc as ecc + from .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt from .lnutil import (get_ecdh, privkey_to_pubkey, LightningPeerConnectionClosed, HandshakeFailed, LNPeerAddr) -from . import ecc from .util import MySocksProxy diff --git a/electrum/lnutil.py b/electrum/lnutil.py index fbf34a2b8..4d5c91bc7 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -10,6 +10,8 @@ from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Un import re import sys +import electrum_ecc as ecc +from electrum_ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number import attr from aiorpcx import NetAddress @@ -21,8 +23,7 @@ from .util import format_short_id as format_short_channel_id from .crypto import sha256, pw_decode_with_version_and_mac from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint, PartialTxOutput, opcodes, TxOutput) -from .ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number -from . import ecc, bitcoin, crypto, transaction +from . import bitcoin, crypto, transaction from . import descriptor from .bitcoin import (redeem_script_to_address, address_to_script, construct_witness, construct_script) diff --git a/electrum/lnverifier.py b/electrum/lnverifier.py index 95512a06c..7d6b22e46 100644 --- a/electrum/lnverifier.py +++ b/electrum/lnverifier.py @@ -28,10 +28,10 @@ import threading from typing import TYPE_CHECKING, Dict, Set import aiorpcx +import electrum_ecc as ecc +from electrum_ecc import ECPubkey from . import bitcoin -from . import ecc -from .ecc import ECPubkey from . import constants from .util import bfh, NetworkJobOnDefaultServer from .lnutil import funding_output_script_from_keys, ShortChannelID diff --git a/electrum/lnworker.py b/electrum/lnworker.py index d03f49eaf..8195d2d0e 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -27,6 +27,7 @@ import aiohttp import dns.resolver import dns.exception from aiorpcx import run_in_thread, NetAddress, ignore_after +from electrum_ecc import ecdsa_der_sig_from_ecdsa_sig64 from . import constants, util from . import keystore @@ -51,7 +52,6 @@ from .logging import Logger from .lntransport import LNTransport, LNResponderTransport, LNTransportBase from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnaddr import lnencode, LnAddr, lndecode -from .ecc import ecdsa_der_sig_from_ecdsa_sig64 from .lnchannel import Channel, AbstractChannel from .lnchannel import ChannelState, PeerState, HTLCWithStatus from .lnrater import LNRater diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py index 7e734fa49..adba3b990 100644 --- a/electrum/paymentrequest.py +++ b/electrum/paymentrequest.py @@ -31,6 +31,7 @@ import urllib.parse import certifi import aiohttp +import electrum_ecc as ecc try: @@ -38,7 +39,7 @@ try: except ImportError: sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'contrib/generate_payreqpb2.sh'") -from . import bitcoin, constants, ecc, util, transaction, x509, rsakey +from . import bitcoin, constants, util, transaction, x509, rsakey from .util import bfh, make_aiohttp_session, error_text_bytes_to_safe_str, get_running_loop from .invoices import Invoice, get_id_from_onchain_outputs from .crypto import sha256 diff --git a/electrum/plugins/cosigner_pool/qt.py b/electrum/plugins/cosigner_pool/qt.py index b95945abe..cbbe8884b 100644 --- a/electrum/plugins/cosigner_pool/qt.py +++ b/electrum/plugins/cosigner_pool/qt.py @@ -31,8 +31,9 @@ import ssl from PyQt6.QtCore import QObject, pyqtSignal from PyQt6.QtWidgets import QPushButton import certifi +import electrum_ecc as ecc -from electrum import util, keystore, ecc, crypto +from electrum import util, keystore, crypto from electrum.transaction import Transaction, PartialTransaction, tx_from_any, SerializationError from electrum.bip32 import BIP32Node from electrum.plugin import BasePlugin, hook diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 6cc160716..86c912cc0 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -6,7 +6,9 @@ import base64 import hashlib from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING -from electrum import bip32, constants, ecc +import electrum_ecc as ecc + +from electrum import bip32, constants from electrum import descriptor from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, normalize_bip32_derivation from electrum.bitcoin import EncodeBase58Check, is_b58_address, is_segwit_script_type, var_int diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py index cee55945e..e50bd7686 100644 --- a/electrum/plugins/trezor/clientbase.py +++ b/electrum/plugins/trezor/clientbase.py @@ -1,7 +1,8 @@ import time from struct import pack -from electrum import ecc +import electrum_ecc as ecc + from electrum.i18n import _ from electrum.util import UserCancelled, UserFacingException from electrum.keystore import bip39_normalize_passphrase diff --git a/electrum/plugins/trustedcoin/trustedcoin.py b/electrum/plugins/trustedcoin/trustedcoin.py index b8e12260c..4304ce1cd 100644 --- a/electrum/plugins/trustedcoin/trustedcoin.py +++ b/electrum/plugins/trustedcoin/trustedcoin.py @@ -32,7 +32,9 @@ from urllib.parse import quote from aiohttp import ClientResponse -from electrum import ecc, constants, keystore, version, bip32, bitcoin +import electrum_ecc as ecc + +from electrum import constants, keystore, version, bip32, bitcoin from electrum.bip32 import BIP32Node, xpub_type from electrum.crypto import sha256 from electrum.transaction import PartialTxOutput, PartialTxInput, PartialTransaction, Transaction diff --git a/electrum/storage.py b/electrum/storage.py index dedd6d006..f90b15469 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -31,7 +31,9 @@ import zlib from enum import IntEnum from typing import Optional -from . import ecc, crypto +import electrum_ecc as ecc + +from . import crypto from .util import (profiler, InvalidPassword, WalletFileException, bfh, standardize_path, test_read_write_permissions, os_chmod) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 7f9d189bc..661370bfa 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -8,10 +8,10 @@ import time import attr import aiohttp +from electrum_ecc import ECPrivkey from . import lnutil from .crypto import sha256, hash_160 -from .ecc import ECPrivkey from .bitcoin import (script_to_p2wsh, opcodes, construct_witness) from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint diff --git a/electrum/transaction.py b/electrum/transaction.py index a9b885684..0acf741ce 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -40,7 +40,9 @@ import itertools import binascii import copy -from . import ecc, bitcoin, constants, segwit_addr, bip32 +import electrum_ecc as ecc + +from . import bitcoin, constants, segwit_addr, bip32 from .bip32 import BIP32Node from .i18n import _ from .util import profiler, to_bytes, bfh, chunks, is_hex_str, parse_max_spend diff --git a/electrum/wallet.py b/electrum/wallet.py index 271535627..8f24fbf43 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -47,6 +47,7 @@ import threading import enum import asyncio +import electrum_ecc as ecc from aiorpcx import timeout_after, TaskTimeout, ignore_after, run_in_thread from .i18n import _ @@ -69,7 +70,7 @@ from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithM from .util import multisig_type, parse_max_spend from .storage import StorageEncryptionVersion, WalletStorage from .wallet_db import WalletDB -from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32 +from . import transaction, bitcoin, coinchooser, paymentrequest, bip32 from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput, PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint, Sighash) from .plugin import run_hook diff --git a/tests/test_bitcoin.py b/tests/test_bitcoin.py index d91c1827f..a890c9642 100644 --- a/tests/test_bitcoin.py +++ b/tests/test_bitcoin.py @@ -4,6 +4,8 @@ import json import os import sys +import electrum_ecc as ecc + from electrum import bitcoin from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, is_address, is_private_key, @@ -23,13 +25,11 @@ from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath, is_xpub, convert_bip32_strpath_to_intpath, normalize_bip32_derivation, is_all_public_derivation) from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS -from electrum import ecc, crypto, constants, bitcoin +from electrum import crypto, constants, bitcoin from electrum.util import bfh, InvalidPassword, randrange from electrum.storage import WalletStorage from electrum.keystore import xtype_from_derivation -from electrum import ecc_fast, crypto - from . import ElectrumTestCase from . import FAST_TESTS @@ -149,7 +149,7 @@ class Test_bitcoin(ElectrumTestCase): def test_libsecp256k1_is_available(self): # we want the unit testing framework to test with libsecp256k1 available. - self.assertTrue(bool(ecc_fast._libsecp256k1)) + self.assertTrue(bool(ecc._libsecp256k1)) def test_pycryptodomex_is_available(self): # we want the unit testing framework to test with pycryptodomex available. diff --git a/tests/test_descriptor.py b/tests/test_descriptor.py index a324755fb..3c4de8e20 100644 --- a/tests/test_descriptor.py +++ b/tests/test_descriptor.py @@ -8,6 +8,8 @@ from binascii import unhexlify import unittest +import electrum_ecc as ecc + from electrum.descriptor import ( parse_descriptor, MultisigDescriptor, @@ -18,7 +20,6 @@ from electrum.descriptor import ( WSHDescriptor, PubkeyProvider, ) -from electrum import ecc from electrum.util import bfh from . import ElectrumTestCase, as_testnet diff --git a/tests/test_ecc.py b/tests/test_ecc.py index e8f434112..8e8b90bb2 100644 --- a/tests/test_ecc.py +++ b/tests/test_ecc.py @@ -4,9 +4,11 @@ from ctypes import ( ) import io -from electrum import ecc, bitcoin -from electrum.ecc import ECPubkey, ECPrivkey -from electrum.ecc_fast import _libsecp256k1 +import electrum_ecc as ecc +from electrum_ecc import ECPubkey, ECPrivkey +from electrum_ecc import _libsecp256k1 + +from electrum import bitcoin from electrum import crypto from electrum.crypto import sha256 diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index fa0b8c206..dcb96758a 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -13,6 +13,7 @@ import unittest from typing import Iterable, NamedTuple, Tuple, List, Dict from aiorpcx import timeout_after, TaskTimeout +from electrum_ecc import ECPrivkey import electrum import electrum.trampoline @@ -20,7 +21,6 @@ from electrum import bitcoin from electrum import util from electrum import constants from electrum.network import Network -from electrum.ecc import ECPrivkey from electrum import simple_config, lnutil from electrum.lnaddr import lnencode, LnAddr, lndecode from electrum.bitcoin import COIN, sha256 diff --git a/tests/test_lntransport.py b/tests/test_lntransport.py index 4ae50c4d8..561489d65 100644 --- a/tests/test_lntransport.py +++ b/tests/test_lntransport.py @@ -1,7 +1,8 @@ import asyncio +from electrum_ecc import ECPrivkey + from electrum import util -from electrum.ecc import ECPrivkey from electrum.lnutil import LNPeerAddr from electrum.lntransport import LNResponderTransport, LNTransport from electrum.util import OldTaskGroup diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py index 1d05cbc5d..0864c4f9d 100644 --- a/tests/test_lnutil.py +++ b/tests/test_lnutil.py @@ -1,8 +1,9 @@ import unittest import json +import electrum_ecc as ecc + from electrum import bitcoin -from electrum import ecc from electrum.json_db import StoredDict from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc, make_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output, diff --git a/tests/test_transaction.py b/tests/test_transaction.py index aec69bae7..d4a10f1a4 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -2,6 +2,8 @@ import json import os from typing import NamedTuple, Union +from electrum_ecc import ECPrivkey + from electrum import transaction, bitcoin from electrum.transaction import (convert_raw_tx_to_hex, tx_from_any, Transaction, PartialTransaction, TxOutpoint, PartialTxInput, @@ -10,7 +12,6 @@ from electrum.transaction import (convert_raw_tx_to_hex, tx_from_any, Transactio from electrum.util import bfh from electrum.bitcoin import (deserialize_privkey, opcodes, construct_script, construct_witness) -from electrum.ecc import ECPrivkey from electrum import descriptor from .test_bitcoin import disable_ecdsa_r_value_grinding From acb4e00a381875d789b83131b2d78202dd997464 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 1 Oct 2024 12:39:16 +0200 Subject: [PATCH 2/6] add electrum_ecc to contrib/deterministic-build/requirements.txt note: Manual edit. Not running freeze_packages because some existing packages now require hatchling to build --- contrib/deterministic-build/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 88c9371e8..4f84882d3 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -14,6 +14,8 @@ certifi==2024.2.2 \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f dnspython==2.2.1 \ --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e +electrum-ecc==0.0.3 \ + --hash=sha256:c8ab69fecb294825367030da532b2d191883fa169789faa2942c256b4043d0a2 frozenlist==1.3.3 \ --hash=sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a idna==3.6 \ @@ -43,4 +45,4 @@ setuptools==65.5.1 \ wheel==0.38.4 \ --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac yarl==1.8.2 \ - --hash=sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562 \ No newline at end of file + --hash=sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562 From 9dbbd815a300f62faecb9e1463dd9d96fecde16e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 1 Oct 2024 10:24:58 +0200 Subject: [PATCH 3/6] build scripts: add libsecp256k1 library to the electrum_ecc directory --- .cirrus.yml | 6 ++---- contrib/build-wine/build-electrum-git.sh | 4 ++-- contrib/make_libsecp256k1.sh | 5 +++-- contrib/osx/make_osx.sh | 6 +++--- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 8c7eb2dae..3475f0fd6 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -46,8 +46,6 @@ task: - ./contrib/make_libsecp256k1.sh - mkdir contrib/_saved_secp256k1_build - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ - libsecp_install_script: - - cp contrib/_saved_secp256k1_build/* electrum/ tox_script: - export PYTHONASYNCIODEBUG - export PYTHONDEVMODE @@ -55,6 +53,7 @@ task: coveralls_script: - if [ ! -z "$COVERALLS_REPO_TOKEN" ] ; then coveralls ; fi env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ ELECTRUM_REQUIREMENTS_CI: contrib/requirements/requirements-ci.txt ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt # following CI_* env vars are set up for coveralls @@ -127,8 +126,6 @@ task: - ./contrib/make_libsecp256k1.sh - mkdir contrib/_saved_secp256k1_build - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ - libsecp_install_script: - - cp contrib/_saved_secp256k1_build/* electrum/ bitcoind_service_background_script: - tests/regtest/run_bitcoind.sh electrumx_service_background_script: @@ -137,6 +134,7 @@ task: - sleep 10s - python3 -m unittest tests/regtest.py env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt # ElectrumX exits with an error without this: ALLOW_ROOT: 1 diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index 39b78b40f..a1913ca3c 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -50,9 +50,9 @@ pushd $WINEPREFIX/drive_c/electrum # see https://github.com/pypa/pip/issues/2195 -- pip makes a copy of the entire directory info "Pip installing Electrum. This might take a long time if the project folder is large." $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-warn-script-location . -# pyinstaller needs to be able to "import electrum", for which we need libsecp256k1: +# pyinstaller needs to be able to "import electrum_ecc", for which we need libsecp256k1: # (or could try "pip install -e" instead) -cp electrum/libsecp256k1-*.dll "$WINEPREFIX/drive_c/python3/Lib/site-packages/electrum/" +cp electrum/libsecp256k1-*.dll "$WINEPREFIX/drive_c/python3/Lib/site-packages/electrum_ecc/" popd diff --git a/contrib/make_libsecp256k1.sh b/contrib/make_libsecp256k1.sh index 1bcfd913b..8887eb814 100755 --- a/contrib/make_libsecp256k1.sh +++ b/contrib/make_libsecp256k1.sh @@ -66,9 +66,10 @@ info "Building $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." if [ -n "$DLL_TARGET_DIR" ] ; then cp -fpv "$here/$pkgname/dist/lib/$dlname" "$DLL_TARGET_DIR/" || fail "Could not copy the $pkgname binary to DLL_TARGET_DIR" + else + 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 'electrum' folder." fi ) diff --git a/contrib/osx/make_osx.sh b/contrib/osx/make_osx.sh index 5621d5d55..6d7c3b838 100755 --- a/contrib/osx/make_osx.sh +++ b/contrib/osx/make_osx.sh @@ -149,7 +149,7 @@ if [ ! -f "$DLL_TARGET_DIR/libsecp256k1.2.dylib" ]; then else info "Skipping libsecp256k1 build: reusing already built dylib." fi -cp -f "$DLL_TARGET_DIR"/libsecp256k1.*.dylib "$PROJECT_ROOT/electrum/" || fail "Could not copy libsecp256k1 dylib" +#cp -f "$DLL_TARGET_DIR"/libsecp256k1.*.dylib "$PROJECT_ROOT/electrum" || fail "Could not copy libsecp256k1 dylib" if [ ! -f "$DLL_TARGET_DIR/libzbar.0.dylib" ]; then info "Building ZBar dylib..." @@ -189,9 +189,9 @@ python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all: info "Building $PACKAGE..." python3 -m pip install --no-build-isolation --no-dependencies \ --no-warn-script-location . > /dev/null || fail "Could not build $PACKAGE" -# pyinstaller needs to be able to "import electrum", for which we need libsecp256k1: +# pyinstaller needs to be able to "import electrum_ecc", for which we need libsecp256k1: # (or could try "pip install -e" instead) -cp "$PROJECT_ROOT/electrum"/libsecp256k1.*.dylib "$VENV_DIR/lib/python$PY_VER_MAJOR/site-packages/electrum/" +cp "$DLL_TARGET_DIR"/libsecp256k1.*.dylib "$VENV_DIR/lib/python$PY_VER_MAJOR/site-packages/electrum_ecc/" # strip debug symbols of some compiled libs # - hidapi (hid.cpython-39-darwin.so) in particular is not reproducible without this From 3d7f3355cbaaa960eb5a2ca7036f896e9be54d03 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 7 Oct 2024 15:59:09 +0000 Subject: [PATCH 4/6] ci: libsecp build: follow-up prev --- .cirrus.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cirrus.yml b/.cirrus.yml index 3475f0fd6..eec8d152a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -49,6 +49,7 @@ task: tox_script: - export PYTHONASYNCIODEBUG - export PYTHONDEVMODE + - export ELECTRUM_ECC_DONT_COMPILE=1 - tox coveralls_script: - if [ ! -z "$COVERALLS_REPO_TOKEN" ] ; then coveralls ; fi @@ -109,6 +110,7 @@ task: - apt-get update - apt-get -y install curl jq bc # install electrum + - export ELECTRUM_ECC_DONT_COMPILE=1 # we build manually to make caching it easier - pip3 install .[tests] # install e-x some commits after 1.16.0 tag - pip3 install git+https://github.com/spesmilo/electrumx.git@4e66804dc0d668cd6bd4602b547e2f5b2e227e97 From f35437f03cdc61a23139849b8ad39991330739ae Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 7 Oct 2024 16:17:16 +0000 Subject: [PATCH 5/6] build: set ELECTRUM_ECC_DONT_COMPILE=1, instead manually build lib Haven't checked if electrum-ecc compiles libsecp reproducibly. For now let's just keep the old flow. (but if we spent time on making that compilation reproducible, the appimage and the macos builds could use it directly) --- contrib/build-linux/appimage/make_appimage.sh | 1 + contrib/build-wine/build-electrum-git.sh | 1 + contrib/make_packages.sh | 2 ++ contrib/osx/make_osx.sh | 1 + 4 files changed, 5 insertions(+) diff --git a/contrib/build-linux/appimage/make_appimage.sh b/contrib/build-linux/appimage/make_appimage.sh index 97de4aaa9..ff2fd0eea 100755 --- a/contrib/build-linux/appimage/make_appimage.sh +++ b/contrib/build-linux/appimage/make_appimage.sh @@ -161,6 +161,7 @@ info "Installing build dependencies." --cache-dir "$PIP_CACHE_DIR" -r "$CONTRIB/deterministic-build/requirements-build-appimage.txt" info "installing electrum and its dependencies." +export ELECTRUM_ECC_DONT_COMPILE=1 "$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --no-warn-script-location \ --cache-dir "$PIP_CACHE_DIR" -r "$CONTRIB/deterministic-build/requirements.txt" "$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --only-binary PyQt6,PyQt6-Qt6,cryptography --no-warn-script-location \ diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index a1913ca3c..517d1a16d 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -32,6 +32,7 @@ export AIOHTTP_NO_EXTENSIONS=1 export YARL_NO_EXTENSIONS=1 export MULTIDICT_NO_EXTENSIONS=1 export FROZENLIST_NO_EXTENSIONS=1 +export ELECTRUM_ECC_DONT_COMPILE=1 info "Installing requirements..." $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-binary :all: --no-warn-script-location \ diff --git a/contrib/make_packages.sh b/contrib/make_packages.sh index f54a71780..6677245ed 100755 --- a/contrib/make_packages.sh +++ b/contrib/make_packages.sh @@ -32,6 +32,8 @@ export YARL_NO_EXTENSIONS=1 export MULTIDICT_NO_EXTENSIONS=1 export FROZENLIST_NO_EXTENSIONS=1 +export ELECTRUM_ECC_DONT_COMPILE=1 + # if we end up having to compile something, at least give reproducibility a fighting chance export LC_ALL=C export TZ=UTC diff --git a/contrib/osx/make_osx.sh b/contrib/osx/make_osx.sh index 6d7c3b838..f9a107e0d 100755 --- a/contrib/osx/make_osx.sh +++ b/contrib/osx/make_osx.sh @@ -169,6 +169,7 @@ cp -f "$DLL_TARGET_DIR/libusb-1.0.dylib" "$PROJECT_ROOT/electrum/" || fail "Coul info "Installing requirements..." +export ELECTRUM_ECC_DONT_COMPILE=1 python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all: \ --no-warn-script-location \ -Ir ./contrib/deterministic-build/requirements.txt \ From 581082d5bb678307ee05bf0616b1993d86a5ecb3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 7 Oct 2024 17:04:14 +0000 Subject: [PATCH 6/6] updates READMEs re electrum-ecc --- README.md | 26 ++++++++++++++++++-------- contrib/build-wine/README_windows.md | 4 ++++ contrib/osx/README_macos.md | 17 +++++++++++------ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0cfcea5ce..f8142e6ca 100644 --- a/README.md +++ b/README.md @@ -23,30 +23,38 @@ is a TL;DR: ``` $ sudo apt-get install libsecp256k1-dev -$ python3 -m pip install --user ".[gui,crypto]" +$ ELECTRUM_ECC_DONT_COMPILE=1 python3 -m pip install --user ".[gui,crypto]" ``` ### Not pure-python dependencies +#### Qt GUI + If you want to use the Qt interface, install the Qt dependencies: ``` $ sudo apt-get install python3-pyqt6 ``` +#### libsecp256k1 + For elliptic curve operations, [libsecp256k1](https://github.com/bitcoin-core/secp256k1) -is a required dependency: -``` -$ sudo apt-get install libsecp256k1-dev -``` +is a required dependency. -Alternatively, when running from a cloned repository, a script is provided to build -libsecp256k1 yourself: +If you "pip install" Electrum, by default libsecp will get compiled locally, +as part of the `electrum-ecc` dependency. This can be opted-out of, +by setting the `ELECTRUM_ECC_DONT_COMPILE=1` environment variable. +For the compilation to work, besides a C compiler, you need at least: ``` $ sudo apt-get install automake libtool -$ ./contrib/make_libsecp256k1.sh +``` +If you opt out of the compilation, you need to provide libsecp in another way, e.g.: +``` +$ sudo apt-get install libsecp256k1-dev ``` +#### cryptography + Due to the need for fast symmetric ciphers, [cryptography](https://github.com/pyca/cryptography) is required. Install from your package manager (or from pip): @@ -54,6 +62,8 @@ Install from your package manager (or from pip): $ sudo apt-get install python3-cryptography ``` +#### hardware-wallet support + If you would like hardware wallet support, [see this](https://github.com/spesmilo/electrum-docs/blob/master/hardware-linux.rst). diff --git a/contrib/build-wine/README_windows.md b/contrib/build-wine/README_windows.md index a34cce1f9..b6ac49ac5 100644 --- a/contrib/build-wine/README_windows.md +++ b/contrib/build-wine/README_windows.md @@ -21,6 +21,10 @@ Run install (this should install most dependencies): ### 2. Install `libsecp256k1` +[comment]: # (technically the dll should be put into site-packages/electrum_ecc/, +but putting it into electrum/ also works because of the `os.add_dll_directory` call in +electrum/__init__.py) + [libsecp256k1](https://github.com/bitcoin-core/secp256k1) is a required dependency. This is a C library, which you need to compile yourself. Electrum needs a dll, named `libsecp256k1-0.dll` (or newer `libsecp256k1-*.dll`), diff --git a/contrib/osx/README_macos.md b/contrib/osx/README_macos.md index 9549d6ed3..a56e63c8d 100644 --- a/contrib/osx/README_macos.md +++ b/contrib/osx/README_macos.md @@ -15,18 +15,23 @@ $ cd electrum $ git submodule update --init ``` -Run install (this should install most dependencies): +### 2. Prepare for compiling libsecp256k1 + +To be able to build the `electrum-ecc` package from source +(which is pulled in when installing Electrum in the next step), +you need: ``` -$ python3 -m pip install --user -e ".[gui,crypto]" +$ brew install autoconf automake libtool coreutils ``` -### 2. Install libsecp256k1 +### 3. Install Electrum + +Run install (this should install the dependencies): ``` -$ brew install autoconf automake libtool coreutils -$ contrib/make_libsecp256k1.sh +$ python3 -m pip install --user -e ".[gui,crypto]" ``` -### 3. Run electrum: +### 4. Run electrum: ``` $ ./run_electrum ```