Browse Source

ln: add lightning_listen config option

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
962f70c7da
  1. 7
      electrum/lnbase.py
  2. 125
      electrum/lntransport.py
  3. 19
      electrum/lnworker.py
  4. 58
      electrum/tests/test_lntransport.py

7
electrum/lnbase.py

@ -220,7 +220,6 @@ class Peer(PrintError):
self.transport.send_bytes(gen_msg(message_name, **kwargs)) self.transport.send_bytes(gen_msg(message_name, **kwargs))
async def initialize(self): async def initialize(self):
await self.transport.handshake()
self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures) self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures)
self.initialized.set_result(True) self.initialized.set_result(True)
@ -342,7 +341,7 @@ class Peer(PrintError):
try: try:
await asyncio.wait_for(self.initialize(), 10) await asyncio.wait_for(self.initialize(), 10)
except (OSError, asyncio.TimeoutError, HandshakeFailed) as e: except (OSError, asyncio.TimeoutError, HandshakeFailed) as e:
self.print_error('disconnecting due to: {}'.format(repr(e))) self.print_error('initialize failed, disconnecting: {}'.format(repr(e)))
return return
self.channel_db.add_recent_peer(self.peer_addr) self.channel_db.add_recent_peer(self.peer_addr)
# loop # loop
@ -530,7 +529,7 @@ class Peer(PrintError):
their_revocation_store = RevocationStore() their_revocation_store = RevocationStore()
remote_balance_sat = funding_sat * 1000 - push_msat remote_balance_sat = funding_sat * 1000 - push_msat
chan = { chan = {
"node_id": self.pubkey, "node_id": self.peer_addr.pubkey,
"channel_id": channel_id, "channel_id": channel_id,
"short_channel_id": None, "short_channel_id": None,
"funding_outpoint": Outpoint(funding_txid, funding_idx), "funding_outpoint": Outpoint(funding_txid, funding_idx),
@ -726,7 +725,7 @@ class Peer(PrintError):
remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"] remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"]
if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h): if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h):
raise Exception("bitcoin_sig invalid in announcement_signatures") raise Exception("bitcoin_sig invalid in announcement_signatures")
if not ecc.verify_signature(self.pubkey, remote_node_sig, h): if not ecc.verify_signature(self.peer_addr.pubkey, remote_node_sig, h):
raise Exception("node_sig invalid in announcement_signatures") raise Exception("node_sig invalid in announcement_signatures")
node_sigs = [local_node_sig, remote_node_sig] node_sigs = [local_node_sig, remote_node_sig]

125
electrum/lntransport.py

@ -23,7 +23,6 @@ class HandshakeState(object):
self.h = sha256(self.h + data) self.h = sha256(self.h + data)
return self.h return self.h
def get_nonce_bytes(n): def get_nonce_bytes(n):
"""BOLT 8 requires the nonce to be 12 bytes, 4 bytes leading """BOLT 8 requires the nonce to be 12 bytes, 4 bytes leading
zeroes and 8 bytes little endian encoded 64 bit integer. zeroes and 8 bytes little endian encoded 64 bit integer.
@ -60,29 +59,22 @@ def get_bolt8_hkdf(salt, ikm):
return T1, T2 return T1, T2
def act1_initiator_message(hs, epriv, epub): def act1_initiator_message(hs, epriv, epub):
hs.update(epub)
ss = get_ecdh(epriv, hs.responder_pub) ss = get_ecdh(epriv, hs.responder_pub)
ck2, temp_k1 = get_bolt8_hkdf(hs.ck, ss) ck2, temp_k1 = get_bolt8_hkdf(hs.ck, ss)
hs.ck = ck2 hs.ck = ck2
c = aead_encrypt(temp_k1, 0, hs.h, b"") c = aead_encrypt(temp_k1, 0, hs.update(epub), b"")
#for next step if we do it #for next step if we do it
hs.update(c) hs.update(c)
msg = hs.handshake_version + epub + c msg = hs.handshake_version + epub + c
assert len(msg) == 50 assert len(msg) == 50
return msg return msg, temp_k1
def create_ephemeral_key() -> (bytes, bytes): def create_ephemeral_key() -> (bytes, bytes):
privkey = ecc.ECPrivkey.generate_random_key() privkey = ecc.ECPrivkey.generate_random_key()
return privkey.get_secret_bytes(), privkey.get_public_key_bytes() return privkey.get_secret_bytes(), privkey.get_public_key_bytes()
class LNTransport: class LNTransportBase:
def __init__(self, privkey, remote_pubkey, reader, writer):
self.privkey = privkey
self.remote_pubkey = remote_pubkey
self.reader = reader
self.writer = writer
def send_bytes(self, msg): def send_bytes(self, msg):
l = len(msg).to_bytes(2, 'big') l = len(msg).to_bytes(2, 'big')
lc = aead_encrypt(self.sk, self.sn(), b'', l) lc = aead_encrypt(self.sk, self.sn(), b'', l)
@ -116,12 +108,97 @@ class LNTransport:
raise LightningPeerConnectionClosed() raise LightningPeerConnectionClosed()
read_buffer += s read_buffer += s
def rn(self):
o = self._rn, self.rk
self._rn += 1
if self._rn == 1000:
self.r_ck, self.rk = get_bolt8_hkdf(self.r_ck, self.rk)
self._rn = 0
return o
def sn(self):
o = self._sn
self._sn += 1
if self._sn == 1000:
self.s_ck, self.sk = get_bolt8_hkdf(self.s_ck, self.sk)
self._sn = 0
return o
def init_counters(self, ck):
# init counters
self._sn = 0
self._rn = 0
self.r_ck = ck
self.s_ck = ck
class LNResponderTransport(LNTransportBase):
def __init__(self, privkey, reader, writer):
self.privkey = privkey
self.reader = reader
self.writer = writer
async def handshake(self, **kwargs):
hs = HandshakeState(privkey_to_pubkey(self.privkey))
act1 = b''
while len(act1) < 50:
act1 += await self.reader.read(50 - len(act1))
if len(act1) != 50:
raise HandshakeFailed('responder: short act 1 read, length is ' + str(len(act1)))
if bytes([act1[0]]) != HandshakeState.handshake_version:
raise HandshakeFailed('responder: bad handshake version in act 1')
c = act1[-16:]
re = act1[1:34]
h = hs.update(re)
ss = get_ecdh(self.privkey, re)
ck, temp_k1 = get_bolt8_hkdf(sha256(HandshakeState.protocol_name), ss)
_p = aead_decrypt(temp_k1, 0, h, c)
hs.update(c)
# act 2
if 'epriv' not in kwargs:
epriv, epub = create_ephemeral_key()
else:
epriv = kwargs['epriv']
epub = ecc.ECPrivkey(epriv).get_public_key_bytes()
hs.ck = ck
hs.responder_pub = re
msg, temp_k2 = act1_initiator_message(hs, epriv, epub)
self.writer.write(msg)
# act 3
act3 = b''
while len(act3) < 66:
act3 += await self.reader.read(66 - len(act3))
if len(act3) != 66:
raise HandshakeFailed('responder: short act 3 read, length is ' + str(len(act3)))
if bytes([act3[0]]) != HandshakeState.handshake_version:
raise HandshakeFailed('responder: bad handshake version in act 3')
c = act3[1:50]
t = act3[-16:]
rs = aead_decrypt(temp_k2, 1, hs.h, c)
ss = get_ecdh(epriv, rs)
ck, temp_k3 = get_bolt8_hkdf(hs.ck, ss)
_p = aead_decrypt(temp_k3, 0, hs.update(c), t)
self.rk, self.sk = get_bolt8_hkdf(ck, b'')
self.init_counters(ck)
return rs
class LNTransport(LNTransportBase):
def __init__(self, privkey, remote_pubkey, reader, writer):
assert type(privkey) is bytes and len(privkey) == 32
self.privkey = privkey
self.remote_pubkey = remote_pubkey
self.reader = reader
self.writer = writer
async def handshake(self): async def handshake(self):
hs = HandshakeState(self.remote_pubkey) hs = HandshakeState(self.remote_pubkey)
# Get a new ephemeral key # Get a new ephemeral key
epriv, epub = create_ephemeral_key() epriv, epub = create_ephemeral_key()
msg = act1_initiator_message(hs, epriv, epub) msg, _temp_k1 = act1_initiator_message(hs, epriv, epub)
# act 1 # act 1
self.writer.write(msg) self.writer.write(msg)
rspns = await self.reader.read(2**10) rspns = await self.reader.read(2**10)
@ -145,27 +222,7 @@ class LNTransport:
ck, temp_k3 = get_bolt8_hkdf(hs.ck, ss) ck, temp_k3 = get_bolt8_hkdf(hs.ck, ss)
hs.ck = ck hs.ck = ck
t = aead_encrypt(temp_k3, 0, hs.h, b'') t = aead_encrypt(temp_k3, 0, hs.h, b'')
self.sk, self.rk = get_bolt8_hkdf(hs.ck, b'')
msg = hs.handshake_version + c + t msg = hs.handshake_version + c + t
self.writer.write(msg) self.writer.write(msg)
# init counters self.sk, self.rk = get_bolt8_hkdf(hs.ck, b'')
self._sn = 0 self.init_counters(ck)
self._rn = 0
self.r_ck = ck
self.s_ck = ck
def rn(self):
o = self._rn, self.rk
self._rn += 1
if self._rn == 1000:
self.r_ck, self.rk = get_bolt8_hkdf(self.r_ck, self.rk)
self._rn = 0
return o
def sn(self):
o = self._sn
self._sn += 1
if self._sn == 1000:
self.s_ck, self.sk = get_bolt8_hkdf(self.s_ck, self.sk)
self._sn = 0
return o

19
electrum/lnworker.py

@ -16,7 +16,7 @@ from . import bitcoin
from .keystore import BIP32_KeyStore from .keystore import BIP32_KeyStore
from .bitcoin import sha256, COIN from .bitcoin import sha256, COIN
from .util import bh2u, bfh, PrintError, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions from .util import bh2u, bfh, PrintError, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions
from .lntransport import LNTransport from .lntransport import LNTransport, LNResponderTransport
from .lnbase import Peer from .lnbase import Peer
from .lnaddr import lnencode, LnAddr, lndecode from .lnaddr import lnencode, LnAddr, lndecode
from .ecc import der_sig_from_sig_string from .ecc import der_sig_from_sig_string
@ -119,6 +119,7 @@ class LNWorker(PrintError):
async def _init_peer(): async def _init_peer():
reader, writer = await asyncio.open_connection(peer_addr.host, peer_addr.port) reader, writer = await asyncio.open_connection(peer_addr.host, peer_addr.port)
transport = LNTransport(self.node_keypair.privkey, node_id, reader, writer) transport = LNTransport(self.node_keypair.privkey, node_id, reader, writer)
await transport.handshake()
peer.transport = transport peer.transport = transport
await self.network.main_taskgroup.spawn(peer.main_loop()) await self.network.main_taskgroup.spawn(peer.main_loop())
asyncio.ensure_future(_init_peer()) asyncio.ensure_future(_init_peer())
@ -493,6 +494,22 @@ class LNWorker(PrintError):
async def main_loop(self): async def main_loop(self):
await self.on_network_update('network_updated') # shortcut (don't block) if funding tx locked and verified await self.on_network_update('network_updated') # shortcut (don't block) if funding tx locked and verified
await self.network.lnwatcher.on_network_update('network_updated') # ping watcher to check our channels await self.network.lnwatcher.on_network_update('network_updated') # ping watcher to check our channels
listen_addr = self.config.get('lightning_listen')
if listen_addr:
adr, colon, port = listen_addr.rpartition(':')
if adr[0] == '[':
# ipv6
adr = adr[1:-1]
async def cb(reader, writer):
t = LNResponderTransport(self.node_keypair.privkey, reader, writer)
node_id = await t.handshake()
peer = Peer(self, LNPeerAddr("bogus", 1337, node_id), request_initial_sync=self.config.get("request_initial_sync", True))
peer.transport = t
self.peers[node_id] = peer
await self.network.main_taskgroup.spawn(peer.main_loop())
self.network.trigger_callback('ln_status')
await asyncio.start_server(cb, adr, int(port))
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)
now = time.time() now = time.time()

58
electrum/tests/test_lntransport.py

@ -0,0 +1,58 @@
from electrum.ecc import ECPrivkey
import asyncio
from electrum.lntransport import LNResponderTransport, LNTransport
from unittest import TestCase
class TestLNTransport(TestCase):
def test_responder(self):
# local static
ls_priv=bytes.fromhex('2121212121212121212121212121212121212121212121212121212121212121')
# ephemeral
e_priv=bytes.fromhex('2222222222222222222222222222222222222222222222222222222222222222')
class Writer:
def __init__(self):
self.state = 0
def write(self, data):
assert self.state == 0
self.state += 1
assert len(data) == 50
class Reader:
def __init__(self):
self.state = 0
async def read(self, num_bytes):
assert self.state in (0, 1)
self.state += 1
if self.state-1 == 0:
assert num_bytes == 50
return bytes.fromhex('00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a')
elif self.state-1 == 1:
assert num_bytes == 66
return bytes.fromhex('00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba')
transport = LNResponderTransport(ls_priv, Reader(), Writer())
asyncio.get_event_loop().run_until_complete(transport.handshake(epriv=e_priv))
def test_loop(self):
l = asyncio.get_event_loop()
responder_shaked = asyncio.Event()
server_shaked = asyncio.Event()
responder_key = ECPrivkey.generate_random_key()
initiator_key = ECPrivkey.generate_random_key()
async def cb(reader, writer):
t = LNResponderTransport(responder_key.get_secret_bytes(), reader, writer)
self.assertEqual(await t.handshake(), initiator_key.get_public_key_bytes())
t.send_bytes(b'hello from server')
self.assertEqual(await t.read_messages().__anext__(), b'hello from client')
responder_shaked.set()
server_future = asyncio.ensure_future(asyncio.start_server(cb, '127.0.0.1', 42898))
l.run_until_complete(server_future)
async def connect():
reader, writer = await asyncio.open_connection('127.0.0.1', 42898)
t = LNTransport(initiator_key.get_secret_bytes(), responder_key.get_public_key_bytes(), reader, writer)
await t.handshake()
t.send_bytes(b'hello from client')
self.assertEqual(await t.read_messages().__anext__(), b'hello from server')
server_shaked.set()
asyncio.ensure_future(connect())
l.run_until_complete(responder_shaked.wait())
l.run_until_complete(server_shaked.wait())
Loading…
Cancel
Save