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 .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

2
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

8
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()

2
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

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.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

3
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)

4
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

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
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

3
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,

103
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<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:
reader: StreamReader
writer: StreamWriter

111
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'<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:
@ -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<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

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 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,

3
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

5
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]

7
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)

45
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))

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_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())

Loading…
Cancel
Save