Browse Source

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.
master
ThomasV 1 year ago
parent
commit
c58c4d7451
  1. 3
      electrum/channel_db.py
  2. 2
      electrum/commands.py
  3. 8
      electrum/crypto.py
  4. 2
      electrum/gui/qml/qechannelopener.py
  5. 2
      electrum/gui/qt/main_window.py
  6. 3
      electrum/lnchannel.py
  7. 4
      electrum/lnonion.py
  8. 7
      electrum/lnpeer.py
  9. 3
      electrum/lnsweep.py
  10. 103
      electrum/lntransport.py
  11. 111
      electrum/lnutil.py
  12. 8
      electrum/lnworker.py
  13. 3
      electrum/trampoline.py
  14. 5
      tests/test_lnchannel.py
  15. 7
      tests/test_lnpeer.py
  16. 45
      tests/test_lntransport.py
  17. 43
      tests/test_lnutil.py

3
electrum/channel_db.py

@ -43,7 +43,8 @@ from .sql_db import SqlDB, sql
from . import constants, util from . import constants, util
from .util import profiler, get_headers_dir, is_ip_address, json_normalize, UserFacingException from .util import profiler, get_headers_dir, is_ip_address, json_normalize, UserFacingException
from .logging import Logger 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) validate_features, IncompatibleOrInsaneFeatures, InvalidGossipMsg)
from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update
from .lnmsg import decode_msg from .lnmsg import decode_msg

2
electrum/commands.py

@ -61,7 +61,7 @@ from .address_synchronizer import TX_HEIGHT_LOCAL
from .mnemonic import Mnemonic from .mnemonic import Mnemonic
from .lnutil import SENT, RECEIVED from .lnutil import SENT, RECEIVED
from .lnutil import LnFeatures from .lnutil import LnFeatures
from .lnutil import extract_nodeid from .lntransport import extract_nodeid
from .lnpeer import channel_id_from_funding_tx from .lnpeer import channel_id_from_funding_tx
from .plugin import run_hook, DeviceMgr, Plugins from .plugin import run_hook, DeviceMgr, Plugins
from .version import ELECTRUM_VERSION from .version import ELECTRUM_VERSION

8
electrum/crypto.py

@ -492,3 +492,11 @@ def ecies_decrypt_message(
if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256): if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256):
raise InvalidPassword() raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext) 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()

2
electrum/gui/qml/qechannelopener.py

@ -8,7 +8,7 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum.i18n import _ from electrum.i18n import _
from electrum.gui import messages from electrum.gui import messages
from electrum.util import bfh 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.bitcoin import DummyAddress
from electrum.lnworker import hardcoded_trampoline_nodes from electrum.lnworker import hardcoded_trampoline_nodes
from electrum.logging import get_logger from electrum.logging import get_logger

2
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.exchange_rate import FxThread
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
from electrum.logging import Logger 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.lnaddr import lndecode
from electrum.submarine_swaps import SwapServerError from electrum.submarine_swaps import SwapServerError

3
electrum/lnchannel.py

@ -43,6 +43,7 @@ from .bitcoin import redeem_script_to_address
from .crypto import sha256, sha256d from .crypto import sha256, sha256d
from .transaction import Transaction, PartialTransaction, TxInput, Sighash from .transaction import Transaction, PartialTransaction, TxInput, Sighash
from .logging import Logger from .logging import Logger
from .lntransport import LNPeerAddr
from .lnonion import OnionFailureCode, OnionRoutingFailure from .lnonion import OnionFailureCode, OnionRoutingFailure
from . import lnutil from . import lnutil
from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, 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, HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc,
funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs, funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs,
ScriptHtlc, PaymentFailure, calc_fees_for_commitment_tx, RemoteMisbehaving, make_htlc_output_witness_script, 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, fee_for_htlc_output, offered_htlc_trim_threshold_sat,
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address,
ChannelType, LNProtocolWarning) ChannelType, LNProtocolWarning)

4
electrum/lnonion.py

@ -30,9 +30,9 @@ from enum import IntEnum
import electrum_ecc as ecc 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 .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) NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag)
from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int
from . import lnmsg from . import lnmsg

7
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 import aiorpcx
from aiorpcx import ignore_after from aiorpcx import ignore_after
from .crypto import sha256, sha256d from .crypto import sha256, sha256d, privkey_to_pubkey
from . import bitcoin, util from . import bitcoin, util
from . import constants from . import constants
from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup, 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, funding_output_script, get_per_commitment_secret_from_seed,
secret_to_pubkey, PaymentFailure, LnFeatures, secret_to_pubkey, PaymentFailure, LnFeatures,
LOCAL, REMOTE, HTLCOwner, LOCAL, REMOTE, HTLCOwner,
ln_compare_features, privkey_to_pubkey, MIN_FINAL_CLTV_DELTA_ACCEPTED, ln_compare_features, MIN_FINAL_CLTV_DELTA_ACCEPTED,
LightningPeerConnectionClosed, HandshakeFailed,
RemoteMisbehaving, ShortChannelID, RemoteMisbehaving, ShortChannelID,
IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage,
ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures) ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures)
from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget
from .lnutil import serialize_htlc_key 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 .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
from .interface import GracefulDisconnect from .interface import GracefulDisconnect
from .lnrouter import fee_for_edge_msat from .lnrouter import fee_for_edge_msat

3
electrum/lnsweep.py

@ -8,6 +8,7 @@ from enum import Enum, auto
import electrum_ecc as ecc import electrum_ecc as ecc
from .util import bfh from .util import bfh
from .crypto import privkey_to_pubkey
from .bitcoin import redeem_script_to_address, dust_threshold, construct_witness from .bitcoin import redeem_script_to_address, dust_threshold, construct_witness
from .invoices import PR_PAID from .invoices import PR_PAID
from . import descriptor 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, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey,
make_htlc_tx_witness, make_htlc_tx_with_open_channel, UpdateAddHtlc, make_htlc_tx_witness, make_htlc_tx_with_open_channel, UpdateAddHtlc,
LOCAL, REMOTE, make_htlc_output_witness_script, 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, RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
map_htlcs_to_ctx_output_idxs, Direction) map_htlcs_to_ctx_output_idxs, Direction)
from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput, from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,

103
electrum/lntransport.py

@ -5,19 +5,23 @@
# Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8 # Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8
import re
import hashlib import hashlib
import asyncio import asyncio
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from typing import Optional from typing import Optional
from functools import cached_property 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 import electrum_ecc as ecc
from .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt from .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt, get_ecdh, privkey_to_pubkey
from .lnutil import (get_ecdh, privkey_to_pubkey, LightningPeerConnectionClosed,
HandshakeFailed, LNPeerAddr)
from .util import MySocksProxy from .util import MySocksProxy
class LightningPeerConnectionClosed(Exception): pass
class HandshakeFailed(Exception): pass
class ConnStringFormatError(Exception): pass
class HandshakeState(object): class HandshakeState(object):
prologue = b"lightning" prologue = b"lightning"
@ -90,6 +94,99 @@ def create_ephemeral_key() -> (bytes, bytes):
return privkey.get_secret_bytes(), privkey.get_public_key_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<host>[:0-9a-f]+)\](?P<port>:\d+)?$')
other = re.compile(r'(?P<host>[^:]+)(?P<port>:\d+)?$')
m = ipv6.match(host_port)
if not m:
m = other.match(host_port)
if not m:
raise ConnStringFormatError('Connection strings must be in <node_pubkey>@<host>:<port> 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'<LNPeerAddr host={self.host} port={self.port} pubkey={self.pubkey.hex()}>'
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: class LNTransportBase:
reader: StreamReader reader: StreamReader
writer: StreamWriter writer: StreamWriter

111
electrum/lnutil.py

@ -13,7 +13,6 @@ import sys
import electrum_ecc as ecc import electrum_ecc as ecc
from electrum_ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number from electrum_ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number
import attr import attr
from aiorpcx import NetAddress
from .util import bfh, inv_dict, UserFacingException from .util import bfh, inv_dict, UserFacingException
from .util import list_enabled_bits from .util import list_enabled_bits
@ -414,10 +413,7 @@ class HtlcLog(NamedTuple):
class LightningError(Exception): pass class LightningError(Exception): pass
class LightningPeerConnectionClosed(LightningError): pass
class UnableToDeriveSecret(LightningError): pass class UnableToDeriveSecret(LightningError): pass
class HandshakeFailed(LightningError): pass
class ConnStringFormatError(LightningError): pass
class RemoteMisbehaving(LightningError): pass class RemoteMisbehaving(LightningError): pass
class NotFoundChanAnnouncementForUpdate(Exception): pass class NotFoundChanAnnouncementForUpdate(Exception): pass
@ -564,8 +560,6 @@ def secret_to_pubkey(secret: int) -> bytes:
assert type(secret) is int assert type(secret) is int
return ecc.ECPrivkey.from_secret_scalar(secret).get_public_key_bytes(compressed=True) 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: def derive_pubkey(basepoint: bytes, per_commitment_point: bytes) -> bytes:
p = ecc.ECPubkey(basepoint) + ecc.GENERATOR * ecc.string_to_number(sha256(per_commitment_point + basepoint)) p = ecc.ECPubkey(basepoint) + ecc.GENERATOR * ecc.string_to_number(sha256(per_commitment_point + basepoint))
@ -1121,10 +1115,6 @@ def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> in
funder_payment_basepoint=funder_conf.payment_basepoint.pubkey, funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
fundee_payment_basepoint=fundee_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): class LnFeatureContexts(enum.Flag):
INIT = enum.auto() INIT = enum.auto()
@ -1480,54 +1470,6 @@ def derive_payment_secret_from_payment_preimage(payment_preimage: bytes) -> byte
return sha256(bytes(modified)) 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'<LNPeerAddr host={self.host} port={self.port} pubkey={self.pubkey.hex()}>'
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: 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 return tx
def split_host_port(host_port: str) -> Tuple[str, str]: # port returned as string
ipv6 = re.compile(r'\[(?P<host>[:0-9a-f]+)\](?P<port>:\d+)?$')
other = re.compile(r'(?P<host>[^:]+)(?P<port>:\d+)?$')
m = ipv6.match(host_port)
if not m:
m = other.match(host_port)
if not m:
raise ConnStringFormatError(_('Connection strings must be in <node_pubkey>@<host>:<port> 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 # key derivation

8
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 timestamp_to_datetime, random_shuffled_copy
from .util import MyEncoder, is_private_netaddress, UnrelatedTransactionException from .util import MyEncoder, is_private_netaddress, UnrelatedTransactionException
from .logging import Logger 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 .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
from .lnaddr import lnencode, LnAddr, lndecode from .lnaddr import lnencode, LnAddr, lndecode
from .lnchannel import Channel, AbstractChannel from .lnchannel import Channel, AbstractChannel
@ -59,9 +59,9 @@ from . import lnutil
from .lnutil import funding_output_script from .lnutil import funding_output_script
from .lnutil import serialize_htlc_key, deserialize_htlc_key from .lnutil import serialize_htlc_key, deserialize_htlc_key
from .bitcoin import DummyAddress from .bitcoin import DummyAddress
from .lnutil import (Outpoint, LNPeerAddr, from .lnutil import (Outpoint,
get_compressed_pubkey_from_bech32, extract_nodeid, get_compressed_pubkey_from_bech32,
PaymentFailure, split_host_port, ConnStringFormatError, PaymentFailure,
generate_keypair, LnKeyFamily, LOCAL, REMOTE, generate_keypair, LnKeyFamily, LOCAL, REMOTE,
MIN_FINAL_CLTV_DELTA_FOR_INVOICE, MIN_FINAL_CLTV_DELTA_FOR_INVOICE,
NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner, NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner,

3
electrum/trampoline.py

@ -6,7 +6,8 @@ from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable,
from .lnutil import LnFeatures, PaymentFeeBudget from .lnutil import LnFeatures, PaymentFeeBudget
from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket
from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_within_budget, LNPaymentTRoute 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 . import constants
from .logging import get_logger from .logging import get_logger

5
tests/test_lnchannel.py

@ -33,6 +33,7 @@ from electrum import lnpeer
from electrum import lnchannel from electrum import lnchannel
from electrum import lnutil from electrum import lnutil
from electrum import bip32 as bip32_utils 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.lnutil import SENT, LOCAL, REMOTE, RECEIVED, UpdateAddHtlc
from electrum.logging import console_stderr_handler from electrum.logging import console_stderr_handler
from electrum.lnchannel import ChannelState 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) 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)] alice_raw = [bip32("m/" + str(i)) for i in range(5)]
bob_raw = [bip32("m/" + str(i)) for i in range(5,11)] 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] alice_privkeys = [lnutil.Keypair(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] 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] alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys]
bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys] bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys]

7
tests/test_lnpeer.py

@ -27,8 +27,9 @@ from electrum.bitcoin import COIN, sha256
from electrum.transaction import Transaction from electrum.transaction import Transaction
from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError
from electrum.lnpeer import Peer from electrum.lnpeer import Peer
from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey from electrum.lntransport import LNPeerAddr
from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner, PaymentFeeBudget 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.lnchannel import ChannelState, PeerState, Channel
from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent
from electrum.channel_db import ChannelDB from electrum.channel_db import ChannelDB
@ -1209,7 +1210,7 @@ class TestPeerDirect(TestPeer):
# create upfront shutdown script for bob, alice doesn't use upfront # create upfront shutdown script for bob, alice doesn't use upfront
# shutdown script # 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_addr = bitcoin.pubkey_to_address('p2wpkh', bob_uss_pub.hex())
bob_uss = bitcoin.address_to_script(bob_uss_addr) bob_uss = bitcoin.address_to_script(bob_uss_addr)

45
tests/test_lntransport.py

@ -1,10 +1,9 @@
import asyncio import asyncio
from electrum_ecc import ECPrivkey import electrum_ecc as ecc
from electrum import util from electrum import util
from electrum.lnutil import LNPeerAddr from electrum.lntransport import LNPeerAddr, LNResponderTransport, LNTransport, extract_nodeid, split_host_port, ConnStringFormatError
from electrum.lntransport import LNResponderTransport, LNTransport
from electrum.util import OldTaskGroup from electrum.util import OldTaskGroup
from . import ElectrumTestCase from . import ElectrumTestCase
@ -46,8 +45,8 @@ class TestLNTransport(ElectrumTestCase):
async def test_loop(self): async def test_loop(self):
responder_shaked = asyncio.Event() responder_shaked = asyncio.Event()
server_shaked = asyncio.Event() server_shaked = asyncio.Event()
responder_key = ECPrivkey.generate_random_key() responder_key = ecc.ECPrivkey.generate_random_key()
initiator_key = ECPrivkey.generate_random_key() initiator_key = ecc.ECPrivkey.generate_random_key()
messages_sent_by_client = [ messages_sent_by_client = [
b'hello from client', b'hello from client',
b'long data from client ' + bytes(range(256)) * 100 + b'... client done', b'long data from client ' + bytes(range(256)) * 100 + b'... client done',
@ -98,3 +97,39 @@ class TestLNTransport(ElectrumTestCase):
server.close() server.close()
await f() 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))

43
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_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output,
make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey, make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey,
derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret, derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret,
get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError, get_compressed_pubkey_from_bech32,
ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, ScriptHtlc, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures,
ln_compare_features, IncompatibleLightningFeatures, ChannelType, ln_compare_features, IncompatibleLightningFeatures, ChannelType,
ImportedChannelBackupStorage) ImportedChannelBackupStorage)
from electrum.util import bfh, MyEncoder 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', 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')) 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): def test_ln_features_validate_transitive_dependencies(self):
features = LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ features = LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
self.assertTrue(features.validate_transitive_dependencies()) self.assertTrue(features.validate_transitive_dependencies())

Loading…
Cancel
Save