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))
async def initialize(self):
await self.transport.handshake()
self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures)
self.initialized.set_result(True)
@ -342,7 +341,7 @@ class Peer(PrintError):
try:
await asyncio.wait_for(self.initialize(), 10)
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
self.channel_db.add_recent_peer(self.peer_addr)
# loop
@ -530,7 +529,7 @@ class Peer(PrintError):
their_revocation_store = RevocationStore()
remote_balance_sat = funding_sat * 1000 - push_msat
chan = {
"node_id": self.pubkey,
"node_id": self.peer_addr.pubkey,
"channel_id": channel_id,
"short_channel_id": None,
"funding_outpoint": Outpoint(funding_txid, funding_idx),
@ -726,7 +725,7 @@ class Peer(PrintError):
remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"]
if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h):
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")
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)
return self.h
def get_nonce_bytes(n):
"""BOLT 8 requires the nonce to be 12 bytes, 4 bytes leading
zeroes and 8 bytes little endian encoded 64 bit integer.
@ -60,29 +59,22 @@ def get_bolt8_hkdf(salt, ikm):
return T1, T2
def act1_initiator_message(hs, epriv, epub):
hs.update(epub)
ss = get_ecdh(epriv, hs.responder_pub)
ck2, temp_k1 = get_bolt8_hkdf(hs.ck, ss)
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
hs.update(c)
msg = hs.handshake_version + epub + c
assert len(msg) == 50
return msg
return msg, temp_k1
def create_ephemeral_key() -> (bytes, bytes):
privkey = ecc.ECPrivkey.generate_random_key()
return privkey.get_secret_bytes(), privkey.get_public_key_bytes()
class LNTransport:
def __init__(self, privkey, remote_pubkey, reader, writer):
self.privkey = privkey
self.remote_pubkey = remote_pubkey
self.reader = reader
self.writer = writer
class LNTransportBase:
def send_bytes(self, msg):
l = len(msg).to_bytes(2, 'big')
lc = aead_encrypt(self.sk, self.sn(), b'', l)
@ -116,12 +108,97 @@ class LNTransport:
raise LightningPeerConnectionClosed()
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):
hs = HandshakeState(self.remote_pubkey)
# Get a new 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
self.writer.write(msg)
rspns = await self.reader.read(2**10)
@ -145,27 +222,7 @@ class LNTransport:
ck, temp_k3 = get_bolt8_hkdf(hs.ck, ss)
hs.ck = ck
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
self.writer.write(msg)
# init counters
self._sn = 0
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
self.sk, self.rk = get_bolt8_hkdf(hs.ck, b'')
self.init_counters(ck)

19
electrum/lnworker.py

@ -16,7 +16,7 @@ from . import bitcoin
from .keystore import BIP32_KeyStore
from .bitcoin import sha256, COIN
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 .lnaddr import lnencode, LnAddr, lndecode
from .ecc import der_sig_from_sig_string
@ -119,6 +119,7 @@ class LNWorker(PrintError):
async def _init_peer():
reader, writer = await asyncio.open_connection(peer_addr.host, peer_addr.port)
transport = LNTransport(self.node_keypair.privkey, node_id, reader, writer)
await transport.handshake()
peer.transport = transport
await self.network.main_taskgroup.spawn(peer.main_loop())
asyncio.ensure_future(_init_peer())
@ -493,6 +494,22 @@ class LNWorker(PrintError):
async def main_loop(self):
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
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:
await asyncio.sleep(1)
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