From c58c4d7451a537750c1b45c6f9c145a422158028 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 21 Oct 2024 15:04:37 +0200 Subject: [PATCH] Make lntransport not require lnutil. This will be useful if we decide to ship lntransport as a separate package. It is also a conceptual cleanup. Notes: - lntransport still requires crypto.py - parsing node id from a bolt11 invoice is not supported. --- electrum/channel_db.py | 3 +- electrum/commands.py | 2 +- electrum/crypto.py | 8 ++ electrum/gui/qml/qechannelopener.py | 2 +- electrum/gui/qt/main_window.py | 2 +- electrum/lnchannel.py | 3 +- electrum/lnonion.py | 4 +- electrum/lnpeer.py | 7 +- electrum/lnsweep.py | 3 +- electrum/lntransport.py | 103 +++++++++++++++++++++++++- electrum/lnutil.py | 111 +--------------------------- electrum/lnworker.py | 8 +- electrum/trampoline.py | 3 +- tests/test_lnchannel.py | 5 +- tests/test_lnpeer.py | 7 +- tests/test_lntransport.py | 45 +++++++++-- tests/test_lnutil.py | 43 +---------- 17 files changed, 179 insertions(+), 180 deletions(-) diff --git a/electrum/channel_db.py b/electrum/channel_db.py index 94fc622d5..880815df5 100644 --- a/electrum/channel_db.py +++ b/electrum/channel_db.py @@ -43,7 +43,8 @@ from .sql_db import SqlDB, sql from . import constants, util from .util import profiler, get_headers_dir, is_ip_address, json_normalize, UserFacingException from .logging import Logger -from .lnutil import (LNPeerAddr, format_short_channel_id, ShortChannelID, +from .lntransport import LNPeerAddr +from .lnutil import (format_short_channel_id, ShortChannelID, validate_features, IncompatibleOrInsaneFeatures, InvalidGossipMsg) from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update from .lnmsg import decode_msg diff --git a/electrum/commands.py b/electrum/commands.py index 25a49c176..6d713fb25 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -61,7 +61,7 @@ from .address_synchronizer import TX_HEIGHT_LOCAL from .mnemonic import Mnemonic from .lnutil import SENT, RECEIVED from .lnutil import LnFeatures -from .lnutil import extract_nodeid +from .lntransport import extract_nodeid from .lnpeer import channel_id_from_funding_tx from .plugin import run_hook, DeviceMgr, Plugins from .version import ELECTRUM_VERSION diff --git a/electrum/crypto.py b/electrum/crypto.py index e31a49c6d..a34fbcc15 100644 --- a/electrum/crypto.py +++ b/electrum/crypto.py @@ -492,3 +492,11 @@ def ecies_decrypt_message( if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256): raise InvalidPassword() return aes_decrypt_with_iv(key_e, iv, ciphertext) + + +def get_ecdh(priv: bytes, pub: bytes) -> bytes: + pt = ecc.ECPubkey(pub) * ecc.string_to_number(priv) + return sha256(pt.get_public_key_bytes()) + +def privkey_to_pubkey(priv: bytes) -> bytes: + return ecc.ECPrivkey(priv[:32]).get_public_key_bytes() diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index abe075d82..74c5e2a90 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -8,7 +8,7 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.i18n import _ from electrum.gui import messages from electrum.util import bfh -from electrum.lnutil import extract_nodeid, ConnStringFormatError +from electrum.lntransport import extract_nodeid, ConnStringFormatError from electrum.bitcoin import DummyAddress from electrum.lnworker import hardcoded_trampoline_nodes from electrum.logging import get_logger diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 099a82733..4e61fae32 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -72,7 +72,7 @@ from electrum.network import Network, UntrustedServerReturnedError, NetworkExcep from electrum.exchange_rate import FxThread from electrum.simple_config import SimpleConfig from electrum.logging import Logger -from electrum.lnutil import extract_nodeid, ConnStringFormatError +from electrum.lntransport import extract_nodeid, ConnStringFormatError from electrum.lnaddr import lndecode from electrum.submarine_swaps import SwapServerError diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index e417a4534..3f1a74693 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -43,6 +43,7 @@ from .bitcoin import redeem_script_to_address from .crypto import sha256, sha256d from .transaction import Transaction, PartialTransaction, TxInput, Sighash from .logging import Logger +from .lntransport import LNPeerAddr from .lnonion import OnionFailureCode, OnionRoutingFailure from . import lnutil from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, @@ -52,7 +53,7 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc, funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs, ScriptHtlc, PaymentFailure, calc_fees_for_commitment_tx, RemoteMisbehaving, make_htlc_output_witness_script, - ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr, + ShortChannelID, map_htlcs_to_ctx_output_idxs, fee_for_htlc_output, offered_htlc_trim_threshold_sat, received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, ChannelType, LNProtocolWarning) diff --git a/electrum/lnonion.py b/electrum/lnonion.py index a1b92fc29..55c0eb2a5 100644 --- a/electrum/lnonion.py +++ b/electrum/lnonion.py @@ -30,9 +30,9 @@ from enum import IntEnum import electrum_ecc as ecc -from .crypto import sha256, hmac_oneshot, chacha20_encrypt +from .crypto import sha256, hmac_oneshot, chacha20_encrypt, get_ecdh from .util import profiler, xor_bytes, bfh -from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH, +from .lnutil import (PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH, NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag) from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int from . import lnmsg diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index d4b628559..b84a993a6 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -19,7 +19,7 @@ from electrum_ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig6 import aiorpcx from aiorpcx import ignore_after -from .crypto import sha256, sha256d +from .crypto import sha256, sha256d, privkey_to_pubkey from . import bitcoin, util from . import constants from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup, @@ -40,14 +40,13 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf funding_output_script, get_per_commitment_secret_from_seed, secret_to_pubkey, PaymentFailure, LnFeatures, LOCAL, REMOTE, HTLCOwner, - ln_compare_features, privkey_to_pubkey, MIN_FINAL_CLTV_DELTA_ACCEPTED, - LightningPeerConnectionClosed, HandshakeFailed, + ln_compare_features, MIN_FINAL_CLTV_DELTA_ACCEPTED, RemoteMisbehaving, ShortChannelID, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures) from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget from .lnutil import serialize_htlc_key -from .lntransport import LNTransport, LNTransportBase +from .lntransport import LNTransport, LNTransportBase, LightningPeerConnectionClosed, HandshakeFailed from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg from .interface import GracefulDisconnect from .lnrouter import fee_for_edge_msat diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index 3b653584a..9834176f4 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -8,6 +8,7 @@ from enum import Enum, auto import electrum_ecc as ecc from .util import bfh +from .crypto import privkey_to_pubkey from .bitcoin import redeem_script_to_address, dust_threshold, construct_witness from .invoices import PR_PAID from . import descriptor @@ -15,7 +16,7 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey, make_htlc_tx_witness, make_htlc_tx_with_open_channel, UpdateAddHtlc, LOCAL, REMOTE, make_htlc_output_witness_script, - get_ordered_channel_configs, privkey_to_pubkey, get_per_commitment_secret_from_seed, + get_ordered_channel_configs, get_per_commitment_secret_from_seed, RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED, map_htlcs_to_ctx_output_idxs, Direction) from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput, diff --git a/electrum/lntransport.py b/electrum/lntransport.py index 1bf1851d5..b876783d3 100644 --- a/electrum/lntransport.py +++ b/electrum/lntransport.py @@ -5,19 +5,23 @@ # Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8 +import re import hashlib import asyncio from asyncio import StreamReader, StreamWriter from typing import Optional from functools import cached_property +from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence +from aiorpcx import NetAddress 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 .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt, get_ecdh, privkey_to_pubkey from .util import MySocksProxy +class LightningPeerConnectionClosed(Exception): pass +class HandshakeFailed(Exception): pass +class ConnStringFormatError(Exception): pass class HandshakeState(object): prologue = b"lightning" @@ -90,6 +94,99 @@ def create_ephemeral_key() -> (bytes, bytes): return privkey.get_secret_bytes(), privkey.get_public_key_bytes() +def split_host_port(host_port: str) -> Tuple[str, str]: # port returned as string + ipv6 = re.compile(r'\[(?P[:0-9a-f]+)\](?P:\d+)?$') + other = re.compile(r'(?P[^:]+)(?P:\d+)?$') + m = ipv6.match(host_port) + if not m: + m = other.match(host_port) + if not m: + raise ConnStringFormatError('Connection strings must be in @: format') + host = m.group('host') + if m.group('port'): + port = m.group('port')[1:] + else: + port = '9735' + try: + int(port) + except ValueError: + raise ConnStringFormatError('Port number must be decimal') + return host, port + +def extract_nodeid(connect_contents: str) -> Tuple[bytes, Optional[str]]: + """Takes a connection-string-like str, and returns a tuple (node_id, rest), + where rest is typically a host (with maybe port). Examples: + - extract_nodeid(pubkey@host:port) == (pubkey, host:port) + - extract_nodeid(pubkey@host) == (pubkey, host) + - extract_nodeid(pubkey) == (pubkey, None) + Can raise ConnStringFormatError. + """ + rest = None + try: + # connection string? + nodeid_hex, rest = connect_contents.split("@", 1) + except ValueError: + # node id as hex? + nodeid_hex = connect_contents + if rest == '': + raise ConnStringFormatError('At least a hostname must be supplied after the at symbol.') + try: + node_id = bytes.fromhex(nodeid_hex) + if len(node_id) != 33: + raise Exception() + except Exception: + raise ConnStringFormatError('Invalid node ID, must be 33 bytes and hexadecimal') + return node_id, rest + +class LNPeerAddr: + # note: while not programmatically enforced, this class is meant to be *immutable* + + def __init__(self, host: str, port: int, pubkey: bytes): + assert isinstance(host, str), repr(host) + assert isinstance(port, int), repr(port) + assert isinstance(pubkey, bytes), repr(pubkey) + try: + net_addr = NetAddress(host, port) # this validates host and port + except Exception as e: + raise ValueError(f"cannot construct LNPeerAddr: invalid host or port (host={host}, port={port})") from e + # note: not validating pubkey as it would be too expensive: + # if not ECPubkey.is_pubkey_bytes(pubkey): raise ValueError() + self.host = host + self.port = port + self.pubkey = pubkey + self._net_addr = net_addr + + def __str__(self): + return '{}@{}'.format(self.pubkey.hex(), self.net_addr_str()) + + @classmethod + def from_str(cls, s): + node_id, rest = extract_nodeid(s) + host, port = split_host_port(rest) + return LNPeerAddr(host, int(port), node_id) + + def __repr__(self): + return f'' + + def net_addr(self) -> NetAddress: + return self._net_addr + + def net_addr_str(self) -> str: + return str(self._net_addr) + + def __eq__(self, other): + if not isinstance(other, LNPeerAddr): + return False + return (self.host == other.host + and self.port == other.port + and self.pubkey == other.pubkey) + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash((self.host, self.port, self.pubkey)) + class LNTransportBase: reader: StreamReader writer: StreamWriter diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 4d5c91bc7..480653484 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -13,7 +13,6 @@ 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 from .util import bfh, inv_dict, UserFacingException from .util import list_enabled_bits @@ -414,10 +413,7 @@ class HtlcLog(NamedTuple): class LightningError(Exception): pass -class LightningPeerConnectionClosed(LightningError): pass class UnableToDeriveSecret(LightningError): pass -class HandshakeFailed(LightningError): pass -class ConnStringFormatError(LightningError): pass class RemoteMisbehaving(LightningError): pass class NotFoundChanAnnouncementForUpdate(Exception): pass @@ -564,8 +560,6 @@ def secret_to_pubkey(secret: int) -> bytes: assert type(secret) is int return ecc.ECPrivkey.from_secret_scalar(secret).get_public_key_bytes(compressed=True) -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)) @@ -1121,10 +1115,6 @@ def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> in funder_payment_basepoint=funder_conf.payment_basepoint.pubkey, fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey) -def get_ecdh(priv: bytes, pub: bytes) -> bytes: - pt = ECPubkey(pub) * string_to_number(priv) - return sha256(pt.get_public_key_bytes()) - class LnFeatureContexts(enum.Flag): INIT = enum.auto() @@ -1480,54 +1470,6 @@ def derive_payment_secret_from_payment_preimage(payment_preimage: bytes) -> byte return sha256(bytes(modified)) -class LNPeerAddr: - # note: while not programmatically enforced, this class is meant to be *immutable* - - def __init__(self, host: str, port: int, pubkey: bytes): - assert isinstance(host, str), repr(host) - assert isinstance(port, int), repr(port) - assert isinstance(pubkey, bytes), repr(pubkey) - try: - net_addr = NetAddress(host, port) # this validates host and port - except Exception as e: - raise ValueError(f"cannot construct LNPeerAddr: invalid host or port (host={host}, port={port})") from e - # note: not validating pubkey as it would be too expensive: - # if not ECPubkey.is_pubkey_bytes(pubkey): raise ValueError() - self.host = host - self.port = port - self.pubkey = pubkey - self._net_addr = net_addr - - def __str__(self): - return '{}@{}'.format(self.pubkey.hex(), self.net_addr_str()) - - @classmethod - def from_str(cls, s): - node_id, rest = extract_nodeid(s) - host, port = split_host_port(rest) - return LNPeerAddr(host, int(port), node_id) - - def __repr__(self): - return f'' - - def net_addr(self) -> NetAddress: - return self._net_addr - - def net_addr_str(self) -> str: - return str(self._net_addr) - - def __eq__(self, other): - if not isinstance(other, LNPeerAddr): - return False - return (self.host == other.host - and self.port == other.port - and self.pubkey == other.pubkey) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash((self.host, self.port, self.pubkey)) def get_compressed_pubkey_from_bech32(bech32_pubkey: str) -> bytes: @@ -1557,57 +1499,8 @@ def make_closing_tx(local_funding_pubkey: bytes, remote_funding_pubkey: bytes, return tx -def split_host_port(host_port: str) -> Tuple[str, str]: # port returned as string - ipv6 = re.compile(r'\[(?P[:0-9a-f]+)\](?P:\d+)?$') - other = re.compile(r'(?P[^:]+)(?P:\d+)?$') - m = ipv6.match(host_port) - if not m: - m = other.match(host_port) - if not m: - raise ConnStringFormatError(_('Connection strings must be in @: format')) - host = m.group('host') - if m.group('port'): - port = m.group('port')[1:] - else: - port = '9735' - try: - int(port) - except ValueError: - raise ConnStringFormatError(_('Port number must be decimal')) - return host, port - - -def extract_nodeid(connect_contents: str) -> Tuple[bytes, Optional[str]]: - """Takes a connection-string-like str, and returns a tuple (node_id, rest), - where rest is typically a host (with maybe port). Examples: - - extract_nodeid(pubkey@host:port) == (pubkey, host:port) - - extract_nodeid(pubkey@host) == (pubkey, host) - - extract_nodeid(pubkey) == (pubkey, None) - - extract_nodeid(bolt11_invoice) == (pubkey, None) - Can raise ConnStringFormatError. - """ - rest = None - try: - # connection string? - nodeid_hex, rest = connect_contents.split("@", 1) - except ValueError: - try: - # invoice? - invoice = lndecode(connect_contents) - nodeid_bytes = invoice.pubkey.serialize() - nodeid_hex = nodeid_bytes.hex() - except Exception: - # node id as hex? - nodeid_hex = connect_contents - if rest == '': - raise ConnStringFormatError(_('At least a hostname must be supplied after the at symbol.')) - try: - node_id = bfh(nodeid_hex) - if len(node_id) != 33: - raise Exception() - except Exception: - raise ConnStringFormatError(_('Invalid node ID, must be 33 bytes and hexadecimal')) - return node_id, rest + + # key derivation diff --git a/electrum/lnworker.py b/electrum/lnworker.py index c0be79bf0..b0a895f43 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -49,7 +49,7 @@ from .util import ignore_exceptions, make_aiohttp_session from .util import timestamp_to_datetime, random_shuffled_copy from .util import MyEncoder, is_private_netaddress, UnrelatedTransactionException from .logging import Logger -from .lntransport import LNTransport, LNResponderTransport, LNTransportBase +from .lntransport import LNTransport, LNResponderTransport, LNTransportBase, LNPeerAddr, split_host_port, extract_nodeid, ConnStringFormatError from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnaddr import lnencode, LnAddr, lndecode from .lnchannel import Channel, AbstractChannel @@ -59,9 +59,9 @@ from . import lnutil from .lnutil import funding_output_script from .lnutil import serialize_htlc_key, deserialize_htlc_key from .bitcoin import DummyAddress -from .lnutil import (Outpoint, LNPeerAddr, - get_compressed_pubkey_from_bech32, extract_nodeid, - PaymentFailure, split_host_port, ConnStringFormatError, +from .lnutil import (Outpoint, + get_compressed_pubkey_from_bech32, + PaymentFailure, generate_keypair, LnKeyFamily, LOCAL, REMOTE, MIN_FINAL_CLTV_DELTA_FOR_INVOICE, NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner, diff --git a/electrum/trampoline.py b/electrum/trampoline.py index 8f0f1ed8b..e98df30b5 100644 --- a/electrum/trampoline.py +++ b/electrum/trampoline.py @@ -6,7 +6,8 @@ from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, from .lnutil import LnFeatures, PaymentFeeBudget from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_within_budget, LNPaymentTRoute -from .lnutil import NoPathFound, LNPeerAddr +from .lnutil import NoPathFound +from .lntransport import LNPeerAddr from . import constants from .logging import get_logger diff --git a/tests/test_lnchannel.py b/tests/test_lnchannel.py index ec49d4374..c933d5720 100644 --- a/tests/test_lnchannel.py +++ b/tests/test_lnchannel.py @@ -33,6 +33,7 @@ from electrum import lnpeer from electrum import lnchannel from electrum import lnutil from electrum import bip32 as bip32_utils +from electrum.crypto import privkey_to_pubkey from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc from electrum.logging import console_stderr_handler from electrum.lnchannel import ChannelState @@ -134,8 +135,8 @@ def create_test_channels(*, feerate=6000, local_msat=None, remote_msat=None, remote_amount = remote_msat if remote_msat is not None else (funding_sat * 1000 // 2) alice_raw = [bip32("m/" + str(i)) for i in range(5)] bob_raw = [bip32("m/" + str(i)) for i in range(5,11)] - alice_privkeys = [lnutil.Keypair(lnutil.privkey_to_pubkey(x), x) for x in alice_raw] - bob_privkeys = [lnutil.Keypair(lnutil.privkey_to_pubkey(x), x) for x in bob_raw] + alice_privkeys = [lnutil.Keypair(privkey_to_pubkey(x), x) for x in alice_raw] + bob_privkeys = [lnutil.Keypair(privkey_to_pubkey(x), x) for x in bob_raw] alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys] bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys] diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index d8f00a566..dfca4b990 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -27,8 +27,9 @@ from electrum.bitcoin import COIN, sha256 from electrum.transaction import Transaction from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError from electrum.lnpeer import Peer -from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey -from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner, PaymentFeeBudget +from electrum.lntransport import LNPeerAddr +from electrum.crypto import privkey_to_pubkey +from electrum.lnutil import Keypair, PaymentFailure, LnFeatures, HTLCOwner, PaymentFeeBudget from electrum.lnchannel import ChannelState, PeerState, Channel from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent from electrum.channel_db import ChannelDB @@ -1209,7 +1210,7 @@ class TestPeerDirect(TestPeer): # create upfront shutdown script for bob, alice doesn't use upfront # shutdown script - bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32)) + bob_uss_pub = privkey_to_pubkey(os.urandom(32)) bob_uss_addr = bitcoin.pubkey_to_address('p2wpkh', bob_uss_pub.hex()) bob_uss = bitcoin.address_to_script(bob_uss_addr) diff --git a/tests/test_lntransport.py b/tests/test_lntransport.py index 561489d65..3b7b7aebf 100644 --- a/tests/test_lntransport.py +++ b/tests/test_lntransport.py @@ -1,10 +1,9 @@ import asyncio -from electrum_ecc import ECPrivkey +import electrum_ecc as ecc from electrum import util -from electrum.lnutil import LNPeerAddr -from electrum.lntransport import LNResponderTransport, LNTransport +from electrum.lntransport import LNPeerAddr, LNResponderTransport, LNTransport, extract_nodeid, split_host_port, ConnStringFormatError from electrum.util import OldTaskGroup from . import ElectrumTestCase @@ -46,8 +45,8 @@ class TestLNTransport(ElectrumTestCase): async def test_loop(self): responder_shaked = asyncio.Event() server_shaked = asyncio.Event() - responder_key = ECPrivkey.generate_random_key() - initiator_key = ECPrivkey.generate_random_key() + responder_key = ecc.ECPrivkey.generate_random_key() + initiator_key = ecc.ECPrivkey.generate_random_key() messages_sent_by_client = [ b'hello from client', b'long data from client ' + bytes(range(256)) * 100 + b'... client done', @@ -98,3 +97,39 @@ class TestLNTransport(ElectrumTestCase): server.close() await f() + + def test_split_host_port(self): + self.assertEqual(split_host_port("[::1]:8000"), ("::1", "8000")) + self.assertEqual(split_host_port("[::1]"), ("::1", "9735")) + self.assertEqual(split_host_port("[2601:602:8800:9a:dc59:a4ff:fede:24a9]:9735"), ("2601:602:8800:9a:dc59:a4ff:fede:24a9", "9735")) + self.assertEqual(split_host_port("[2601:602:8800::a4ff:fede:24a9]:9735"), ("2601:602:8800::a4ff:fede:24a9", "9735")) + self.assertEqual(split_host_port("kæn.guru:8000"), ("kæn.guru", "8000")) + self.assertEqual(split_host_port("kæn.guru"), ("kæn.guru", "9735")) + self.assertEqual(split_host_port("127.0.0.1:8000"), ("127.0.0.1", "8000")) + self.assertEqual(split_host_port("127.0.0.1"), ("127.0.0.1", "9735")) + # accepted by getaddrinfo but not ipaddress.ip_address + self.assertEqual(split_host_port("127.0.0:8000"), ("127.0.0", "8000")) + self.assertEqual(split_host_port("127.0.0"), ("127.0.0", "9735")) + self.assertEqual(split_host_port("electrum.org:8000"), ("electrum.org", "8000")) + self.assertEqual(split_host_port("electrum.org"), ("electrum.org", "9735")) + + with self.assertRaises(ConnStringFormatError): + split_host_port("electrum.org:8000:") + with self.assertRaises(ConnStringFormatError): + split_host_port("electrum.org:") + + def test_extract_nodeid(self): + pubkey1 = ecc.GENERATOR.get_public_key_bytes(compressed=True) + with self.assertRaises(ConnStringFormatError): + extract_nodeid("00" * 32 + "@localhost") + with self.assertRaises(ConnStringFormatError): + extract_nodeid("00" * 33 + "@") + # pubkey + host + self.assertEqual(extract_nodeid("00" * 33 + "@localhost"), (b"\x00" * 33, "localhost")) + self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@11.22.33.44"), (pubkey1, "11.22.33.44")) + self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]"), (pubkey1, "[2001:41d0:e:734::1]")) + # pubkey + host + port + self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@11.22.33.44:5555"), (pubkey1, "11.22.33.44:5555")) + self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]:8888"), (pubkey1, "[2001:41d0:e:734::1]:8888")) + # just pubkey + self.assertEqual(extract_nodeid(f"{pubkey1.hex()}"), (pubkey1, None)) diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py index 0864c4f9d..aa51d57d1 100644 --- a/tests/test_lnutil.py +++ b/tests/test_lnutil.py @@ -9,8 +9,8 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see make_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output, make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey, derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret, - get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError, - ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, + get_compressed_pubkey_from_bech32, + ScriptHtlc, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, ln_compare_features, IncompatibleLightningFeatures, ChannelType, ImportedChannelBackupStorage) from electrum.util import bfh, MyEncoder @@ -742,45 +742,6 @@ class TestLNUtil(ElectrumTestCase): self.assertEqual(b'\x03\x84\xef\x87\xd9d\xa2\xaaa7=\xff\xb8\xfe=t8[}>;\n\x13\xa8e\x8eo:\xf5Mi\xb5H', get_compressed_pubkey_from_bech32('ln1qwzwlp7evj325cfh8hlm3l3awsu9klf78v9p82r93ehn4a2ddx65s66awg5')) - def test_split_host_port(self): - self.assertEqual(split_host_port("[::1]:8000"), ("::1", "8000")) - self.assertEqual(split_host_port("[::1]"), ("::1", "9735")) - self.assertEqual(split_host_port("[2601:602:8800:9a:dc59:a4ff:fede:24a9]:9735"), ("2601:602:8800:9a:dc59:a4ff:fede:24a9", "9735")) - self.assertEqual(split_host_port("[2601:602:8800::a4ff:fede:24a9]:9735"), ("2601:602:8800::a4ff:fede:24a9", "9735")) - self.assertEqual(split_host_port("kæn.guru:8000"), ("kæn.guru", "8000")) - self.assertEqual(split_host_port("kæn.guru"), ("kæn.guru", "9735")) - self.assertEqual(split_host_port("127.0.0.1:8000"), ("127.0.0.1", "8000")) - self.assertEqual(split_host_port("127.0.0.1"), ("127.0.0.1", "9735")) - # accepted by getaddrinfo but not ipaddress.ip_address - self.assertEqual(split_host_port("127.0.0:8000"), ("127.0.0", "8000")) - self.assertEqual(split_host_port("127.0.0"), ("127.0.0", "9735")) - self.assertEqual(split_host_port("electrum.org:8000"), ("electrum.org", "8000")) - self.assertEqual(split_host_port("electrum.org"), ("electrum.org", "9735")) - - with self.assertRaises(ConnStringFormatError): - split_host_port("electrum.org:8000:") - with self.assertRaises(ConnStringFormatError): - split_host_port("electrum.org:") - - def test_extract_nodeid(self): - pubkey1 = ecc.GENERATOR.get_public_key_bytes(compressed=True) - with self.assertRaises(ConnStringFormatError): - extract_nodeid("00" * 32 + "@localhost") - with self.assertRaises(ConnStringFormatError): - extract_nodeid("00" * 33 + "@") - # pubkey + host - self.assertEqual(extract_nodeid("00" * 33 + "@localhost"), (b"\x00" * 33, "localhost")) - self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@11.22.33.44"), (pubkey1, "11.22.33.44")) - self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]"), (pubkey1, "[2001:41d0:e:734::1]")) - # pubkey + host + port - self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@11.22.33.44:5555"), (pubkey1, "11.22.33.44:5555")) - self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]:8888"), (pubkey1, "[2001:41d0:e:734::1]:8888")) - # just pubkey - self.assertEqual(extract_nodeid(f"{pubkey1.hex()}"), (pubkey1, None)) - # bolt11 invoice - self.assertEqual(extract_nodeid("lnbc241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqszrwfgrl5k3rt4q4mclc8t00p2tcjsf9pmpcq6lu5zhmampyvk43fk30eqpdm8t5qmdpzan25aqxqaqdzmy0smrtduazjcxx975vz78ccpx0qhev"), - (bfh("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"), None)) - def test_ln_features_validate_transitive_dependencies(self): features = LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ self.assertTrue(features.validate_transitive_dependencies())