ln: add lightning_listen config option
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Normal file
58
electrum/tests/test_lntransport.py
Normal file
@@ -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())
|
||||
Reference in New Issue
Block a user