commit 962f70c7da0425098ac580fee999e55e85b3b7e2
parent 52377dbfa04072e2296426a6db2e921157bd6da0
Author: Janus <ysangkok@gmail.com>
Date: Tue, 16 Oct 2018 17:45:28 +0200
ln: add lightning_listen config option
Diffstat:
4 files changed, 170 insertions(+), 39 deletions(-)
diff --git a/electrum/lnbase.py b/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]
diff --git a/electrum/lntransport.py b/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)
diff --git a/electrum/lnworker.py b/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()
diff --git a/electrum/tests/test_lntransport.py b/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())