From 5791ed1b66cf7f12ae1937296db3e5d12fd9ec89 Mon Sep 17 00:00:00 2001 From: Janus Date: Wed, 27 Jun 2018 18:33:55 +0200 Subject: [PATCH 1/5] ln: store HTLCStateMachine in lnworker.channels --- gui/qt/channels_list.py | 7 +- lib/commands.py | 2 +- lib/lnbase.py | 173 ++++++++++++++++++++------------------- lib/lnhtlc.py | 16 ++++ lib/lnwatcher.py | 2 +- lib/lnworker.py | 77 ++++++++--------- lib/tests/test_lnhtlc.py | 36 +++++--- 7 files changed, 176 insertions(+), 137 deletions(-) diff --git a/gui/qt/channels_list.py b/gui/qt/channels_list.py index 49092301..72e200e7 100644 --- a/gui/qt/channels_list.py +++ b/gui/qt/channels_list.py @@ -33,7 +33,8 @@ class ChannelsList(MyTreeWidget): channel_id = self.currentItem().data(0, QtCore.Qt.UserRole) print('ID', bh2u(channel_id)) def close(): - self.parent.wallet.lnworker.close_channel(channel_id) + suc, msg = self.parent.wallet.lnworker.close_channel(channel_id) + assert suc # TODO show error message in dialog menu.addAction(_("Close channel"), close) menu.exec_(self.viewport().mapToGlobal(position)) @@ -49,8 +50,8 @@ class ChannelsList(MyTreeWidget): def do_update_rows(self): self.clear() for chan in self.parent.wallet.lnworker.channels.values(): - item = SortableTreeWidgetItem(self.format_fields(chan)) - item.setData(0, QtCore.Qt.UserRole, chan.channel_id) + item = SortableTreeWidgetItem(self.format_fields(chan.state)) + item.setData(0, QtCore.Qt.UserRole, chan.state.channel_id) self.insertTopLevelItem(0, item) def get_toolbar(self): diff --git a/lib/commands.py b/lib/commands.py index 3eda752c..c50e450e 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -684,7 +684,7 @@ class Commands: # lightning network commands @command('wpn') def open_channel(self, node_id, amount, channel_push=0, password=None): - f = self.wallet.lnworker.open_channel(node_id, satoshis(amount), satoshis(channel_push), password) + f = self.wallet.lnworker.open_channel(bytes.fromhex(node_id), satoshis(amount), satoshis(channel_push), password) return f.result() @command('wn') diff --git a/lib/lnbase.py b/lib/lnbase.py index 4b29aec3..8cbbcb21 100644 --- a/lib/lnbase.py +++ b/lib/lnbase.py @@ -776,7 +776,7 @@ class Peer(PrintError): def on_announcement_signatures(self, payload): channel_id = payload['channel_id'] chan = self.channels[payload['channel_id']] - if chan.local_state.was_announced: + if chan.state.local_state.was_announced: h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) else: self.announcement_signatures[channel_id].put_nowait(payload) @@ -927,22 +927,23 @@ class Peer(PrintError): # broadcast funding tx success, _txid = self.network.broadcast_transaction(funding_tx) assert success, success - return chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0, current_commitment_signature=remote_sig)) + m.state = chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0, current_commitment_signature=remote_sig)) + return m @aiosafe async def reestablish_channel(self, chan): await self.initialized - chan_id = chan.channel_id + chan_id = chan.state.channel_id self.channel_state[chan_id] = 'REESTABLISHING' self.network.trigger_callback('channel', chan) self.send_message(gen_msg("channel_reestablish", channel_id=chan_id, - next_local_commitment_number=chan.local_state.ctn+1, - next_remote_revocation_number=chan.remote_state.ctn + next_local_commitment_number=chan.state.local_state.ctn+1, + next_remote_revocation_number=chan.state.remote_state.ctn )) await self.channel_reestablished[chan_id] self.channel_state[chan_id] = 'OPENING' - if chan.local_state.funding_locked_received and chan.short_channel_id: + if chan.state.local_state.funding_locked_received and chan.state.short_channel_id: self.mark_open(chan) self.network.trigger_callback('channel', chan) @@ -955,26 +956,26 @@ class Peer(PrintError): return channel_reestablish_msg = payload remote_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big') - if remote_ctn != chan.remote_state.ctn + 1: - raise Exception("expected remote ctn {}, got {}".format(chan.remote_state.ctn + 1, remote_ctn)) + if remote_ctn != chan.state.remote_state.ctn + 1: + raise Exception("expected remote ctn {}, got {}".format(chan.state.remote_state.ctn + 1, remote_ctn)) local_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big') - if local_ctn != chan.local_state.ctn: - raise Exception("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn)) + if local_ctn != chan.state.local_state.ctn: + raise Exception("expected local ctn {}, got {}".format(chan.state.local_state.ctn, local_ctn)) their = channel_reestablish_msg["my_current_per_commitment_point"] - our = chan.remote_state.current_per_commitment_point + our = chan.state.remote_state.current_per_commitment_point if our is None: - our = chan.remote_state.next_per_commitment_point + our = chan.state.remote_state.next_per_commitment_point if our != their: raise Exception("Remote PCP mismatch: {} {}".format(bh2u(our), bh2u(their))) self.channel_reestablished[chan_id].set_result(True) def funding_locked(self, chan): - channel_id = chan.channel_id + channel_id = chan.state.channel_id per_commitment_secret_index = 2**48 - 2 per_commitment_point_second = secret_to_pubkey(int.from_bytes( - get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big')) + get_per_commitment_secret_from_seed(chan.state.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big')) self.send_message(gen_msg("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)) - if chan.local_state.funding_locked_received: + if chan.state.local_state.funding_locked_received: self.mark_open(chan) def on_funding_locked(self, payload): @@ -982,14 +983,14 @@ class Peer(PrintError): chan = self.channels.get(channel_id) if not chan: raise Exception("Got unknown funding_locked", channel_id) - if not chan.local_state.funding_locked_received: - our_next_point = chan.remote_state.next_per_commitment_point + if not chan.state.local_state.funding_locked_received: + our_next_point = chan.state.remote_state.next_per_commitment_point their_next_point = payload["next_per_commitment_point"] - new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) - new_local_state = chan.local_state._replace(funding_locked_received = True) - chan = chan._replace(remote_state=new_remote_state, local_state=new_local_state) + new_remote_state = chan.state.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) + new_local_state = chan.state.local_state._replace(funding_locked_received = True) + chan.state = chan.state._replace(remote_state=new_remote_state, local_state=new_local_state) self.lnworker.save_channel(chan) - if chan.short_channel_id: + if chan.state.short_channel_id: self.mark_open(chan) def on_network_update(self, chan, funding_tx_depth): @@ -998,8 +999,8 @@ class Peer(PrintError): Runs on the Network thread. """ - if not chan.local_state.was_announced and funding_tx_depth >= 6: - chan = chan._replace(local_state=chan.local_state._replace(was_announced=True)) + if not chan.state.local_state.was_announced and funding_tx_depth >= 6: + chan.state = chan.state._replace(local_state=chan.state.local_state._replace(was_announced=True)) coro = self.handle_announcements(chan) self.lnworker.save_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) @@ -1007,10 +1008,10 @@ class Peer(PrintError): @aiosafe async def handle_announcements(self, chan): h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) - announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get() + announcement_signatures_msg = await self.announcement_signatures[chan.state.channel_id].get() remote_node_sig = announcement_signatures_msg["node_signature"] remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"] - if not ecc.verify_signature(chan.remote_config.multisig_key.pubkey, remote_bitcoin_sig, h): + if not ecc.verify_signature(chan.state.remote_config.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): raise Exception("node_sig invalid in announcement_signatures") @@ -1018,7 +1019,7 @@ class Peer(PrintError): node_sigs = [local_node_sig, remote_node_sig] bitcoin_sigs = [local_bitcoin_sig, remote_bitcoin_sig] node_ids = [privkey_to_pubkey(self.privkey), self.pubkey] - bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey] + bitcoin_keys = [chan.state.local_config.multisig_key.pubkey, chan.state.remote_config.multisig_key.pubkey] if node_ids[0] > node_ids[1]: node_sigs.reverse() @@ -1034,7 +1035,7 @@ class Peer(PrintError): len=0, #features not set (defaults to zeros) chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), - short_channel_id=chan.short_channel_id, + short_channel_id=chan.state.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], bitcoin_key_1=bitcoin_keys[0], @@ -1046,23 +1047,23 @@ class Peer(PrintError): print("SENT CHANNEL ANNOUNCEMENT") def mark_open(self, chan): - if self.channel_state[chan.channel_id] == "OPEN": + if self.channel_state[chan.state.channel_id] == "OPEN": return - assert chan.local_state.funding_locked_received - self.channel_state[chan.channel_id] = "OPEN" - self.network.trigger_callback('channel', chan) + assert chan.state.local_state.funding_locked_received + self.channel_state[chan.state.channel_id] = "OPEN" + self.network.trigger_callback('channel', chan.state) # add channel to database sorted_keys = list(sorted([self.pubkey, self.lnworker.pubkey])) - self.channel_db.on_channel_announcement({"short_channel_id": chan.short_channel_id, "node_id_1": sorted_keys[0], "node_id_2": sorted_keys[1]}) - self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x01', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) - self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x00', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) + self.channel_db.on_channel_announcement({"short_channel_id": chan.state.short_channel_id, "node_id_1": sorted_keys[0], "node_id_2": sorted_keys[1]}) + self.channel_db.on_channel_update({"short_channel_id": chan.state.short_channel_id, 'flags': b'\x01', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) + self.channel_db.on_channel_update({"short_channel_id": chan.state.short_channel_id, 'flags': b'\x00', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) self.print_error("CHANNEL OPENING COMPLETED") def send_announcement_signatures(self, chan): - bitcoin_keys = [chan.local_config.multisig_key.pubkey, - chan.remote_config.multisig_key.pubkey] + bitcoin_keys = [chan.state.local_config.multisig_key.pubkey, + chan.state.remote_config.multisig_key.pubkey] node_ids = [privkey_to_pubkey(self.privkey), self.pubkey] @@ -1076,7 +1077,7 @@ class Peer(PrintError): len=0, #features not set (defaults to zeros) chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), - short_channel_id=chan.short_channel_id, + short_channel_id=chan.state.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], bitcoin_key_1=bitcoin_keys[0], @@ -1084,11 +1085,11 @@ class Peer(PrintError): ) to_hash = chan_ann[256+2:] h = bitcoin.Hash(to_hash) - bitcoin_signature = ecc.ECPrivkey(chan.local_config.multisig_key.privkey).sign(h, sigencode_string_canonize, sigdecode_string) + bitcoin_signature = ecc.ECPrivkey(chan.state.local_config.multisig_key.privkey).sign(h, sigencode_string_canonize, sigdecode_string) node_signature = ecc.ECPrivkey(self.privkey).sign(h, sigencode_string_canonize, sigdecode_string) self.send_message(gen_msg("announcement_signatures", - channel_id=chan.channel_id, - short_channel_id=chan.short_channel_id, + channel_id=chan.state.channel_id, + short_channel_id=chan.state.short_channel_id, node_signature=node_signature, bitcoin_signature=bitcoin_signature )) @@ -1123,7 +1124,7 @@ class Peer(PrintError): @aiosafe async def pay(self, path, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): - assert self.channel_state[chan.channel_id] == "OPEN" + assert self.channel_state[chan.state.channel_id] == "OPEN" assert amount_msat > 0, "amount_msat is not greater zero" height = self.network.get_local_height() route = self.network.path_finder.create_route_from_path(path, self.lnworker.pubkey) @@ -1139,62 +1140,61 @@ class Peer(PrintError): self.secret_key = os.urandom(32) hops_data += [OnionHopsDataSingle(OnionPerHop(b"\x00"*8, amount_msat.to_bytes(8, "big"), (final_cltv_expiry_without_deltas).to_bytes(4, "big")))] onion = new_onion_packet([x.node_id for x in route], self.secret_key, hops_data, associated_data) - msat_local = chan.local_state.amount_msat - (amount_msat + total_fee) - msat_remote = chan.remote_state.amount_msat + (amount_msat + total_fee) + msat_local = chan.state.local_state.amount_msat - (amount_msat + total_fee) + msat_remote = chan.state.remote_state.amount_msat + (amount_msat + total_fee) htlc = UpdateAddHtlc(amount_msat, payment_hash, final_cltv_expiry_with_deltas, total_fee) amount_msat += total_fee - self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=chan.local_state.next_htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) + self.send_message(gen_msg("update_add_htlc", channel_id=chan.state.channel_id, id=chan.state.local_state.next_htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) - m = HTLCStateMachine(chan) - m.add_htlc(htlc) - self.attempted_route[(chan.channel_id, htlc.htlc_id)] = route + chan.add_htlc(htlc) + self.attempted_route[(chan.state.channel_id, htlc.htlc_id)] = route - sig_64, htlc_sigs = m.sign_next_commitment() + sig_64, htlc_sigs = chan.sign_next_commitment() htlc_sig = htlc_sigs[0] - self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) + self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) - await self.receive_revoke(m) + await self.receive_revoke(chan) - self.revoke(m) + self.revoke(chan) - fulfill_coro = asyncio.ensure_future(self.update_fulfill_htlc[chan.channel_id].get()) - failure_coro = asyncio.ensure_future(self.update_fail_htlc[chan.channel_id].get()) + fulfill_coro = asyncio.ensure_future(self.update_fulfill_htlc[chan.state.channel_id].get()) + failure_coro = asyncio.ensure_future(self.update_fail_htlc[chan.state.channel_id].get()) done, pending = await asyncio.wait([fulfill_coro, failure_coro], return_when=FIRST_COMPLETED) if failure_coro.done(): - sig_64, htlc_sigs = m.sign_next_commitment() - self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0])) - while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"": - self.revoke(m) - await self.receive_revoke(m) - m.fail_htlc(htlc) - sig_64, htlc_sigs = m.sign_next_commitment() - self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0)) - await self.receive_revoke(m) + sig_64, htlc_sigs = chan.sign_next_commitment() + self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0])) + while (await self.commitment_signed[chan.state.channel_id].get())["htlc_signature"] != b"": + self.revoke(chan) + await self.receive_revoke(chan) + chan.fail_htlc(htlc) + sig_64, htlc_sigs = chan.sign_next_commitment() + self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=0)) + await self.receive_revoke(chan) fulfill_coro.cancel() - self.lnworker.save_channel(m.state) + self.lnworker.save_channel(chan) return failure_coro.result() if fulfill_coro.done(): failure_coro.cancel() update_fulfill_htlc_msg = fulfill_coro.result() - m.receive_htlc_settle(update_fulfill_htlc_msg["payment_preimage"], int.from_bytes(update_fulfill_htlc_msg["id"], "big")) + chan.receive_htlc_settle(update_fulfill_htlc_msg["payment_preimage"], int.from_bytes(update_fulfill_htlc_msg["id"], "big")) - while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"": - self.revoke(m) + while (await self.commitment_signed[chan.state.channel_id].get())["htlc_signature"] != b"": + self.revoke(chan) # TODO process above commitment transactions - bare_ctx = make_commitment_using_open_channel(m.state, m.state.remote_state.ctn + 1, False, m.state.remote_state.next_per_commitment_point, + bare_ctx = make_commitment_using_open_channel(chan.state, chan.state.remote_state.ctn + 1, False, chan.state.remote_state.next_per_commitment_point, msat_remote, msat_local) - sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) - self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0)) + sig_64 = sign_and_get_sig_string(bare_ctx, chan.state.local_config, chan.state.remote_config) + self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=0)) - await self.receive_revoke(m) + await self.receive_revoke(chan) - self.lnworker.save_channel(m.state) + self.lnworker.save_channel(chan) async def receive_revoke(self, m): revoke_and_ack_msg = await self.revoke_and_ack[m.state.channel_id].get() @@ -1217,10 +1217,10 @@ class Peer(PrintError): @aiosafe async def receive_commitment_revoke_ack(self, htlc, decoded, payment_preimage): chan = self.channels[htlc['channel_id']] - channel_id = chan.channel_id + channel_id = chan.state.channel_id expected_received_msat = int(decoded.amount * COIN * 1000) htlc_id = int.from_bytes(htlc["id"], 'big') - assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) + assert htlc_id == chan.state.remote_state.next_htlc_id, (htlc_id, chan.state.remote_state.next_htlc_id) assert self.channel_state[channel_id] == "OPEN" @@ -1232,19 +1232,17 @@ class Peer(PrintError): htlc = UpdateAddHtlc(amount_msat, payment_hash, cltv_expiry, 0) - m = HTLCStateMachine(chan) + chan.receive_htlc(htlc) - m.receive_htlc(htlc) + assert (await self.receive_commitment(chan)) == 1 - assert (await self.receive_commitment(m)) == 1 + self.revoke(chan) - self.revoke(m) - - sig_64, htlc_sigs = m.sign_next_commitment() + sig_64, htlc_sigs = chan.sign_next_commitment() htlc_sig = htlc_sigs[0] self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) - await self.receive_revoke(m) + await self.receive_revoke(chan) m.settle_htlc(payment_preimage, htlc_id) self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage)) @@ -1255,19 +1253,20 @@ class Peer(PrintError): sig_64 = sign_and_get_sig_string(bare_ctx, m.state.local_config, m.state.remote_config) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) - await self.receive_revoke(m) + await self.receive_revoke(chan) - assert (await self.receive_commitment(m)) == 0 + assert (await self.receive_commitment(chan)) == 0 - self.revoke(m) + self.revoke(chan) - self.lnworker.save_channel(m.state) + self.lnworker.save_channel(chan) def on_commitment_signed(self, payload): self.print_error("commitment_signed", payload) channel_id = payload['channel_id'] chan = self.channels[channel_id] - self.lnworker.save_channel(chan._replace(local_state=chan.local_state._replace(current_commitment_signature=payload['signature']))) + chan.state = chan.state._replace(local_state=chan.state.local_state._replace(current_commitment_signature=payload['signature'])) + self.lnworker.save_channel(chan) self.commitment_signed[channel_id].put_nowait(payload) def on_update_fulfill_htlc(self, payload): @@ -1298,6 +1297,10 @@ class Peer(PrintError): channel_id = payload["channel_id"] self.revoke_and_ack[channel_id].put_nowait(payload) + def on_update_fee(self, payload): + channel_id = payload["channel_id"] + self.channels[channel_id].update_fee(int.from_bytes(payload["feerate_per_kw"], "big")) + def count_trailing_zeros(index): """ BOLT-03 (where_to_put_secret) """ diff --git a/lib/lnhtlc.py b/lib/lnhtlc.py index b87c1dc2..655b792d 100644 --- a/lib/lnhtlc.py +++ b/lib/lnhtlc.py @@ -60,6 +60,7 @@ class HTLCStateMachine(PrintError): self.total_msat_sent = 0 self.total_msat_received = 0 + self.pending_feerate = None def add_htlc(self, htlc): """ @@ -188,9 +189,17 @@ class HTLCStateMachine(PrintError): last_secret, this_point, next_point = self.points + if self.pending_feerate is not None: + new_feerate = self.pending_feerate + else: + new_feerate = self.state.constraints.feerate + self.state = self.state._replace( local_state=self.state.local_state._replace( ctn=self.state.local_state.ctn + 1 + ), + constraints=self.state.constraints._replace( + feerate=new_feerate ) ) @@ -432,3 +441,10 @@ class HTLCStateMachine(PrintError): @property def local_commit_fee(self): return self.state.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs()) + + def update_fee(self, fee): + self.pending_feerate = fee + + def receive_update_fee(self, fee): + self.pending_feerate = fee + diff --git a/lib/lnwatcher.py b/lib/lnwatcher.py index 294fd68a..fdca5232 100644 --- a/lib/lnwatcher.py +++ b/lib/lnwatcher.py @@ -15,7 +15,7 @@ class LNWatcher(PrintError): return response['params'], response['result'] def watch_channel(self, chan, callback): - script = funding_output_script(chan.local_config, chan.remote_config) + script = funding_output_script(chan.state.local_config, chan.state.remote_config) funding_address = redeem_script_to_address('p2wsh', script) self.watched_channels[funding_address] = chan, callback self.network.subscribe_to_addresses([funding_address], self.on_address_status) diff --git a/lib/lnworker.py b/lib/lnworker.py index 4df151d5..1a59a149 100644 --- a/lib/lnworker.py +++ b/lib/lnworker.py @@ -14,6 +14,7 @@ from .lnbase import Peer, Outpoint, ChannelConfig, LocalState, RemoteState, Keyp from .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode from .ecc import ECPrivkey, CURVE_ORDER, der_sig_from_sig_string from .transaction import Transaction +from .lnhtlc import HTLCStateMachine is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key") @@ -53,7 +54,7 @@ def serialize_channels(channels_dict): serialized_channels = [] for chan in channels_dict.values(): namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()} - serialized_channels.append({k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in chan._asdict().items()}) + serialized_channels.append({k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in chan.state._asdict().items()}) class MyJsonEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, bytes): @@ -64,8 +65,8 @@ def serialize_channels(channels_dict): dumped = MyJsonEncoder().encode(serialized_channels) roundtripped = json.loads(dumped) reconstructed = set(reconstruct_namedtuples(x) for x in roundtripped) - if reconstructed != set(channels_dict.values()): - raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed) + "\n" + repr(channels)) + if reconstructed != set(x.state for x in channels_dict.values()): + raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed) + "\n" + repr(channels_dict)) return roundtripped @@ -90,10 +91,10 @@ class LNWorker(PrintError): self.pubkey = ECPrivkey(self.privkey).get_public_key_bytes() self.config = network.config self.peers = {} - self.channels = {x.channel_id: x for x in map(reconstruct_namedtuples, wallet.storage.get("channels", []))} + self.channels = {x.channel_id: HTLCStateMachine(x) for x in map(reconstruct_namedtuples, wallet.storage.get("channels", []))} self.invoices = wallet.storage.get('lightning_invoices', {}) peer_list = network.config.get('lightning_peers', node_list) - self.channel_state = {chan.channel_id: "DISCONNECTED" for chan in self.channels.values()} + self.channel_state = {chan.state.channel_id: "DISCONNECTED" for chan in self.channels.values()} for chan_id, chan in self.channels.items(): self.network.lnwatcher.watch_channel(chan, self.on_channel_utxos) for host, port, pubkey in peer_list: @@ -104,7 +105,7 @@ class LNWorker(PrintError): def channels_for_peer(self, node_id): assert type(node_id) is bytes - return {x: y for (x, y) in self.channels.items() if y.node_id == node_id} + return {x: y for (x, y) in self.channels.items() if y.state.node_id == node_id} def add_peer(self, host, port, node_id): peer = Peer(self, host, int(port), node_id, request_initial_sync=self.config.get("request_initial_sync", True)) @@ -113,17 +114,18 @@ class LNWorker(PrintError): self.lock = threading.Lock() def save_channel(self, openchannel): - if openchannel.channel_id not in self.channel_state: - self.channel_state[openchannel.channel_id] = "OPENING" - self.channels[openchannel.channel_id] = openchannel + assert type(openchannel) is HTLCStateMachine + if openchannel.state.channel_id not in self.channel_state: + self.channel_state[openchannel.state.channel_id] = "OPENING" + self.channels[openchannel.state.channel_id] = openchannel for node_id, peer in self.peers.items(): peer.channels = self.channels_for_peer(node_id) - if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point: + if openchannel.state.remote_state.next_per_commitment_point == openchannel.state.remote_state.current_per_commitment_point: raise Exception("Tried to save channel with next_point == current_point, this should not happen") dumped = serialize_channels(self.channels) self.wallet.storage.put("channels", dumped) self.wallet.storage.write() - self.network.trigger_callback('channel', openchannel) + self.network.trigger_callback('channel', openchannel.state) def save_short_chan_id(self, chan): """ @@ -131,48 +133,48 @@ class LNWorker(PrintError): If the Funding TX has not been mined, return None """ - assert self.channel_state[chan.channel_id] in ["OPEN", "OPENING"] - peer = self.peers[chan.node_id] - conf = self.wallet.get_tx_height(chan.funding_outpoint.txid)[1] - if conf >= chan.constraints.funding_txn_minimum_depth: - block_height, tx_pos = self.wallet.get_txpos(chan.funding_outpoint.txid) + assert self.channel_state[chan.state.channel_id] in ["OPEN", "OPENING"] + peer = self.peers[chan.state.node_id] + conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] + if conf >= chan.state.constraints.funding_txn_minimum_depth: + block_height, tx_pos = self.wallet.get_txpos(chan.state.funding_outpoint.txid) if tx_pos == -1: self.print_error('funding tx is not yet SPV verified.. but there are ' 'already enough confirmations (currently {})'.format(conf)) - return None - chan = chan._replace(short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index)) + return False + chan.state = chan.state._replace(short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.state.funding_outpoint.output_index)) self.save_channel(chan) - return chan - return None + return True + return False def on_channel_utxos(self, chan, utxos): outpoints = [Outpoint(x["tx_hash"], x["tx_pos"]) for x in utxos] - if chan.funding_outpoint not in outpoints: - self.channel_state[chan.channel_id] = "CLOSED" - elif self.channel_state[chan.channel_id] == 'DISCONNECTED': - peer = self.peers[chan.node_id] + if chan.state.funding_outpoint not in outpoints: + self.channel_state[chan.state.channel_id] = "CLOSED" + elif self.channel_state[chan.state.channel_id] == 'DISCONNECTED': + peer = self.peers[chan.state.node_id] coro = peer.reestablish_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) def on_network_update(self, event, *args): for chan in self.channels.values(): - peer = self.peers[chan.node_id] - if self.channel_state[chan.channel_id] == "OPENING": - chan = self.save_short_chan_id(chan) - if not chan: + peer = self.peers[chan.state.node_id] + if self.channel_state[chan.state.channel_id] == "OPENING": + res = self.save_short_chan_id(chan) + if not res: self.print_error("network update but funding tx is still not at sufficient depth") continue # this results in the channel being marked OPEN peer.funding_locked(chan) - elif self.channel_state[chan.channel_id] == "OPEN": - conf = self.wallet.get_tx_height(chan.funding_outpoint.txid)[1] + elif self.channel_state[chan.state.channel_id] == "OPEN": + conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] peer.on_network_update(chan, conf) async def _open_channel_coroutine(self, node_id, amount_sat, push_sat, password): if node_id not in self.peers: node = self.network.lightning_nodes.get(node_id) if node is None: - return False + return "node not found, peers available are: " + str(self.network.lightning_nodes.keys()) host, port = node['addresses'][0] self.add_peer(host, port, node_id) peer = self.peers[node_id] @@ -198,7 +200,7 @@ class LNWorker(PrintError): node_id, short_channel_id = path[0] peer = self.peers[node_id] for chan in self.channels.values(): - if chan.short_channel_id == short_channel_id: + if chan.state.short_channel_id == short_channel_id: break else: raise Exception("ChannelDB returned path with short_channel_id that is not in channel list") @@ -206,7 +208,6 @@ class LNWorker(PrintError): return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) def add_invoice(self, amount_sat, message): - is_open = lambda chan: self.channel_state[chan.channel_id] == "OPEN" payment_preimage = os.urandom(32) RHASH = sha256(payment_preimage) amount_btc = amount_sat/Decimal(COIN) if amount_sat else None @@ -230,13 +231,15 @@ class LNWorker(PrintError): return serialize_channels(self.channels) def close_channel(self, chan_id): - from .lnhtlc import HTLCStateMachine chan = self.channels[chan_id] # local_commitment always gives back the next expected local_commitment, # but in this case, we want the current one. So substract one ctn number - tx = HTLCStateMachine(chan._replace(local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1))).local_commitment - tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)}) - remote_sig = chan.local_state.current_commitment_signature + old_state = chan.state + chan.state = chan.state._replace(local_state=chan.state.local_state._replace(ctn=chan.state.local_state.ctn - 1)) + tx = chan.local_commitment + chan.state = old_state + tx.sign({bh2u(chan.state.local_config.multisig_key.pubkey): (chan.state.local_config.multisig_key.privkey, True)}) + remote_sig = chan.state.local_state.current_commitment_signature remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01" none_idx = tx._inputs[0]["signatures"].index(None) tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig)) diff --git a/lib/tests/test_lnhtlc.py b/lib/tests/test_lnhtlc.py index 9784e28b..7c99fc74 100644 --- a/lib/tests/test_lnhtlc.py +++ b/lib/tests/test_lnhtlc.py @@ -250,28 +250,44 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): ) aliceHtlcIndex = alice_channel.add_htlc(htlc) - bobHtlcIndex = bob_channel.receive_htlc(htlc) - force_state_transition(alice_channel, bob_channel) - self.assertEqual(len(alice_channel.local_commitment.outputs()), 3) - self.assertEqual(len(bob_channel.local_commitment.outputs()), 2) - default_fee = calc_static_fee(0) - self.assertEqual(bob_channel.local_commit_fee, default_fee) - bob_channel.settle_htlc(paymentPreimage, htlc.htlc_id) alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex) - force_state_transition(bob_channel, alice_channel) - self.assertEqual(len(alice_channel.local_commitment.outputs()), 2) - self.assertEqual(alice_channel.total_msat_sent // 1000, htlcAmt) + def test_UpdateFeeSenderCommits(self): + alice_channel, bob_channel = create_test_channels() + + paymentPreimage = b"\x01" * 32 + paymentHash = bitcoin.sha256(paymentPreimage) + htlc = lnhtlc.UpdateAddHtlc( + payment_hash = paymentHash, + amount_msat = one_bitcoin_in_msat, + cltv_expiry = 5, # also in create_test_channels + total_fee = 0 + ) + + aliceHtlcIndex = alice_channel.add_htlc(htlc) + bobHtlcIndex = bob_channel.receive_htlc(htlc) + + fee = 111 + alice_channel.update_fee(fee) + bob_channel.receive_update_fee(fee) + + alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() + bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) + self.assertNotEqual(fee, alice_channel.state.constraints.feerate) + rev, _ = alice_channel.revoke_current_commitment() + self.assertEqual(fee, alice_channel.state.constraints.feerate) + bob_channel.receive_revocation(rev) + def force_state_transition(chanA, chanB): chanB.receive_new_commitment(*chanA.sign_next_commitment()) rev, _ = chanB.revoke_current_commitment() From 49541f78a694d6202a2d016dd635349386c6c5af Mon Sep 17 00:00:00 2001 From: Janus Date: Wed, 27 Jun 2018 18:49:51 +0200 Subject: [PATCH 2/5] ln: shortcut some OpenChannel fields to traversing too much --- gui/qt/channels_list.py | 4 +- lib/lnbase.py | 102 ++++++++++++++++++++-------------------- lib/lnhtlc.py | 19 ++++++++ lib/lnworker.py | 18 +++---- 4 files changed, 81 insertions(+), 62 deletions(-) diff --git a/gui/qt/channels_list.py b/gui/qt/channels_list.py index 72e200e7..f31df046 100644 --- a/gui/qt/channels_list.py +++ b/gui/qt/channels_list.py @@ -50,8 +50,8 @@ class ChannelsList(MyTreeWidget): def do_update_rows(self): self.clear() for chan in self.parent.wallet.lnworker.channels.values(): - item = SortableTreeWidgetItem(self.format_fields(chan.state)) - item.setData(0, QtCore.Qt.UserRole, chan.state.channel_id) + item = SortableTreeWidgetItem(self.format_fields(chan)) + item.setData(0, QtCore.Qt.UserRole, chan.channel_id) self.insertTopLevelItem(0, item) def get_toolbar(self): diff --git a/lib/lnbase.py b/lib/lnbase.py index 8cbbcb21..2bda2941 100644 --- a/lib/lnbase.py +++ b/lib/lnbase.py @@ -776,7 +776,7 @@ class Peer(PrintError): def on_announcement_signatures(self, payload): channel_id = payload['channel_id'] chan = self.channels[payload['channel_id']] - if chan.state.local_state.was_announced: + if chan.local_state.was_announced: h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) else: self.announcement_signatures[channel_id].put_nowait(payload) @@ -933,17 +933,17 @@ class Peer(PrintError): @aiosafe async def reestablish_channel(self, chan): await self.initialized - chan_id = chan.state.channel_id + chan_id = chan.channel_id self.channel_state[chan_id] = 'REESTABLISHING' self.network.trigger_callback('channel', chan) self.send_message(gen_msg("channel_reestablish", channel_id=chan_id, - next_local_commitment_number=chan.state.local_state.ctn+1, - next_remote_revocation_number=chan.state.remote_state.ctn + next_local_commitment_number=chan.local_state.ctn+1, + next_remote_revocation_number=chan.remote_state.ctn )) await self.channel_reestablished[chan_id] self.channel_state[chan_id] = 'OPENING' - if chan.state.local_state.funding_locked_received and chan.state.short_channel_id: + if chan.local_state.funding_locked_received and chan.state.short_channel_id: self.mark_open(chan) self.network.trigger_callback('channel', chan) @@ -956,26 +956,26 @@ class Peer(PrintError): return channel_reestablish_msg = payload remote_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big') - if remote_ctn != chan.state.remote_state.ctn + 1: - raise Exception("expected remote ctn {}, got {}".format(chan.state.remote_state.ctn + 1, remote_ctn)) + if remote_ctn != chan.remote_state.ctn + 1: + raise Exception("expected remote ctn {}, got {}".format(chan.remote_state.ctn + 1, remote_ctn)) local_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big') - if local_ctn != chan.state.local_state.ctn: - raise Exception("expected local ctn {}, got {}".format(chan.state.local_state.ctn, local_ctn)) + if local_ctn != chan.local_state.ctn: + raise Exception("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn)) their = channel_reestablish_msg["my_current_per_commitment_point"] - our = chan.state.remote_state.current_per_commitment_point + our = chan.remote_state.current_per_commitment_point if our is None: - our = chan.state.remote_state.next_per_commitment_point + our = chan.remote_state.next_per_commitment_point if our != their: raise Exception("Remote PCP mismatch: {} {}".format(bh2u(our), bh2u(their))) self.channel_reestablished[chan_id].set_result(True) def funding_locked(self, chan): - channel_id = chan.state.channel_id + channel_id = chan.channel_id per_commitment_secret_index = 2**48 - 2 per_commitment_point_second = secret_to_pubkey(int.from_bytes( - get_per_commitment_secret_from_seed(chan.state.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big')) + get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big')) self.send_message(gen_msg("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)) - if chan.state.local_state.funding_locked_received: + if chan.local_state.funding_locked_received: self.mark_open(chan) def on_funding_locked(self, payload): @@ -983,11 +983,11 @@ class Peer(PrintError): chan = self.channels.get(channel_id) if not chan: raise Exception("Got unknown funding_locked", channel_id) - if not chan.state.local_state.funding_locked_received: - our_next_point = chan.state.remote_state.next_per_commitment_point + if not chan.local_state.funding_locked_received: + our_next_point = chan.remote_state.next_per_commitment_point their_next_point = payload["next_per_commitment_point"] - new_remote_state = chan.state.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) - new_local_state = chan.state.local_state._replace(funding_locked_received = True) + new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) + new_local_state = chan.local_state._replace(funding_locked_received = True) chan.state = chan.state._replace(remote_state=new_remote_state, local_state=new_local_state) self.lnworker.save_channel(chan) if chan.state.short_channel_id: @@ -999,8 +999,8 @@ class Peer(PrintError): Runs on the Network thread. """ - if not chan.state.local_state.was_announced and funding_tx_depth >= 6: - chan.state = chan.state._replace(local_state=chan.state.local_state._replace(was_announced=True)) + if not chan.local_state.was_announced and funding_tx_depth >= 6: + chan.state = chan.state._replace(local_state=chan.local_state._replace(was_announced=True)) coro = self.handle_announcements(chan) self.lnworker.save_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) @@ -1008,10 +1008,10 @@ class Peer(PrintError): @aiosafe async def handle_announcements(self, chan): h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) - announcement_signatures_msg = await self.announcement_signatures[chan.state.channel_id].get() + announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get() remote_node_sig = announcement_signatures_msg["node_signature"] remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"] - if not ecc.verify_signature(chan.state.remote_config.multisig_key.pubkey, remote_bitcoin_sig, h): + if not ecc.verify_signature(chan.remote_config.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): raise Exception("node_sig invalid in announcement_signatures") @@ -1019,7 +1019,7 @@ class Peer(PrintError): node_sigs = [local_node_sig, remote_node_sig] bitcoin_sigs = [local_bitcoin_sig, remote_bitcoin_sig] node_ids = [privkey_to_pubkey(self.privkey), self.pubkey] - bitcoin_keys = [chan.state.local_config.multisig_key.pubkey, chan.state.remote_config.multisig_key.pubkey] + bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey] if node_ids[0] > node_ids[1]: node_sigs.reverse() @@ -1047,10 +1047,10 @@ class Peer(PrintError): print("SENT CHANNEL ANNOUNCEMENT") def mark_open(self, chan): - if self.channel_state[chan.state.channel_id] == "OPEN": + if self.channel_state[chan.channel_id] == "OPEN": return - assert chan.state.local_state.funding_locked_received - self.channel_state[chan.state.channel_id] = "OPEN" + assert chan.local_state.funding_locked_received + self.channel_state[chan.channel_id] = "OPEN" self.network.trigger_callback('channel', chan.state) # add channel to database sorted_keys = list(sorted([self.pubkey, self.lnworker.pubkey])) @@ -1062,8 +1062,8 @@ class Peer(PrintError): def send_announcement_signatures(self, chan): - bitcoin_keys = [chan.state.local_config.multisig_key.pubkey, - chan.state.remote_config.multisig_key.pubkey] + bitcoin_keys = [chan.local_config.multisig_key.pubkey, + chan.remote_config.multisig_key.pubkey] node_ids = [privkey_to_pubkey(self.privkey), self.pubkey] @@ -1085,10 +1085,10 @@ class Peer(PrintError): ) to_hash = chan_ann[256+2:] h = bitcoin.Hash(to_hash) - bitcoin_signature = ecc.ECPrivkey(chan.state.local_config.multisig_key.privkey).sign(h, sigencode_string_canonize, sigdecode_string) + bitcoin_signature = ecc.ECPrivkey(chan.local_config.multisig_key.privkey).sign(h, sigencode_string_canonize, sigdecode_string) node_signature = ecc.ECPrivkey(self.privkey).sign(h, sigencode_string_canonize, sigdecode_string) self.send_message(gen_msg("announcement_signatures", - channel_id=chan.state.channel_id, + channel_id=chan.channel_id, short_channel_id=chan.state.short_channel_id, node_signature=node_signature, bitcoin_signature=bitcoin_signature @@ -1124,7 +1124,7 @@ class Peer(PrintError): @aiosafe async def pay(self, path, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): - assert self.channel_state[chan.state.channel_id] == "OPEN" + assert self.channel_state[chan.channel_id] == "OPEN" assert amount_msat > 0, "amount_msat is not greater zero" height = self.network.get_local_height() route = self.network.path_finder.create_route_from_path(path, self.lnworker.pubkey) @@ -1140,38 +1140,38 @@ class Peer(PrintError): self.secret_key = os.urandom(32) hops_data += [OnionHopsDataSingle(OnionPerHop(b"\x00"*8, amount_msat.to_bytes(8, "big"), (final_cltv_expiry_without_deltas).to_bytes(4, "big")))] onion = new_onion_packet([x.node_id for x in route], self.secret_key, hops_data, associated_data) - msat_local = chan.state.local_state.amount_msat - (amount_msat + total_fee) - msat_remote = chan.state.remote_state.amount_msat + (amount_msat + total_fee) + msat_local = chan.local_state.amount_msat - (amount_msat + total_fee) + msat_remote = chan.remote_state.amount_msat + (amount_msat + total_fee) htlc = UpdateAddHtlc(amount_msat, payment_hash, final_cltv_expiry_with_deltas, total_fee) amount_msat += total_fee - self.send_message(gen_msg("update_add_htlc", channel_id=chan.state.channel_id, id=chan.state.local_state.next_htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) + self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=chan.local_state.next_htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) chan.add_htlc(htlc) - self.attempted_route[(chan.state.channel_id, htlc.htlc_id)] = route + self.attempted_route[(chan.channel_id, htlc.htlc_id)] = route sig_64, htlc_sigs = chan.sign_next_commitment() htlc_sig = htlc_sigs[0] - self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) + self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) await self.receive_revoke(chan) self.revoke(chan) - fulfill_coro = asyncio.ensure_future(self.update_fulfill_htlc[chan.state.channel_id].get()) - failure_coro = asyncio.ensure_future(self.update_fail_htlc[chan.state.channel_id].get()) + fulfill_coro = asyncio.ensure_future(self.update_fulfill_htlc[chan.channel_id].get()) + failure_coro = asyncio.ensure_future(self.update_fail_htlc[chan.channel_id].get()) done, pending = await asyncio.wait([fulfill_coro, failure_coro], return_when=FIRST_COMPLETED) if failure_coro.done(): sig_64, htlc_sigs = chan.sign_next_commitment() - self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0])) - while (await self.commitment_signed[chan.state.channel_id].get())["htlc_signature"] != b"": + self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sigs[0])) + while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"": self.revoke(chan) await self.receive_revoke(chan) chan.fail_htlc(htlc) sig_64, htlc_sigs = chan.sign_next_commitment() - self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=0)) + self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0)) await self.receive_revoke(chan) fulfill_coro.cancel() self.lnworker.save_channel(chan) @@ -1182,33 +1182,33 @@ class Peer(PrintError): chan.receive_htlc_settle(update_fulfill_htlc_msg["payment_preimage"], int.from_bytes(update_fulfill_htlc_msg["id"], "big")) - while (await self.commitment_signed[chan.state.channel_id].get())["htlc_signature"] != b"": + while (await self.commitment_signed[chan.channel_id].get())["htlc_signature"] != b"": self.revoke(chan) # TODO process above commitment transactions - bare_ctx = make_commitment_using_open_channel(chan.state, chan.state.remote_state.ctn + 1, False, chan.state.remote_state.next_per_commitment_point, + bare_ctx = make_commitment_using_open_channel(chan.state, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, msat_remote, msat_local) - sig_64 = sign_and_get_sig_string(bare_ctx, chan.state.local_config, chan.state.remote_config) - self.send_message(gen_msg("commitment_signed", channel_id=chan.state.channel_id, signature=sig_64, num_htlcs=0)) + sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) + self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0)) await self.receive_revoke(chan) self.lnworker.save_channel(chan) async def receive_revoke(self, m): - revoke_and_ack_msg = await self.revoke_and_ack[m.state.channel_id].get() + revoke_and_ack_msg = await self.revoke_and_ack[m.channel_id].get() m.receive_revocation(RevokeAndAck(revoke_and_ack_msg["per_commitment_secret"], revoke_and_ack_msg["next_per_commitment_point"])) def revoke(self, m): rev, _ = m.revoke_current_commitment() self.send_message(gen_msg("revoke_and_ack", - channel_id=m.state.channel_id, + channel_id=m.channel_id, per_commitment_secret=rev.per_commitment_secret, next_per_commitment_point=rev.next_per_commitment_point)) async def receive_commitment(self, m): - commitment_signed_msg = await self.commitment_signed[m.state.channel_id].get() + commitment_signed_msg = await self.commitment_signed[m.channel_id].get() data = commitment_signed_msg["htlc_signature"] htlc_sigs = [data[i:i+64] for i in range(0, len(data), 64)] m.receive_new_commitment(commitment_signed_msg["signature"], htlc_sigs) @@ -1217,10 +1217,10 @@ class Peer(PrintError): @aiosafe async def receive_commitment_revoke_ack(self, htlc, decoded, payment_preimage): chan = self.channels[htlc['channel_id']] - channel_id = chan.state.channel_id + channel_id = chan.channel_id expected_received_msat = int(decoded.amount * COIN * 1000) htlc_id = int.from_bytes(htlc["id"], 'big') - assert htlc_id == chan.state.remote_state.next_htlc_id, (htlc_id, chan.state.remote_state.next_htlc_id) + assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) assert self.channel_state[channel_id] == "OPEN" @@ -1265,7 +1265,7 @@ class Peer(PrintError): self.print_error("commitment_signed", payload) channel_id = payload['channel_id'] chan = self.channels[channel_id] - chan.state = chan.state._replace(local_state=chan.state.local_state._replace(current_commitment_signature=payload['signature'])) + chan.state = chan.state._replace(local_state=chan.local_state._replace(current_commitment_signature=payload['signature'])) self.lnworker.save_channel(chan) self.commitment_signed[channel_id].put_nowait(payload) diff --git a/lib/lnhtlc.py b/lib/lnhtlc.py index 655b792d..569b8722 100644 --- a/lib/lnhtlc.py +++ b/lib/lnhtlc.py @@ -448,3 +448,22 @@ class HTLCStateMachine(PrintError): def receive_update_fee(self, fee): self.pending_feerate = fee + @property + def local_state(self): + return self.state.local_state + + @property + def remote_state(self): + return self.state.remote_state + + @property + def remote_config(self): + return self.state.remote_config + + @property + def local_config(self): + return self.state.local_config + + @property + def channel_id(self): + return self.state.channel_id diff --git a/lib/lnworker.py b/lib/lnworker.py index 1a59a149..90080c19 100644 --- a/lib/lnworker.py +++ b/lib/lnworker.py @@ -94,7 +94,7 @@ class LNWorker(PrintError): self.channels = {x.channel_id: HTLCStateMachine(x) for x in map(reconstruct_namedtuples, wallet.storage.get("channels", []))} self.invoices = wallet.storage.get('lightning_invoices', {}) peer_list = network.config.get('lightning_peers', node_list) - self.channel_state = {chan.state.channel_id: "DISCONNECTED" for chan in self.channels.values()} + self.channel_state = {chan.channel_id: "DISCONNECTED" for chan in self.channels.values()} for chan_id, chan in self.channels.items(): self.network.lnwatcher.watch_channel(chan, self.on_channel_utxos) for host, port, pubkey in peer_list: @@ -115,9 +115,9 @@ class LNWorker(PrintError): def save_channel(self, openchannel): assert type(openchannel) is HTLCStateMachine - if openchannel.state.channel_id not in self.channel_state: - self.channel_state[openchannel.state.channel_id] = "OPENING" - self.channels[openchannel.state.channel_id] = openchannel + if openchannel.channel_id not in self.channel_state: + self.channel_state[openchannel.channel_id] = "OPENING" + self.channels[openchannel.channel_id] = openchannel for node_id, peer in self.peers.items(): peer.channels = self.channels_for_peer(node_id) if openchannel.state.remote_state.next_per_commitment_point == openchannel.state.remote_state.current_per_commitment_point: @@ -133,7 +133,7 @@ class LNWorker(PrintError): If the Funding TX has not been mined, return None """ - assert self.channel_state[chan.state.channel_id] in ["OPEN", "OPENING"] + assert self.channel_state[chan.channel_id] in ["OPEN", "OPENING"] peer = self.peers[chan.state.node_id] conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] if conf >= chan.state.constraints.funding_txn_minimum_depth: @@ -150,8 +150,8 @@ class LNWorker(PrintError): def on_channel_utxos(self, chan, utxos): outpoints = [Outpoint(x["tx_hash"], x["tx_pos"]) for x in utxos] if chan.state.funding_outpoint not in outpoints: - self.channel_state[chan.state.channel_id] = "CLOSED" - elif self.channel_state[chan.state.channel_id] == 'DISCONNECTED': + self.channel_state[chan.channel_id] = "CLOSED" + elif self.channel_state[chan.channel_id] == 'DISCONNECTED': peer = self.peers[chan.state.node_id] coro = peer.reestablish_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) @@ -159,14 +159,14 @@ class LNWorker(PrintError): def on_network_update(self, event, *args): for chan in self.channels.values(): peer = self.peers[chan.state.node_id] - if self.channel_state[chan.state.channel_id] == "OPENING": + if self.channel_state[chan.channel_id] == "OPENING": res = self.save_short_chan_id(chan) if not res: self.print_error("network update but funding tx is still not at sufficient depth") continue # this results in the channel being marked OPEN peer.funding_locked(chan) - elif self.channel_state[chan.state.channel_id] == "OPEN": + elif self.channel_state[chan.channel_id] == "OPEN": conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] peer.on_network_update(chan, conf) From 33974740954aa8f5483b7f1d22e7fe32faf76105 Mon Sep 17 00:00:00 2001 From: Janus Date: Wed, 27 Jun 2018 20:23:03 +0200 Subject: [PATCH 3/5] ln: merge OpenChannel and HTLCStateMachine --- gui/qt/channels_list.py | 6 +- lib/lnbase.py | 142 +++++++++++++------------- lib/lnhtlc.py | 210 +++++++++++++++++++++++++-------------- lib/lnwatcher.py | 2 +- lib/lnworker.py | 101 +++++-------------- lib/tests/test_lnhtlc.py | 32 +++--- 6 files changed, 248 insertions(+), 245 deletions(-) diff --git a/gui/qt/channels_list.py b/gui/qt/channels_list.py index f31df046..94c59a7f 100644 --- a/gui/qt/channels_list.py +++ b/gui/qt/channels_list.py @@ -4,13 +4,13 @@ from PyQt5.QtWidgets import * from electrum.util import inv_dict, bh2u, bfh from electrum.i18n import _ -from electrum.lnbase import OpenChannel +from electrum.lnhtlc import HTLCStateMachine from .util import MyTreeWidget, SortableTreeWidgetItem, WindowModalDialog, Buttons, OkButton, CancelButton from .amountedit import BTCAmountEdit class ChannelsList(MyTreeWidget): update_rows = QtCore.pyqtSignal() - update_single_row = QtCore.pyqtSignal(OpenChannel) + update_single_row = QtCore.pyqtSignal(HTLCStateMachine) def __init__(self, parent): MyTreeWidget.__init__(self, parent, self.create_menu, [_('Node ID'), _('Balance'), _('Remote'), _('Status')], 0) @@ -38,7 +38,7 @@ class ChannelsList(MyTreeWidget): menu.addAction(_("Close channel"), close) menu.exec_(self.viewport().mapToGlobal(position)) - @QtCore.pyqtSlot(OpenChannel) + @QtCore.pyqtSlot(HTLCStateMachine) def do_update_single_row(self, chan): for i in range(self.topLevelItemCount()): item = self.topLevelItem(i) diff --git a/lib/lnbase.py b/lib/lnbase.py index 2bda2941..e563e9fb 100644 --- a/lib/lnbase.py +++ b/lib/lnbase.py @@ -4,12 +4,53 @@ Derived from https://gist.github.com/AdamISZ/046d05c156aaeb56cc897f85eecb3eb8 """ +from collections import namedtuple, defaultdict, OrderedDict, defaultdict +Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) +Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) +ChannelConfig = namedtuple("ChannelConfig", [ + "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", + "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) +OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) +RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) +LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) +ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) +#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) + +class RevocationStore: + """ taken from lnd """ + def __init__(self): + self.buckets = [None] * 48 + self.index = 2**48 - 1 + def add_next_entry(self, hsh): + new_element = ShachainElement(index=self.index, secret=hsh) + bucket = count_trailing_zeros(self.index) + for i in range(0, bucket): + this_bucket = self.buckets[i] + e = shachain_derive(new_element, this_bucket.index) + + if e != this_bucket: + raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) + self.buckets[bucket] = new_element + self.index -= 1 + def serialize(self): + return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} + @staticmethod + def from_json_obj(decoded_json_obj): + store = RevocationStore() + decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) + store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] + store.index = decoded_json_obj["index"] + return store + def __eq__(self, o): + return type(o) is RevocationStore and self.serialize() == o.serialize() + def __hash__(self): + return hash(json.dumps(self.serialize(), sort_keys=True)) + from ecdsa.util import sigdecode_der, sigencode_string_canonize, sigdecode_string from ecdsa.curves import SECP256k1 import queue import traceback import json -from collections import OrderedDict, defaultdict import asyncio from concurrent.futures import FIRST_COMPLETED import os @@ -18,7 +59,6 @@ import binascii import hashlib import hmac from typing import Sequence, Union, Tuple -from collections import namedtuple, defaultdict import cryptography.hazmat.primitives.ciphers.aead as AEAD from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from cryptography.hazmat.backends import default_backend @@ -274,17 +314,6 @@ def create_ephemeral_key(privkey): pub = privkey_to_pubkey(privkey) return (privkey[:32], pub) -Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) -Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) -ChannelConfig = namedtuple("ChannelConfig", [ - "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", - "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) -OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) -RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) -LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) -ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) -OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) - def aiosafe(f): async def f2(*args, **kwargs): @@ -887,14 +916,14 @@ class Peer(PrintError): # remote commitment transaction channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index) their_revocation_store = RevocationStore() - chan = OpenChannel( - node_id=self.pubkey, - channel_id=channel_id, - short_channel_id=None, - funding_outpoint=Outpoint(funding_txid, funding_index), - local_config=local_config, - remote_config=remote_config, - remote_state=RemoteState( + chan = { + "node_id": self.pubkey, + "channel_id": channel_id, + "short_channel_id": None, + "funding_outpoint": Outpoint(funding_txid, funding_index), + "local_config": local_config, + "remote_config": remote_config, + "remote_state": RemoteState( ctn = -1, next_per_commitment_point=remote_per_commitment_point, current_per_commitment_point=None, @@ -902,7 +931,7 @@ class Peer(PrintError): revocation_store=their_revocation_store, next_htlc_id = 0 ), - local_state=LocalState( + "local_state": LocalState( ctn = -1, per_commitment_secret_seed=per_commitment_secret_seed, amount_msat=local_amount, @@ -911,8 +940,8 @@ class Peer(PrintError): was_announced = False, current_commitment_signature = None ), - constraints=ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth) - ) + "constraints": ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth) + } m = HTLCStateMachine(chan) sig_64, _ = m.sign_next_commitment() self.send_message(gen_msg("funding_created", @@ -927,7 +956,8 @@ class Peer(PrintError): # broadcast funding tx success, _txid = self.network.broadcast_transaction(funding_tx) assert success, success - m.state = chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0, current_commitment_signature=remote_sig)) + m.remote_state = m.remote_state._replace(ctn=0) + m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig) return m @aiosafe @@ -943,7 +973,7 @@ class Peer(PrintError): )) await self.channel_reestablished[chan_id] self.channel_state[chan_id] = 'OPENING' - if chan.local_state.funding_locked_received and chan.state.short_channel_id: + if chan.local_state.funding_locked_received and chan.short_channel_id: self.mark_open(chan) self.network.trigger_callback('channel', chan) @@ -988,9 +1018,10 @@ class Peer(PrintError): their_next_point = payload["next_per_commitment_point"] new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) new_local_state = chan.local_state._replace(funding_locked_received = True) - chan.state = chan.state._replace(remote_state=new_remote_state, local_state=new_local_state) + chan.remote_state=new_remote_state + chan.local_state=new_local_state self.lnworker.save_channel(chan) - if chan.state.short_channel_id: + if chan.short_channel_id: self.mark_open(chan) def on_network_update(self, chan, funding_tx_depth): @@ -1000,7 +1031,7 @@ class Peer(PrintError): Runs on the Network thread. """ if not chan.local_state.was_announced and funding_tx_depth >= 6: - chan.state = chan.state._replace(local_state=chan.local_state._replace(was_announced=True)) + chan.local_state=chan.local_state._replace(was_announced=True) coro = self.handle_announcements(chan) self.lnworker.save_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) @@ -1035,7 +1066,7 @@ class Peer(PrintError): len=0, #features not set (defaults to zeros) chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), - short_channel_id=chan.state.short_channel_id, + short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], bitcoin_key_1=bitcoin_keys[0], @@ -1051,12 +1082,12 @@ class Peer(PrintError): return assert chan.local_state.funding_locked_received self.channel_state[chan.channel_id] = "OPEN" - self.network.trigger_callback('channel', chan.state) + self.network.trigger_callback('channel', chan) # add channel to database sorted_keys = list(sorted([self.pubkey, self.lnworker.pubkey])) - self.channel_db.on_channel_announcement({"short_channel_id": chan.state.short_channel_id, "node_id_1": sorted_keys[0], "node_id_2": sorted_keys[1]}) - self.channel_db.on_channel_update({"short_channel_id": chan.state.short_channel_id, 'flags': b'\x01', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) - self.channel_db.on_channel_update({"short_channel_id": chan.state.short_channel_id, 'flags': b'\x00', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) + self.channel_db.on_channel_announcement({"short_channel_id": chan.short_channel_id, "node_id_1": sorted_keys[0], "node_id_2": sorted_keys[1]}) + self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x01', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) + self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x00', 'cltv_expiry_delta': b'\x90', 'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01'}) self.print_error("CHANNEL OPENING COMPLETED") @@ -1077,7 +1108,7 @@ class Peer(PrintError): len=0, #features not set (defaults to zeros) chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), - short_channel_id=chan.state.short_channel_id, + short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], bitcoin_key_1=bitcoin_keys[0], @@ -1089,7 +1120,7 @@ class Peer(PrintError): node_signature = ecc.ECPrivkey(self.privkey).sign(h, sigencode_string_canonize, sigdecode_string) self.send_message(gen_msg("announcement_signatures", channel_id=chan.channel_id, - short_channel_id=chan.state.short_channel_id, + short_channel_id=chan.short_channel_id, node_signature=node_signature, bitcoin_signature=bitcoin_signature )) @@ -1186,7 +1217,7 @@ class Peer(PrintError): self.revoke(chan) # TODO process above commitment transactions - bare_ctx = make_commitment_using_open_channel(chan.state, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, + bare_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, msat_remote, msat_local) sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) @@ -1248,9 +1279,9 @@ class Peer(PrintError): self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage)) # remote commitment transaction without htlcs - bare_ctx = make_commitment_using_open_channel(m.state, m.state.remote_state.ctn + 1, False, m.state.remote_state.next_per_commitment_point, - m.state.remote_state.amount_msat - expected_received_msat, m.state.local_state.amount_msat + expected_received_msat) - sig_64 = sign_and_get_sig_string(bare_ctx, m.state.local_config, m.state.remote_config) + bare_ctx = make_commitment_using_open_channel(m, m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point, + m.remote_state.amount_msat - expected_received_msat, m.local_state.amount_msat + expected_received_msat) + sig_64 = sign_and_get_sig_string(bare_ctx, m.local_config, m.remote_config) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) await self.receive_revoke(chan) @@ -1265,7 +1296,7 @@ class Peer(PrintError): self.print_error("commitment_signed", payload) channel_id = payload['channel_id'] chan = self.channels[channel_id] - chan.state = chan.state._replace(local_state=chan.local_state._replace(current_commitment_signature=payload['signature'])) + chan.local_state=chan.local_state._replace(current_commitment_signature=payload['signature']) self.lnworker.save_channel(chan) self.commitment_signed[channel_id].put_nowait(payload) @@ -1312,32 +1343,3 @@ def count_trailing_zeros(index): ShachainElement = namedtuple("ShachainElement", ["secret", "index"]) ShachainElement.__str__ = lambda self: "ShachainElement(" + bh2u(self.secret) + "," + str(self.index) + ")" -class RevocationStore: - """ taken from lnd """ - def __init__(self): - self.buckets = [None] * 48 - self.index = 2**48 - 1 - def add_next_entry(self, hsh): - new_element = ShachainElement(index=self.index, secret=hsh) - bucket = count_trailing_zeros(self.index) - for i in range(0, bucket): - this_bucket = self.buckets[i] - e = shachain_derive(new_element, this_bucket.index) - - if e != this_bucket: - raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) - self.buckets[bucket] = new_element - self.index -= 1 - def serialize(self): - return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} - @staticmethod - def from_json_obj(decoded_json_obj): - store = RevocationStore() - decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) - store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] - store.index = decoded_json_obj["index"] - return store - def __eq__(self, o): - return type(o) is RevocationStore and self.serialize() == o.serialize() - def __hash__(self): - return hash(json.dumps(self.serialize(), sort_keys=True)) diff --git a/lib/lnhtlc.py b/lib/lnhtlc.py index 569b8722..dc45b49d 100644 --- a/lib/lnhtlc.py +++ b/lib/lnhtlc.py @@ -1,4 +1,6 @@ # ported from lnd 42de4400bff5105352d0552155f73589166d162b +import binascii +import json from ecdsa.util import sigencode_string_canonize, sigdecode_der from .util import bfh, PrintError from .bitcoin import Hash @@ -7,6 +9,7 @@ from ecdsa.curves import SECP256k1 from .crypto import sha256 from . import ecc from . import lnbase +from .lnbase import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore HTLC_TIMEOUT_WEIGHT = lnbase.HTLC_TIMEOUT_WEIGHT HTLC_SUCCESS_WEIGHT = lnbase.HTLC_SUCCESS_WEIGHT @@ -38,6 +41,24 @@ class UpdateAddHtlc: def __repr__(self): return "UpdateAddHtlc" + str(self.as_tuple()) +is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key") + +def maybeDecode(k, v): + assert type(v) is not list + if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature"] and v is not None: + return binascii.unhexlify(v) + return v + +def decodeAll(v): + return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v + +def typeWrap(k, v, local): + if is_key(k): + if local: + return Keypair(**v) + else: + return OnlyPubkeyKeypair(**v) + return v class HTLCStateMachine(PrintError): def lookup_htlc(self, log, htlc_id): @@ -52,7 +73,33 @@ class HTLCStateMachine(PrintError): return str(self.name) def __init__(self, state, name = None): - self.state = state + self.local_config = state["local_config"] + if type(self.local_config) is not ChannelConfig: + new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in self.local_config.items()} + self.local_config = ChannelConfig(**new_local_config) + + self.remote_config = state["remote_config"] + if type(self.remote_config) is not ChannelConfig: + new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in self.remote_config.items()} + self.remote_config = ChannelConfig(**new_remote_config) + + self.local_state = state["local_state"] + if type(self.local_state) is not LocalState: + self.local_state = LocalState(**decodeAll(self.local_state)) + + self.remote_state = state["remote_state"] + if type(self.remote_state) is not RemoteState: + self.remote_state = RemoteState(**decodeAll(self.remote_state)) + + if type(self.remote_state.revocation_store) is not RevocationStore: + self.remote_state = self.remote_state._replace(revocation_store = RevocationStore.from_json_obj(self.remote_state.revocation_store)) + + self.channel_id = maybeDecode("channel_id", state["channel_id"]) if type(state["channel_id"]) is not bytes else state["channel_id"] + self.constraints = ChannelConstraints(**decodeAll(state["constraints"])) if type(state["constraints"]) is not ChannelConstraints else state["constraints"] + self.funding_outpoint = Outpoint(**decodeAll(state["funding_outpoint"])) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"] + self.node_id = maybeDecode("node_id", state["node_id"]) if type(state["node_id"]) is not bytes else state["node_id"] + self.short_channel_id = maybeDecode("short_channel_id", state["short_channel_id"]) if type(state["short_channel_id"]) is not bytes else state["short_channel_id"] + self.local_update_log = [] self.remote_update_log = [] @@ -70,8 +117,8 @@ class HTLCStateMachine(PrintError): assert type(htlc) is UpdateAddHtlc self.local_update_log.append(htlc) self.print_error("add_htlc") - htlc_id = self.state.local_state.next_htlc_id - self.state = self.state._replace(local_state=self.state.local_state._replace(next_htlc_id=htlc_id + 1)) + htlc_id = self.local_state.next_htlc_id + self.local_state=self.local_state._replace(next_htlc_id=htlc_id + 1) htlc.htlc_id = htlc_id return htlc_id @@ -84,8 +131,8 @@ class HTLCStateMachine(PrintError): self.print_error("receive_htlc") assert type(htlc) is UpdateAddHtlc self.remote_update_log.append(htlc) - htlc_id = self.state.remote_state.next_htlc_id - self.state = self.state._replace(remote_state=self.state.remote_state._replace(next_htlc_id=htlc_id + 1)) + htlc_id = self.remote_state.next_htlc_id + self.remote_state=self.remote_state._replace(next_htlc_id=htlc_id + 1) htlc.htlc_id = htlc_id return htlc_id @@ -105,14 +152,14 @@ class HTLCStateMachine(PrintError): from .lnbase import sign_and_get_sig_string, derive_privkey, make_htlc_tx_with_open_channel for htlc in self.local_update_log: if not type(htlc) is UpdateAddHtlc: continue - if htlc.l_locked_in is None: htlc.l_locked_in = self.state.local_state.ctn + if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn self.print_error("sign_next_commitment") - sig_64 = sign_and_get_sig_string(self.remote_commitment, self.state.local_config, self.state.remote_config) + sig_64 = sign_and_get_sig_string(self.remote_commitment, self.local_config, self.remote_config) their_remote_htlc_privkey_number = derive_privkey( - int.from_bytes(self.state.local_config.htlc_basepoint.privkey, 'big'), - self.state.remote_state.next_per_commitment_point) + int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'), + self.remote_state.next_per_commitment_point) their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big') for_us = False @@ -122,11 +169,11 @@ class HTLCStateMachine(PrintError): assert len(htlcs) <= 1 for htlc in htlcs: weight = lnbase.HTLC_SUCCESS_WEIGHT if we_receive else lnbase.HTLC_TIMEOUT_WEIGHT - if htlc.amount_msat // 1000 - weight * (self.state.constraints.feerate // 1000) < self.state.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - weight * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: continue original_htlc_output_index = 0 - args = [self.state.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.remote_commitment, original_htlc_output_index] - htlc_tx = make_htlc_tx_with_open_channel(self.state, *args) + args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.remote_commitment, original_htlc_output_index] + htlc_tx = make_htlc_tx_with_open_channel(self, *args) sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order()) @@ -150,12 +197,12 @@ class HTLCStateMachine(PrintError): self.print_error("receive_new_commitment") for htlc in self.remote_update_log: if not type(htlc) is UpdateAddHtlc: continue - if htlc.r_locked_in is None: htlc.r_locked_in = self.state.remote_state.ctn + if htlc.r_locked_in is None: htlc.r_locked_in = self.remote_state.ctn assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes preimage_hex = self.local_commitment.serialize_preimage(0) pre_hash = Hash(bfh(preimage_hex)) - if not ecc.verify_signature(self.state.remote_config.multisig_key.pubkey, sig, pre_hash): + if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash): raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig)) _, this_point, _ = self.points @@ -166,9 +213,9 @@ class HTLCStateMachine(PrintError): payment_hash = self.htlcs_in_remote[0].payment_hash amount_msat = self.htlcs_in_remote[0].amount_msat cltv_expiry = self.htlcs_in_remote[0].cltv_expiry - htlc_tx = make_htlc_tx_with_open_channel(self.state, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.local_commitment, 0) + htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.local_commitment, 0) pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0))) - remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point) + remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point) if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash): raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs") @@ -192,15 +239,13 @@ class HTLCStateMachine(PrintError): if self.pending_feerate is not None: new_feerate = self.pending_feerate else: - new_feerate = self.state.constraints.feerate + new_feerate = self.constraints.feerate - self.state = self.state._replace( - local_state=self.state.local_state._replace( - ctn=self.state.local_state.ctn + 1 - ), - constraints=self.state.constraints._replace( - feerate=new_feerate - ) + self.local_state=self.local_state._replace( + ctn=self.local_state.ctn + 1 + ) + self.constraints=self.constraints._replace( + feerate=new_feerate ) return RevokeAndAck(last_secret, next_point), "current htlcs" @@ -208,14 +253,13 @@ class HTLCStateMachine(PrintError): @property def points(self): from .lnbase import get_per_commitment_secret_from_seed, secret_to_pubkey - chan = self.state - last_small_num = chan.local_state.ctn + last_small_num = self.local_state.ctn next_small_num = last_small_num + 2 this_small_num = last_small_num + 1 - last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) - this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) + last_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) + this_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big')) - next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) + next_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) return last_secret, this_point, next_point @@ -272,22 +316,20 @@ class HTLCStateMachine(PrintError): received_fees = sum(x.total_fee for x in to_remove) - self.state.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret) + self.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret) - next_point = self.state.remote_state.next_per_commitment_point + next_point = self.remote_state.next_per_commitment_point print("RECEIVED", received_this_batch) print("SENT", sent_this_batch) - self.state = self.state._replace( - remote_state=self.state.remote_state._replace( - ctn=self.state.remote_state.ctn + 1, - current_per_commitment_point=next_point, - next_per_commitment_point=revocation.next_per_commitment_point, - amount_msat=self.state.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees - ), - local_state=self.state.local_state._replace( - amount_msat = self.state.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees - ) + self.remote_state=self.remote_state._replace( + ctn=self.remote_state.ctn + 1, + current_per_commitment_point=next_point, + next_per_commitment_point=revocation.next_per_commitment_point, + amount_msat=self.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees + ) + self.local_state=self.local_state._replace( + amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees ) @staticmethod @@ -306,9 +348,9 @@ class HTLCStateMachine(PrintError): htlc_value_remote, total_fee_remote = self.htlcsum(self.htlcs_in_remote) total_fee_local += local_settled_fee total_fee_remote += remote_settled_fee - local_msat = self.state.local_state.amount_msat -\ + local_msat = self.local_state.amount_msat -\ htlc_value_local + remote_settled_value - local_settled_value - remote_msat = self.state.remote_state.amount_msat -\ + remote_msat = self.remote_state.amount_msat -\ htlc_value_remote + local_settled_value - remote_settled_value return remote_msat, total_fee_remote, local_msat, total_fee_local @@ -319,17 +361,17 @@ class HTLCStateMachine(PrintError): assert local_msat >= 0 assert remote_msat >= 0 - this_point = self.state.remote_state.next_per_commitment_point + this_point = self.remote_state.next_per_commitment_point - remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point) - local_htlc_pubkey = derive_pubkey(self.state.local_config.htlc_basepoint.pubkey, this_point) - local_revocation_pubkey = derive_blinded_pubkey(self.state.local_config.revocation_basepoint.pubkey, this_point) + remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point) + local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point) + local_revocation_pubkey = derive_blinded_pubkey(self.local_config.revocation_basepoint.pubkey, this_point) trimmed = 0 htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( @@ -337,13 +379,13 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self.state, self.state.remote_state.ctn + 1, + commit = make_commitment_using_open_channel(self, self.remote_state.ctn + 1, False, this_point, remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote, trimmed) return commit @@ -357,15 +399,15 @@ class HTLCStateMachine(PrintError): _, this_point, _ = self.points - remote_htlc_pubkey = derive_pubkey(self.state.remote_config.htlc_basepoint.pubkey, this_point) - local_htlc_pubkey = derive_pubkey(self.state.local_config.htlc_basepoint.pubkey, this_point) - remote_revocation_pubkey = derive_blinded_pubkey(self.state.remote_config.revocation_basepoint.pubkey, this_point) + remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point) + local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point) + remote_revocation_pubkey = derive_blinded_pubkey(self.remote_config.revocation_basepoint.pubkey, this_point) trimmed = 0 htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( @@ -373,13 +415,13 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.state.constraints.feerate // 1000) < self.state.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self.state, self.state.local_state.ctn + 1, + commit = make_commitment_using_open_channel(self, self.local_state.ctn + 1, True, this_point, local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote, trimmed) return commit @@ -392,7 +434,7 @@ class HTLCStateMachine(PrintError): for htlc in update_log: if type(htlc) is not UpdateAddHtlc: continue - height = (self.state.local_state.ctn if subject == "remote" else self.state.remote_state.ctn) + height = (self.local_state.ctn if subject == "remote" else self.remote_state.ctn) locked_in = (htlc.r_locked_in if subject == "remote" else htlc.l_locked_in) if locked_in is None or just_unsettled == (SettleHtlc(htlc.htlc_id) in other_log): @@ -432,15 +474,15 @@ class HTLCStateMachine(PrintError): @property def l_current_height(self): - return self.state.local_state.ctn + return self.local_state.ctn @property def r_current_height(self): - return self.state.remote_state.ctn + return self.remote_state.ctn @property def local_commit_fee(self): - return self.state.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs()) + return self.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs()) def update_fee(self, fee): self.pending_feerate = fee @@ -448,22 +490,36 @@ class HTLCStateMachine(PrintError): def receive_update_fee(self, fee): self.pending_feerate = fee - @property - def local_state(self): - return self.state.local_state + def to_save(self): + return { + "local_config": self.local_config, + "remote_config": self.remote_config, + "local_state": self.local_state, + "remote_state": self.remote_state, + "channel_id": self.channel_id, + "short_channel_id": self.short_channel_id, + "constraints": self.constraints, + "funding_outpoint": self.funding_outpoint, + "node_id": self.node_id, + "channel_id": self.channel_id + } - @property - def remote_state(self): - return self.state.remote_state + def serialize(self): + namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()} + serialized_channel = {k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in self.to_save().items()} + class MyJsonEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, bytes): + return binascii.hexlify(o).decode("ascii") + if isinstance(o, RevocationStore): + return o.serialize() + return super(MyJsonEncoder, self) + dumped = MyJsonEncoder().encode(serialized_channel) + roundtripped = json.loads(dumped) + reconstructed = HTLCStateMachine(roundtripped) + if reconstructed.to_save() != self.to_save(): + raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed.to_save()) + "\n" + repr(self.to_save())) + return roundtripped - @property - def remote_config(self): - return self.state.remote_config - - @property - def local_config(self): - return self.state.local_config - - @property - def channel_id(self): - return self.state.channel_id + def __str__(self): + return self.serialize() diff --git a/lib/lnwatcher.py b/lib/lnwatcher.py index fdca5232..294fd68a 100644 --- a/lib/lnwatcher.py +++ b/lib/lnwatcher.py @@ -15,7 +15,7 @@ class LNWatcher(PrintError): return response['params'], response['result'] def watch_channel(self, chan, callback): - script = funding_output_script(chan.state.local_config, chan.state.remote_config) + script = funding_output_script(chan.local_config, chan.remote_config) funding_address = redeem_script_to_address('p2wsh', script) self.watched_channels[funding_address] = chan, callback self.network.subscribe_to_addresses([funding_address], self.on_address_status) diff --git a/lib/lnworker.py b/lib/lnworker.py index 90080c19..4e68ad42 100644 --- a/lib/lnworker.py +++ b/lib/lnworker.py @@ -10,67 +10,12 @@ from . import constants from .bitcoin import sha256, COIN from .util import bh2u, bfh, PrintError from .constants import set_testnet, set_simnet -from .lnbase import Peer, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore, calc_short_channel_id, privkey_to_pubkey +from .lnbase import Peer, calc_short_channel_id, privkey_to_pubkey from .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode from .ecc import ECPrivkey, CURVE_ORDER, der_sig_from_sig_string from .transaction import Transaction from .lnhtlc import HTLCStateMachine - -is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key") - -def maybeDecode(k, v): - if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature"] and v is not None: - return binascii.unhexlify(v) - return v - -def decodeAll(v): - return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v - -def typeWrap(k, v, local): - if is_key(k): - if local: - return Keypair(**v) - else: - return OnlyPubkeyKeypair(**v) - return v - -def reconstruct_namedtuples(openingchannel): - openingchannel = decodeAll(openingchannel) - openingchannel=OpenChannel(**openingchannel) - openingchannel = openingchannel._replace(funding_outpoint=Outpoint(**openingchannel.funding_outpoint)) - new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in openingchannel.local_config.items()} - openingchannel = openingchannel._replace(local_config=ChannelConfig(**new_local_config)) - new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in openingchannel.remote_config.items()} - openingchannel = openingchannel._replace(remote_config=ChannelConfig(**new_remote_config)) - new_local_state = decodeAll(openingchannel.local_state) - openingchannel = openingchannel._replace(local_state=LocalState(**new_local_state)) - new_remote_state = decodeAll(openingchannel.remote_state) - new_remote_state["revocation_store"] = RevocationStore.from_json_obj(new_remote_state["revocation_store"]) - openingchannel = openingchannel._replace(remote_state=RemoteState(**new_remote_state)) - openingchannel = openingchannel._replace(constraints=ChannelConstraints(**openingchannel.constraints)) - return openingchannel - -def serialize_channels(channels_dict): - serialized_channels = [] - for chan in channels_dict.values(): - namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()} - serialized_channels.append({k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in chan.state._asdict().items()}) - class MyJsonEncoder(json.JSONEncoder): - def default(self, o): - if isinstance(o, bytes): - return binascii.hexlify(o).decode("ascii") - if isinstance(o, RevocationStore): - return o.serialize() - return super(MyJsonEncoder, self) - dumped = MyJsonEncoder().encode(serialized_channels) - roundtripped = json.loads(dumped) - reconstructed = set(reconstruct_namedtuples(x) for x in roundtripped) - if reconstructed != set(x.state for x in channels_dict.values()): - raise Exception("Channels did not roundtrip serialization without changes:\n" + repr(reconstructed) + "\n" + repr(channels_dict)) - return roundtripped - - - +from .lnbase import Outpoint # hardcoded nodes node_list = [ @@ -91,7 +36,7 @@ class LNWorker(PrintError): self.pubkey = ECPrivkey(self.privkey).get_public_key_bytes() self.config = network.config self.peers = {} - self.channels = {x.channel_id: HTLCStateMachine(x) for x in map(reconstruct_namedtuples, wallet.storage.get("channels", []))} + self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))} self.invoices = wallet.storage.get('lightning_invoices', {}) peer_list = network.config.get('lightning_peers', node_list) self.channel_state = {chan.channel_id: "DISCONNECTED" for chan in self.channels.values()} @@ -105,7 +50,7 @@ class LNWorker(PrintError): def channels_for_peer(self, node_id): assert type(node_id) is bytes - return {x: y for (x, y) in self.channels.items() if y.state.node_id == node_id} + return {x: y for (x, y) in self.channels.items() if y.node_id == node_id} def add_peer(self, host, port, node_id): peer = Peer(self, host, int(port), node_id, request_initial_sync=self.config.get("request_initial_sync", True)) @@ -120,12 +65,12 @@ class LNWorker(PrintError): self.channels[openchannel.channel_id] = openchannel for node_id, peer in self.peers.items(): peer.channels = self.channels_for_peer(node_id) - if openchannel.state.remote_state.next_per_commitment_point == openchannel.state.remote_state.current_per_commitment_point: + if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point: raise Exception("Tried to save channel with next_point == current_point, this should not happen") - dumped = serialize_channels(self.channels) + dumped = [x.serialize() for x in self.channels.values()] self.wallet.storage.put("channels", dumped) self.wallet.storage.write() - self.network.trigger_callback('channel', openchannel.state) + self.network.trigger_callback('channel', openchannel) def save_short_chan_id(self, chan): """ @@ -134,31 +79,31 @@ class LNWorker(PrintError): If the Funding TX has not been mined, return None """ assert self.channel_state[chan.channel_id] in ["OPEN", "OPENING"] - peer = self.peers[chan.state.node_id] - conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] - if conf >= chan.state.constraints.funding_txn_minimum_depth: - block_height, tx_pos = self.wallet.get_txpos(chan.state.funding_outpoint.txid) + peer = self.peers[chan.node_id] + conf = self.wallet.get_tx_height(chan.funding_outpoint.txid)[1] + if conf >= chan.constraints.funding_txn_minimum_depth: + block_height, tx_pos = self.wallet.get_txpos(chan.funding_outpoint.txid) if tx_pos == -1: self.print_error('funding tx is not yet SPV verified.. but there are ' 'already enough confirmations (currently {})'.format(conf)) return False - chan.state = chan.state._replace(short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.state.funding_outpoint.output_index)) + chan.short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index) self.save_channel(chan) return True return False def on_channel_utxos(self, chan, utxos): outpoints = [Outpoint(x["tx_hash"], x["tx_pos"]) for x in utxos] - if chan.state.funding_outpoint not in outpoints: + if chan.funding_outpoint not in outpoints: self.channel_state[chan.channel_id] = "CLOSED" elif self.channel_state[chan.channel_id] == 'DISCONNECTED': - peer = self.peers[chan.state.node_id] + peer = self.peers[chan.node_id] coro = peer.reestablish_channel(chan) asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) def on_network_update(self, event, *args): for chan in self.channels.values(): - peer = self.peers[chan.state.node_id] + peer = self.peers[chan.node_id] if self.channel_state[chan.channel_id] == "OPENING": res = self.save_short_chan_id(chan) if not res: @@ -167,7 +112,7 @@ class LNWorker(PrintError): # this results in the channel being marked OPEN peer.funding_locked(chan) elif self.channel_state[chan.channel_id] == "OPEN": - conf = self.wallet.get_tx_height(chan.state.funding_outpoint.txid)[1] + conf = self.wallet.get_tx_height(chan.funding_outpoint.txid)[1] peer.on_network_update(chan, conf) async def _open_channel_coroutine(self, node_id, amount_sat, push_sat, password): @@ -200,7 +145,7 @@ class LNWorker(PrintError): node_id, short_channel_id = path[0] peer = self.peers[node_id] for chan in self.channels.values(): - if chan.state.short_channel_id == short_channel_id: + if chan.short_channel_id == short_channel_id: break else: raise Exception("ChannelDB returned path with short_channel_id that is not in channel list") @@ -228,18 +173,18 @@ class LNWorker(PrintError): self.wallet.storage.write() def list_channels(self): - return serialize_channels(self.channels) + return [str(x) for x in self.channels] def close_channel(self, chan_id): chan = self.channels[chan_id] # local_commitment always gives back the next expected local_commitment, # but in this case, we want the current one. So substract one ctn number - old_state = chan.state - chan.state = chan.state._replace(local_state=chan.state.local_state._replace(ctn=chan.state.local_state.ctn - 1)) + old_local_state = chan.local_state + chan.local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1) tx = chan.local_commitment - chan.state = old_state - tx.sign({bh2u(chan.state.local_config.multisig_key.pubkey): (chan.state.local_config.multisig_key.privkey, True)}) - remote_sig = chan.state.local_state.current_commitment_signature + chan.local_state = old_local_state + tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)}) + remote_sig = chan.local_state.current_commitment_signature remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01" none_idx = tx._inputs[0]["signatures"].index(None) tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig)) diff --git a/lib/tests/test_lnhtlc.py b/lib/tests/test_lnhtlc.py index 7c99fc74..e43a0876 100644 --- a/lib/tests/test_lnhtlc.py +++ b/lib/tests/test_lnhtlc.py @@ -36,13 +36,13 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate max_accepted_htlcs=5 ) - return lnbase.OpenChannel( - channel_id=channel_id, - short_channel_id=channel_id[:8], - funding_outpoint=lnbase.Outpoint(funding_txid, funding_index), - local_config=local_config, - remote_config=remote_config, - remote_state=lnbase.RemoteState( + return { + "channel_id":channel_id, + "short_channel_id":channel_id[:8], + "funding_outpoint":lnbase.Outpoint(funding_txid, funding_index), + "local_config":local_config, + "remote_config":remote_config, + "remote_state":lnbase.RemoteState( ctn = 0, next_per_commitment_point=nex, current_per_commitment_point=cur, @@ -50,7 +50,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate revocation_store=their_revocation_store, next_htlc_id = 0 ), - local_state=lnbase.LocalState( + "local_state":lnbase.LocalState( ctn = 0, per_commitment_secret_seed=seed, amount_msat=local_amount, @@ -59,9 +59,9 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate was_announced=False, current_commitment_signature=None ), - constraints=lnbase.ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=is_initiator, funding_txn_minimum_depth=3), - node_id=other_node_id - ) + "constraints":lnbase.ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=is_initiator, funding_txn_minimum_depth=3), + "node_id":other_node_id + } def bip32(sequence): xprv, xpub = bitcoin.bip32_root(b"9dk", 'standard') @@ -184,8 +184,8 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): self.assertEqual(alice_channel.total_msat_received, bobSent, "alice has incorrect milli-satoshis received") self.assertEqual(bob_channel.total_msat_sent, bobSent, "bob has incorrect milli-satoshis sent") self.assertEqual(bob_channel.total_msat_received, aliceSent, "bob has incorrect milli-satoshis received") - self.assertEqual(bob_channel.state.local_state.ctn, 1, "bob has incorrect commitment height") - self.assertEqual(alice_channel.state.local_state.ctn, 1, "alice has incorrect commitment height") + self.assertEqual(bob_channel.local_state.ctn, 1, "bob has incorrect commitment height") + self.assertEqual(alice_channel.local_state.ctn, 1, "alice has incorrect commitment height") # Both commitment transactions should have three outputs, and one of # them should be exactly the amount of the HTLC. @@ -238,7 +238,7 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): paymentPreimage = b"\x01" * 32 paymentHash = bitcoin.sha256(paymentPreimage) - fee_per_kw = alice_channel.state.constraints.feerate + fee_per_kw = alice_channel.constraints.feerate self.assertEqual(fee_per_kw, 6000) htlcAmt = 500 + lnbase.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) self.assertEqual(htlcAmt, 4478) @@ -283,9 +283,9 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) - self.assertNotEqual(fee, alice_channel.state.constraints.feerate) + self.assertNotEqual(fee, alice_channel.constraints.feerate) rev, _ = alice_channel.revoke_current_commitment() - self.assertEqual(fee, alice_channel.state.constraints.feerate) + self.assertEqual(fee, alice_channel.constraints.feerate) bob_channel.receive_revocation(rev) def force_state_transition(chanA, chanB): From 40b4e555e6de71ce9dcefc2ef9fc5733475c3dab Mon Sep 17 00:00:00 2001 From: Janus Date: Thu, 28 Jun 2018 15:50:45 +0200 Subject: [PATCH 4/5] ln: avoid recursive dependencies, make new lnutil --- lib/lnbase.py | 366 +---------- lib/lnhtlc.py | 62 +- lib/lnrouter.py | 10 +- lib/lnutil.py | 315 ++++++++++ lib/lnwatcher.py | 2 +- lib/lnworker.py | 10 +- lib/tests/test_lnhtlc.py | 11 +- lib/tests/test_lnrouter.py | 150 +++++ lib/tests/{test_lnbase.py => test_lnutil.py} | 623 +++++++------------ 9 files changed, 772 insertions(+), 777 deletions(-) create mode 100644 lib/lnutil.py create mode 100644 lib/tests/test_lnrouter.py rename lib/tests/{test_lnbase.py => test_lnutil.py} (70%) diff --git a/lib/lnbase.py b/lib/lnbase.py index e563e9fb..172b058b 100644 --- a/lib/lnbase.py +++ b/lib/lnbase.py @@ -5,49 +5,12 @@ """ from collections import namedtuple, defaultdict, OrderedDict, defaultdict -Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) -Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) -ChannelConfig = namedtuple("ChannelConfig", [ - "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", - "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) -OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) -RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) -LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) -ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) -#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) - -class RevocationStore: - """ taken from lnd """ - def __init__(self): - self.buckets = [None] * 48 - self.index = 2**48 - 1 - def add_next_entry(self, hsh): - new_element = ShachainElement(index=self.index, secret=hsh) - bucket = count_trailing_zeros(self.index) - for i in range(0, bucket): - this_bucket = self.buckets[i] - e = shachain_derive(new_element, this_bucket.index) - - if e != this_bucket: - raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) - self.buckets[bucket] = new_element - self.index -= 1 - def serialize(self): - return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} - @staticmethod - def from_json_obj(decoded_json_obj): - store = RevocationStore() - decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) - store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] - store.index = decoded_json_obj["index"] - return store - def __eq__(self, o): - return type(o) is RevocationStore and self.serialize() == o.serialize() - def __hash__(self): - return hash(json.dumps(self.serialize(), sort_keys=True)) +from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore +from .lnutil import sign_and_get_sig_string, funding_output_script, get_ecdh, get_per_commitment_secret_from_seed +from .lnutil import secret_to_pubkey +from .bitcoin import COIN from ecdsa.util import sigdecode_der, sigencode_string_canonize, sigdecode_string -from ecdsa.curves import SECP256k1 import queue import traceback import json @@ -58,18 +21,8 @@ import time import binascii import hashlib import hmac -from typing import Sequence, Union, Tuple import cryptography.hazmat.primitives.ciphers.aead as AEAD -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms -from cryptography.hazmat.backends import default_backend -HTLC_TIMEOUT_WEIGHT = 663 -HTLC_SUCCESS_WEIGHT = 703 - -from .ecc import ser_to_point, point_to_ser, string_to_number -from .bitcoin import (deserialize_privkey, rev_hex, int_to_hex, - push_script, script_num_to_hex, - add_number_to_script, var_int, COIN) from . import bitcoin from . import ecc from . import crypto @@ -82,6 +35,8 @@ from .lnrouter import new_onion_packet, OnionHopsDataSingle, OnionPerHop, decode from .lightning_payencode.lnaddr import lndecode from .lnhtlc import UpdateAddHtlc, HTLCStateMachine, RevokeAndAck, SettleHtlc +REV_GENESIS = bytes.fromhex(bitcoin.rev_hex(constants.net.GENESIS)) + def channel_id_from_funding_tx(funding_txid, funding_index): funding_txid_bytes = bytes.fromhex(funding_txid)[::-1] i = int.from_bytes(funding_txid_bytes, 'big') ^ funding_index @@ -289,10 +244,6 @@ def get_bolt8_hkdf(salt, ikm): assert len(T1 + T2) == 64 return T1, T2 -def get_ecdh(priv: bytes, pub: bytes) -> bytes: - pt = ecc.ECPubkey(pub) * string_to_number(priv) - return sha256(pt.get_public_key_bytes()) - def act1_initiator_message(hs, my_privkey): #Get a new ephemeral key epriv, epub = create_ephemeral_key(my_privkey) @@ -328,290 +279,6 @@ def aiosafe(f): traceback.print_exc() return f2 -def get_obscured_ctn(ctn, local, remote): - mask = int.from_bytes(sha256(local + remote)[-6:], 'big') - return ctn ^ mask - -def secret_to_pubkey(secret): - assert type(secret) is int - return point_to_ser(SECP256k1.generator * secret) - -def derive_pubkey(basepoint, per_commitment_point): - p = ecc.ECPubkey(basepoint) + ecc.generator() * ecc.string_to_number(sha256(per_commitment_point + basepoint)) - return p.get_public_key_bytes() - -def derive_privkey(secret, per_commitment_point): - assert type(secret) is int - basepoint = point_to_ser(SECP256k1.generator * secret) - basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint)) - basepoint %= SECP256k1.order - return basepoint - -def derive_blinded_pubkey(basepoint, per_commitment_point): - k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point)) - k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint)) - return (k1 + k2).get_public_key_bytes() - -def shachain_derive(element, toIndex): - return ShachainElement(get_per_commitment_secret_from_seed(element.secret, toIndex, count_trailing_zeros(element.index)), toIndex) - - -def get_per_commitment_secret_from_seed(seed: bytes, i: int, bits: int = 48) -> bytes: - """Generate per commitment secret.""" - per_commitment_secret = bytearray(seed) - for bitindex in range(bits - 1, -1, -1): - mask = 1 << bitindex - if i & mask: - per_commitment_secret[bitindex // 8] ^= 1 << (bitindex % 8) - per_commitment_secret = bytearray(sha256(per_commitment_secret)) - bajts = bytes(per_commitment_secret) - return bajts - - -def overall_weight(num_htlc): - return 500 + 172 * num_htlc + 224 - -def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): - assert type(amount_msat) is int - assert type(local_feerate) is int - assert type(revocationpubkey) is bytes - assert type(local_delayedpubkey) is bytes - script = bytes([opcodes.OP_IF]) \ - + bfh(push_script(bh2u(revocationpubkey))) \ - + bytes([opcodes.OP_ELSE]) \ - + bitcoin.add_number_to_script(to_self_delay) \ - + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) \ - + bfh(push_script(bh2u(local_delayedpubkey))) \ - + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) - - p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script)) - weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT - fee = local_feerate * weight - final_amount_sat = (amount_msat - fee) // 1000 - assert final_amount_sat > 0, final_amount_sat - output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat) - return output - -def make_htlc_tx_witness(remotehtlcsig, localhtlcsig, payment_preimage, witness_script): - assert type(remotehtlcsig) is bytes - assert type(localhtlcsig) is bytes - assert type(payment_preimage) is bytes - assert type(witness_script) is bytes - return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])) - -def make_htlc_tx_inputs(htlc_output_txid, htlc_output_index, revocationpubkey, local_delayedpubkey, amount_msat, witness_script): - assert type(htlc_output_txid) is str - assert type(htlc_output_index) is int - assert type(revocationpubkey) is bytes - assert type(local_delayedpubkey) is bytes - assert type(amount_msat) is int - assert type(witness_script) is str - c_inputs = [{ - 'scriptSig': '', - 'type': 'p2wsh', - 'signatures': [], - 'num_sig': 0, - 'prevout_n': htlc_output_index, - 'prevout_hash': htlc_output_txid, - 'value': amount_msat // 1000, - 'coinbase': False, - 'sequence': 0x0, - 'preimage_script': witness_script, - }] - return c_inputs - -def make_htlc_tx(cltv_timeout, inputs, output): - assert type(cltv_timeout) is int - c_outputs = [output] - tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2) - tx.BIP_LI01_sort() - return tx - -def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash): - assert type(revocation_pubkey) is bytes - assert type(remote_htlcpubkey) is bytes - assert type(local_htlcpubkey) is bytes - assert type(payment_hash) is bytes - return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ - + bfh(push_script(bh2u(remote_htlcpubkey)))\ - + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\ - + bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\ - + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\ - + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) - -def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash, cltv_expiry): - for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]: - assert type(i) is bytes - assert type(cltv_expiry) is int - - return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \ - + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ - + bfh(push_script(bh2u(remote_htlcpubkey))) \ - + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \ - + bitcoin.add_number_to_script(32) \ - + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \ - + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \ - + bytes([opcodes.OP_EQUALVERIFY]) \ - + bitcoin.add_number_to_script(2) \ - + bytes([opcodes.OP_SWAP]) \ - + bfh(push_script(bh2u(local_htlcpubkey))) \ - + bitcoin.add_number_to_script(2) \ - + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \ - + bitcoin.add_number_to_script(cltv_expiry) \ - + bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) - -def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, cltv_expiry, payment_hash, commit, original_htlc_output_index): - conf = chan.local_config if for_us else chan.remote_config - other_conf = chan.local_config if not for_us else chan.remote_config - - revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp) - other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp) - htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp) - # HTLC-success for the HTLC spending from a received HTLC output - # if we do not receive, and the commitment tx is not for us, they receive, so it is also an HTLC-success - is_htlc_success = for_us == we_receive - htlc_tx_output = make_htlc_tx_output( - amount_msat = amount_msat, - local_feerate = chan.constraints.feerate, - revocationpubkey=revocation_pubkey, - local_delayedpubkey=delayedpubkey, - success = is_htlc_success, - to_self_delay = other_conf.to_self_delay) - if is_htlc_success: - preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry) - else: - preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash) - htlc_tx_inputs = make_htlc_tx_inputs( - commit.txid(), commit.htlc_output_indices[original_htlc_output_index], - revocationpubkey=revocation_pubkey, - local_delayedpubkey=delayedpubkey, - amount_msat=amount_msat, - witness_script=bh2u(preimage_script)) - if is_htlc_success: - cltv_expiry = 0 - htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) - return htlc_tx - -def make_commitment_using_open_channel(chan, ctn, for_us, pcp, local_msat, remote_msat, htlcs=[], trimmed=0): - conf = chan.local_config if for_us else chan.remote_config - other_conf = chan.local_config if not for_us else chan.remote_config - payment_pubkey = derive_pubkey(other_conf.payment_basepoint.pubkey, pcp) - remote_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) - return make_commitment( - ctn, - conf.multisig_key.pubkey, - other_conf.multisig_key.pubkey, - payment_pubkey, - chan.local_config.payment_basepoint.pubkey, - chan.remote_config.payment_basepoint.pubkey, - remote_revocation_pubkey, - derive_pubkey(conf.delayed_basepoint.pubkey, pcp), - other_conf.to_self_delay, - *chan.funding_outpoint, - chan.constraints.capacity, - local_msat, - remote_msat, - chan.local_config.dust_limit_sat, - chan.constraints.feerate, - for_us, - chan.constraints.is_initiator, - htlcs=htlcs, - trimmed=trimmed) - -def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey, - remote_payment_pubkey, payment_basepoint, - remote_payment_basepoint, revocation_pubkey, - delayed_pubkey, to_self_delay, funding_txid, - funding_pos, funding_sat, local_amount, remote_amount, - dust_limit_sat, local_feerate, for_us, we_are_initiator, - htlcs, trimmed=0): - - pubkeys = sorted([bh2u(local_funding_pubkey), bh2u(remote_funding_pubkey)]) - payments = [payment_basepoint, remote_payment_basepoint] - if not we_are_initiator: - payments.reverse() - obs = get_obscured_ctn(ctn, *payments) - locktime = (0x20 << 24) + (obs & 0xffffff) - sequence = (0x80 << 24) + (obs >> 24) - print_error('locktime', locktime, hex(locktime)) - # commitment tx input - c_inputs = [{ - 'type': 'p2wsh', - 'x_pubkeys': pubkeys, - 'signatures': [None, None], - 'num_sig': 2, - 'prevout_n': funding_pos, - 'prevout_hash': funding_txid, - 'value': funding_sat, - 'coinbase': False, - 'sequence': sequence - }] - # commitment tx outputs - local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + add_number_to_script(to_self_delay) \ - + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) - local_address = bitcoin.redeem_script_to_address('p2wsh', bh2u(local_script)) - remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey)) - # TODO trim htlc outputs here while also considering 2nd stage htlc transactions - fee = local_feerate * overall_weight(len(htlcs)) - fee -= trimmed * 1000 - assert type(fee) is int - we_pay_fee = for_us == we_are_initiator - to_local_amt = local_amount - (fee if we_pay_fee else 0) - assert type(to_local_amt) is int - to_local = (bitcoin.TYPE_ADDRESS, local_address, to_local_amt // 1000) - to_remote_amt = remote_amount - (fee if not we_pay_fee else 0) - assert type(to_remote_amt) is int - to_remote = (bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt // 1000) - c_outputs = [to_local, to_remote] - for script, msat_amount in htlcs: - c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)] - - # trim outputs - c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs)) - assert sum(x[2] for x in c_outputs) <= funding_sat - - # create commitment tx - tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2) - tx.BIP_LI01_sort() - - tx.htlc_output_indices = {} - for idx, output in enumerate(c_outputs): - if output in tx.outputs(): - # minus the first two outputs (to_local, to_remote) - tx.htlc_output_indices[idx - 2] = tx.outputs().index(output) - - return tx - - -def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index: int) -> bytes: - bh = block_height.to_bytes(3, byteorder='big') - tpos = tx_pos_in_block.to_bytes(3, byteorder='big') - oi = output_index.to_bytes(2, byteorder='big') - return bh + tpos + oi - - -def sign_and_get_sig_string(tx, local_config, remote_config): - pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) - tx.sign({bh2u(local_config.multisig_key.pubkey): (local_config.multisig_key.privkey, True)}) - sig_index = pubkeys.index(bh2u(local_config.multisig_key.pubkey)) - sig = bytes.fromhex(tx.inputs()[0]["signatures"][sig_index]) - r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) - sig_64 = sigencode_string_canonize(r, s, SECP256k1.generator.order()) - return sig_64 - -def is_synced(network): - local_height, server_height = network.get_status_value("updated") - synced = server_height != 0 and network.is_up_to_date() and local_height >= server_height - return synced - -def funding_output_script(local_config, remote_config): - pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) - return transaction.multisig_script(pubkeys, 2) - class Peer(PrintError): def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False): @@ -864,7 +531,7 @@ class Peer(PrintError): msg = gen_msg( "open_channel", temporary_channel_id=temp_channel_id, - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, funding_satoshis=funding_sat, push_msat=push_msat, dust_limit_satoshis=local_config.dust_limit_sat, @@ -1065,7 +732,7 @@ class Peer(PrintError): bitcoin_signature_2=bitcoin_sigs[1], len=0, #features not set (defaults to zeros) - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], @@ -1107,7 +774,7 @@ class Peer(PrintError): chan_ann = gen_msg("channel_announcement", len=0, #features not set (defaults to zeros) - chain_hash=bytes.fromhex(rev_hex(constants.net.GENESIS)), + chain_hash=REV_GENESIS, short_channel_id=chan.short_channel_id, node_id_1=node_ids[0], node_id_2=node_ids[1], @@ -1217,7 +884,7 @@ class Peer(PrintError): self.revoke(chan) # TODO process above commitment transactions - bare_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, + bare_ctx = chan.make_commitment(chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, msat_remote, msat_local) sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) @@ -1249,7 +916,7 @@ class Peer(PrintError): async def receive_commitment_revoke_ack(self, htlc, decoded, payment_preimage): chan = self.channels[htlc['channel_id']] channel_id = chan.channel_id - expected_received_msat = int(decoded.amount * COIN * 1000) + expected_received_msat = int(decoded.amount * bitcoin.COIN * 1000) htlc_id = int.from_bytes(htlc["id"], 'big') assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) @@ -1279,7 +946,7 @@ class Peer(PrintError): self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage)) # remote commitment transaction without htlcs - bare_ctx = make_commitment_using_open_channel(m, m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point, + bare_ctx = chan.make_commitment(m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point, m.remote_state.amount_msat - expected_received_msat, m.local_state.amount_msat + expected_received_msat) sig_64 = sign_and_get_sig_string(bare_ctx, m.local_config, m.remote_config) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) @@ -1333,13 +1000,4 @@ class Peer(PrintError): self.channels[channel_id].update_fee(int.from_bytes(payload["feerate_per_kw"], "big")) -def count_trailing_zeros(index): - """ BOLT-03 (where_to_put_secret) """ - try: - return list(reversed(bin(index)[2:])).index("1") - except ValueError: - return 48 - -ShachainElement = namedtuple("ShachainElement", ["secret", "index"]) -ShachainElement.__str__ = lambda self: "ShachainElement(" + bh2u(self.secret) + "," + str(self.index) + ")" diff --git a/lib/lnhtlc.py b/lib/lnhtlc.py index dc45b49d..4028fd73 100644 --- a/lib/lnhtlc.py +++ b/lib/lnhtlc.py @@ -1,17 +1,17 @@ # ported from lnd 42de4400bff5105352d0552155f73589166d162b +from collections import namedtuple import binascii import json -from ecdsa.util import sigencode_string_canonize, sigdecode_der from .util import bfh, PrintError from .bitcoin import Hash -from collections import namedtuple -from ecdsa.curves import SECP256k1 from .crypto import sha256 from . import ecc -from . import lnbase -from .lnbase import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore -HTLC_TIMEOUT_WEIGHT = lnbase.HTLC_TIMEOUT_WEIGHT -HTLC_SUCCESS_WEIGHT = lnbase.HTLC_SUCCESS_WEIGHT +from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore +from .lnutil import get_per_commitment_secret_from_seed +from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey +from .lnutil import sign_and_get_sig_string +from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc +from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT SettleHtlc = namedtuple("SettleHtlc", ["htlc_id"]) RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"]) @@ -149,7 +149,6 @@ class HTLCStateMachine(PrintError): any). The HTLC signatures are sorted according to the BIP 69 order of the HTLC's on the commitment transaction. """ - from .lnbase import sign_and_get_sig_string, derive_privkey, make_htlc_tx_with_open_channel for htlc in self.local_update_log: if not type(htlc) is UpdateAddHtlc: continue if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn @@ -168,15 +167,14 @@ class HTLCStateMachine(PrintError): for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]): assert len(htlcs) <= 1 for htlc in htlcs: - weight = lnbase.HTLC_SUCCESS_WEIGHT if we_receive else lnbase.HTLC_TIMEOUT_WEIGHT + weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT if htlc.amount_msat // 1000 - weight * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: continue original_htlc_output_index = 0 args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.remote_commitment, original_htlc_output_index] htlc_tx = make_htlc_tx_with_open_channel(self, *args) sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) - r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) - htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order()) + htlc_sig = ecc.sig_string_from_der_sig(sig[:-1]) htlcsigs.append(htlc_sig) return sig_64, htlcsigs @@ -192,7 +190,6 @@ class HTLCStateMachine(PrintError): state, then this newly added commitment becomes our current accepted channel state. """ - from .lnbase import make_htlc_tx_with_open_channel , derive_pubkey self.print_error("receive_new_commitment") for htlc in self.remote_update_log: @@ -252,7 +249,6 @@ class HTLCStateMachine(PrintError): @property def points(self): - from .lnbase import get_per_commitment_secret_from_seed, secret_to_pubkey last_small_num = self.local_state.ctn next_small_num = last_small_num + 2 this_small_num = last_small_num + 1 @@ -356,7 +352,6 @@ class HTLCStateMachine(PrintError): @property def remote_commitment(self): - from .lnbase import make_commitment_using_open_channel, make_received_htlc, make_offered_htlc, derive_pubkey, derive_blinded_pubkey remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts() assert local_msat >= 0 assert remote_msat >= 0 @@ -371,7 +366,7 @@ class HTLCStateMachine(PrintError): htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( @@ -379,20 +374,19 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self, self.remote_state.ctn + 1, + commit = self.make_commitment(self.remote_state.ctn + 1, False, this_point, remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote, trimmed) return commit @property def local_commitment(self): - from .lnbase import make_commitment_using_open_channel, make_received_htlc, make_offered_htlc, derive_pubkey, derive_blinded_pubkey, get_per_commitment_secret_from_seed, secret_to_pubkey remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts() assert local_msat >= 0 assert remote_msat >= 0 @@ -407,7 +401,7 @@ class HTLCStateMachine(PrintError): htlcs_in_local = [] for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - lnbase.HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_local.append( @@ -415,13 +409,13 @@ class HTLCStateMachine(PrintError): htlcs_in_remote = [] for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - lnbase.HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat: trimmed += htlc.amount_msat // 1000 continue htlcs_in_remote.append( ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) - commit = make_commitment_using_open_channel(self, self.local_state.ctn + 1, + commit = self.make_commitment(self.local_state.ctn + 1, True, this_point, local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote, trimmed) return commit @@ -523,3 +517,29 @@ class HTLCStateMachine(PrintError): def __str__(self): return self.serialize() + + def make_commitment(chan, ctn, for_us, pcp, local_msat, remote_msat, htlcs=[], trimmed=0): + conf = chan.local_config if for_us else chan.remote_config + other_conf = chan.local_config if not for_us else chan.remote_config + payment_pubkey = derive_pubkey(other_conf.payment_basepoint.pubkey, pcp) + remote_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + return make_commitment( + ctn, + conf.multisig_key.pubkey, + other_conf.multisig_key.pubkey, + payment_pubkey, + chan.local_config.payment_basepoint.pubkey, + chan.remote_config.payment_basepoint.pubkey, + remote_revocation_pubkey, + derive_pubkey(conf.delayed_basepoint.pubkey, pcp), + other_conf.to_self_delay, + *chan.funding_outpoint, + chan.constraints.capacity, + local_msat, + remote_msat, + chan.local_config.dust_limit_sat, + chan.constraints.feerate, + for_us, + chan.constraints.is_initiator, + htlcs=htlcs, + trimmed=trimmed) diff --git a/lib/lnrouter.py b/lib/lnrouter.py index c01363ce..4df8f15a 100644 --- a/lib/lnrouter.py +++ b/lib/lnrouter.py @@ -40,8 +40,8 @@ from . import bitcoin from . import ecc from . import crypto from .crypto import sha256 -from .util import PrintError, bh2u, print_error, bfh, profiler, xor_bytes -from . import lnbase +from .util import PrintError, bh2u, profiler, xor_bytes +from .lnutil import get_ecdh class ChannelInfo(PrintError): @@ -368,7 +368,7 @@ def get_shared_secrets_along_route(payment_path_pubkeys: Sequence[bytes], ephemeral_key = session_key # compute shared key for each hop for i in range(0, num_hops): - hop_shared_secrets[i] = lnbase.get_ecdh(ephemeral_key, payment_path_pubkeys[i]) + hop_shared_secrets[i] = get_ecdh(ephemeral_key, payment_path_pubkeys[i]) ephemeral_pubkey = ecc.ECPrivkey(ephemeral_key).get_public_key_bytes() blinding_factor = sha256(ephemeral_pubkey + hop_shared_secrets[i]) blinding_factor_int = int.from_bytes(blinding_factor, byteorder="big") @@ -435,7 +435,7 @@ ProcessedOnionPacket = namedtuple("ProcessedOnionPacket", ["are_we_final", "hop_ # TODO replay protection def process_onion_packet(onion_packet: OnionPacket, associated_data: bytes, our_onion_private_key: bytes) -> ProcessedOnionPacket: - shared_secret = lnbase.get_ecdh(our_onion_private_key, onion_packet.public_key) + shared_secret = get_ecdh(our_onion_private_key, onion_packet.public_key) # check message integrity mu_key = get_bolt04_onion_key(b'mu', shared_secret) @@ -519,5 +519,7 @@ def get_failure_msg_from_onion_error(decrypted_error_packet: bytes) -> OnionRout return OnionRoutingFailureMessage(failure_code, failure_data) + + # <----- bolt 04, "onion" diff --git a/lib/lnutil.py b/lib/lnutil.py new file mode 100644 index 00000000..934122d2 --- /dev/null +++ b/lib/lnutil.py @@ -0,0 +1,315 @@ +from .util import bfh, bh2u +from .crypto import sha256 +import json +from collections import namedtuple +from .transaction import Transaction +from .ecc import CURVE_ORDER, generator, sig_string_from_der_sig, ECPubkey, string_to_number +from . import ecc, bitcoin, crypto, transaction +from .transaction import opcodes +from .bitcoin import push_script + +HTLC_TIMEOUT_WEIGHT = 663 +HTLC_SUCCESS_WEIGHT = 703 + +Keypair = namedtuple("Keypair", ["pubkey", "privkey"]) +Outpoint = namedtuple("Outpoint", ["txid", "output_index"]) +ChannelConfig = namedtuple("ChannelConfig", [ + "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", + "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) +OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) +RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) +LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) +ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) +#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) + +class RevocationStore: + """ taken from lnd """ + def __init__(self): + self.buckets = [None] * 48 + self.index = 2**48 - 1 + def add_next_entry(self, hsh): + new_element = ShachainElement(index=self.index, secret=hsh) + bucket = count_trailing_zeros(self.index) + for i in range(0, bucket): + this_bucket = self.buckets[i] + e = shachain_derive(new_element, this_bucket.index) + + if e != this_bucket: + raise Exception("hash is not derivable: {} {} {}".format(bh2u(e.secret), bh2u(this_bucket.secret), this_bucket.index)) + self.buckets[bucket] = new_element + self.index -= 1 + def serialize(self): + return {"index": self.index, "buckets": [[bh2u(k.secret), k.index] if k is not None else None for k in self.buckets]} + @staticmethod + def from_json_obj(decoded_json_obj): + store = RevocationStore() + decode = lambda to_decode: ShachainElement(bfh(to_decode[0]), int(to_decode[1])) + store.buckets = [k if k is None else decode(k) for k in decoded_json_obj["buckets"]] + store.index = decoded_json_obj["index"] + return store + def __eq__(self, o): + return type(o) is RevocationStore and self.serialize() == o.serialize() + def __hash__(self): + return hash(json.dumps(self.serialize(), sort_keys=True)) + +def count_trailing_zeros(index): + """ BOLT-03 (where_to_put_secret) """ + try: + return list(reversed(bin(index)[2:])).index("1") + except ValueError: + return 48 + +def shachain_derive(element, toIndex): + return ShachainElement(get_per_commitment_secret_from_seed(element.secret, toIndex, count_trailing_zeros(element.index)), toIndex) + +ShachainElement = namedtuple("ShachainElement", ["secret", "index"]) +ShachainElement.__str__ = lambda self: "ShachainElement(" + bh2u(self.secret) + "," + str(self.index) + ")" + +def get_per_commitment_secret_from_seed(seed: bytes, i: int, bits: int = 48) -> bytes: + """Generate per commitment secret.""" + per_commitment_secret = bytearray(seed) + for bitindex in range(bits - 1, -1, -1): + mask = 1 << bitindex + if i & mask: + per_commitment_secret[bitindex // 8] ^= 1 << (bitindex % 8) + per_commitment_secret = bytearray(sha256(per_commitment_secret)) + bajts = bytes(per_commitment_secret) + return bajts + +def secret_to_pubkey(secret): + assert type(secret) is int + return (ecc.generator() * secret).get_public_key_bytes() + +def derive_pubkey(basepoint, per_commitment_point): + p = ecc.ECPubkey(basepoint) + ecc.generator() * ecc.string_to_number(sha256(per_commitment_point + basepoint)) + return p.get_public_key_bytes() + +def derive_privkey(secret, per_commitment_point): + assert type(secret) is int + basepoint = (ecc.generator() * secret).get_public_key_bytes() + basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint)) + basepoint %= CURVE_ORDER + return basepoint + +def derive_blinded_pubkey(basepoint, per_commitment_point): + k1 = ecc.ECPubkey(basepoint) * ecc.string_to_number(sha256(basepoint + per_commitment_point)) + k2 = ecc.ECPubkey(per_commitment_point) * ecc.string_to_number(sha256(per_commitment_point + basepoint)) + return (k1 + k2).get_public_key_bytes() + +def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): + assert type(amount_msat) is int + assert type(local_feerate) is int + assert type(revocationpubkey) is bytes + assert type(local_delayedpubkey) is bytes + script = bytes([opcodes.OP_IF]) \ + + bfh(push_script(bh2u(revocationpubkey))) \ + + bytes([opcodes.OP_ELSE]) \ + + bitcoin.add_number_to_script(to_self_delay) \ + + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) \ + + bfh(push_script(bh2u(local_delayedpubkey))) \ + + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) + + p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script)) + weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT + fee = local_feerate * weight + final_amount_sat = (amount_msat - fee) // 1000 + assert final_amount_sat > 0, final_amount_sat + output = (bitcoin.TYPE_ADDRESS, p2wsh, final_amount_sat) + return output + +def make_htlc_tx_witness(remotehtlcsig, localhtlcsig, payment_preimage, witness_script): + assert type(remotehtlcsig) is bytes + assert type(localhtlcsig) is bytes + assert type(payment_preimage) is bytes + assert type(witness_script) is bytes + return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])) + +def make_htlc_tx_inputs(htlc_output_txid, htlc_output_index, revocationpubkey, local_delayedpubkey, amount_msat, witness_script): + assert type(htlc_output_txid) is str + assert type(htlc_output_index) is int + assert type(revocationpubkey) is bytes + assert type(local_delayedpubkey) is bytes + assert type(amount_msat) is int + assert type(witness_script) is str + c_inputs = [{ + 'scriptSig': '', + 'type': 'p2wsh', + 'signatures': [], + 'num_sig': 0, + 'prevout_n': htlc_output_index, + 'prevout_hash': htlc_output_txid, + 'value': amount_msat // 1000, + 'coinbase': False, + 'sequence': 0x0, + 'preimage_script': witness_script, + }] + return c_inputs + +def make_htlc_tx(cltv_timeout, inputs, output): + assert type(cltv_timeout) is int + c_outputs = [output] + tx = Transaction.from_io(inputs, c_outputs, locktime=cltv_timeout, version=2) + tx.BIP_LI01_sort() + return tx + +def make_offered_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash): + assert type(revocation_pubkey) is bytes + assert type(remote_htlcpubkey) is bytes + assert type(local_htlcpubkey) is bytes + assert type(payment_hash) is bytes + return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ + + bfh(push_script(bh2u(remote_htlcpubkey)))\ + + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\ + + bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\ + + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\ + + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) + +def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash, cltv_expiry): + for i in [revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, payment_hash]: + assert type(i) is bytes + assert type(cltv_expiry) is int + + return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \ + + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \ + + bfh(push_script(bh2u(remote_htlcpubkey))) \ + + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \ + + bitcoin.add_number_to_script(32) \ + + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \ + + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \ + + bytes([opcodes.OP_EQUALVERIFY]) \ + + bitcoin.add_number_to_script(2) \ + + bytes([opcodes.OP_SWAP]) \ + + bfh(push_script(bh2u(local_htlcpubkey))) \ + + bitcoin.add_number_to_script(2) \ + + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \ + + bitcoin.add_number_to_script(cltv_expiry) \ + + bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF]) + +def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, cltv_expiry, payment_hash, commit, original_htlc_output_index): + conf = chan.local_config if for_us else chan.remote_config + other_conf = chan.local_config if not for_us else chan.remote_config + + revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp) + other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp) + other_htlc_pubkey = derive_pubkey(other_conf.htlc_basepoint.pubkey, pcp) + htlc_pubkey = derive_pubkey(conf.htlc_basepoint.pubkey, pcp) + # HTLC-success for the HTLC spending from a received HTLC output + # if we do not receive, and the commitment tx is not for us, they receive, so it is also an HTLC-success + is_htlc_success = for_us == we_receive + htlc_tx_output = make_htlc_tx_output( + amount_msat = amount_msat, + local_feerate = chan.constraints.feerate, + revocationpubkey=revocation_pubkey, + local_delayedpubkey=delayedpubkey, + success = is_htlc_success, + to_self_delay = other_conf.to_self_delay) + if is_htlc_success: + preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry) + else: + preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash) + htlc_tx_inputs = make_htlc_tx_inputs( + commit.txid(), commit.htlc_output_indices[original_htlc_output_index], + revocationpubkey=revocation_pubkey, + local_delayedpubkey=delayedpubkey, + amount_msat=amount_msat, + witness_script=bh2u(preimage_script)) + if is_htlc_success: + cltv_expiry = 0 + htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output) + return htlc_tx + + +def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey, + remote_payment_pubkey, payment_basepoint, + remote_payment_basepoint, revocation_pubkey, + delayed_pubkey, to_self_delay, funding_txid, + funding_pos, funding_sat, local_amount, remote_amount, + dust_limit_sat, local_feerate, for_us, we_are_initiator, + htlcs, trimmed=0): + + pubkeys = sorted([bh2u(local_funding_pubkey), bh2u(remote_funding_pubkey)]) + payments = [payment_basepoint, remote_payment_basepoint] + if not we_are_initiator: + payments.reverse() + obs = get_obscured_ctn(ctn, *payments) + locktime = (0x20 << 24) + (obs & 0xffffff) + sequence = (0x80 << 24) + (obs >> 24) + # commitment tx input + c_inputs = [{ + 'type': 'p2wsh', + 'x_pubkeys': pubkeys, + 'signatures': [None, None], + 'num_sig': 2, + 'prevout_n': funding_pos, + 'prevout_hash': funding_txid, + 'value': funding_sat, + 'coinbase': False, + 'sequence': sequence + }] + # commitment tx outputs + local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + bitcoin.add_number_to_script(to_self_delay) \ + + bytes([opcodes.OP_CSV, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG]) + local_address = bitcoin.redeem_script_to_address('p2wsh', bh2u(local_script)) + remote_address = bitcoin.pubkey_to_address('p2wpkh', bh2u(remote_payment_pubkey)) + # TODO trim htlc outputs here while also considering 2nd stage htlc transactions + fee = local_feerate * overall_weight(len(htlcs)) + fee -= trimmed * 1000 + assert type(fee) is int + we_pay_fee = for_us == we_are_initiator + to_local_amt = local_amount - (fee if we_pay_fee else 0) + assert type(to_local_amt) is int + to_local = (bitcoin.TYPE_ADDRESS, local_address, to_local_amt // 1000) + to_remote_amt = remote_amount - (fee if not we_pay_fee else 0) + assert type(to_remote_amt) is int + to_remote = (bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt // 1000) + c_outputs = [to_local, to_remote] + for script, msat_amount in htlcs: + c_outputs += [(bitcoin.TYPE_ADDRESS, bitcoin.redeem_script_to_address('p2wsh', bh2u(script)), msat_amount // 1000)] + + # trim outputs + c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs)) + assert sum(x[2] for x in c_outputs) <= funding_sat + + # create commitment tx + tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2) + tx.BIP_LI01_sort() + + tx.htlc_output_indices = {} + for idx, output in enumerate(c_outputs): + if output in tx.outputs(): + # minus the first two outputs (to_local, to_remote) + tx.htlc_output_indices[idx - 2] = tx.outputs().index(output) + + return tx + +def sign_and_get_sig_string(tx, local_config, remote_config): + pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) + tx.sign({bh2u(local_config.multisig_key.pubkey): (local_config.multisig_key.privkey, True)}) + sig_index = pubkeys.index(bh2u(local_config.multisig_key.pubkey)) + sig = bytes.fromhex(tx.inputs()[0]["signatures"][sig_index]) + sig_64 = sig_string_from_der_sig(sig[:-1]) + return sig_64 + +def funding_output_script(local_config, remote_config): + pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) + return transaction.multisig_script(pubkeys, 2) + +def calc_short_channel_id(block_height: int, tx_pos_in_block: int, output_index: int) -> bytes: + bh = block_height.to_bytes(3, byteorder='big') + tpos = tx_pos_in_block.to_bytes(3, byteorder='big') + oi = output_index.to_bytes(2, byteorder='big') + return bh + tpos + oi + +def get_obscured_ctn(ctn, local, remote): + mask = int.from_bytes(sha256(local + remote)[-6:], 'big') + return ctn ^ mask + +def overall_weight(num_htlc): + return 500 + 172 * num_htlc + 224 + +def get_ecdh(priv: bytes, pub: bytes) -> bytes: + pt = ECPubkey(pub) * string_to_number(priv) + return sha256(pt.get_public_key_bytes()) diff --git a/lib/lnwatcher.py b/lib/lnwatcher.py index 294fd68a..46bb5bf5 100644 --- a/lib/lnwatcher.py +++ b/lib/lnwatcher.py @@ -1,5 +1,5 @@ from .util import PrintError -from .lnbase import funding_output_script +from .lnutil import funding_output_script from .bitcoin import redeem_script_to_address class LNWatcher(PrintError): diff --git a/lib/lnworker.py b/lib/lnworker.py index 4e68ad42..5138b6c2 100644 --- a/lib/lnworker.py +++ b/lib/lnworker.py @@ -10,12 +10,12 @@ from . import constants from .bitcoin import sha256, COIN from .util import bh2u, bfh, PrintError from .constants import set_testnet, set_simnet -from .lnbase import Peer, calc_short_channel_id, privkey_to_pubkey +from .lnbase import Peer, privkey_to_pubkey from .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode -from .ecc import ECPrivkey, CURVE_ORDER, der_sig_from_sig_string +from .ecc import der_sig_from_sig_string from .transaction import Transaction from .lnhtlc import HTLCStateMachine -from .lnbase import Outpoint +from .lnutil import Outpoint, calc_short_channel_id # hardcoded nodes node_list = [ @@ -33,7 +33,7 @@ class LNWorker(PrintError): wallet.storage.put('lightning_privkey', pk) wallet.storage.write() self.privkey = bfh(pk) - self.pubkey = ECPrivkey(self.privkey).get_public_key_bytes() + self.pubkey = privkey_to_pubkey(self.privkey) self.config = network.config self.peers = {} self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))} @@ -157,8 +157,6 @@ class LNWorker(PrintError): RHASH = sha256(payment_preimage) amount_btc = amount_sat/Decimal(COIN) if amount_sat else None pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]), self.privkey) - decoded = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) - assert decoded.pubkey.serialize() == privkey_to_pubkey(self.privkey) self.invoices[bh2u(payment_preimage)] = pay_req self.wallet.storage.put('lightning_invoices', self.invoices) self.wallet.storage.write() diff --git a/lib/tests/test_lnhtlc.py b/lib/tests/test_lnhtlc.py index e43a0876..1a13d816 100644 --- a/lib/tests/test_lnhtlc.py +++ b/lib/tests/test_lnhtlc.py @@ -4,6 +4,7 @@ import unittest import lib.bitcoin as bitcoin import lib.lnbase as lnbase import lib.lnhtlc as lnhtlc +import lib.lnutil as lnutil import lib.util as util import os import binascii @@ -87,10 +88,10 @@ def create_test_channels(): alice_seed = os.urandom(32) bob_seed = os.urandom(32) - alice_cur = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 1), "big")) - alice_next = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 2), "big")) - bob_cur = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 1), "big")) - bob_next = lnbase.secret_to_pubkey(int.from_bytes(lnbase.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 2), "big")) + alice_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 1), "big")) + alice_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 2), "big")) + bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 1), "big")) + bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 2), "big")) return \ lnhtlc.HTLCStateMachine( @@ -240,7 +241,7 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): paymentHash = bitcoin.sha256(paymentPreimage) fee_per_kw = alice_channel.constraints.feerate self.assertEqual(fee_per_kw, 6000) - htlcAmt = 500 + lnbase.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) + htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) self.assertEqual(htlcAmt, 4478) htlc = lnhtlc.UpdateAddHtlc( payment_hash = paymentHash, diff --git a/lib/tests/test_lnrouter.py b/lib/tests/test_lnrouter.py new file mode 100644 index 00000000..d41199d4 --- /dev/null +++ b/lib/tests/test_lnrouter.py @@ -0,0 +1,150 @@ +import unittest + +from lib.util import bh2u, bfh +from lib.lnbase import Peer +from lib.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop +from lib import bitcoin, lnrouter + +class Test_LNRouter(unittest.TestCase): + + #@staticmethod + #def parse_witness_list(witness_bytes): + # amount_witnesses = witness_bytes[0] + # witness_bytes = witness_bytes[1:] + # res = [] + # for i in range(amount_witnesses): + # witness_length = witness_bytes[0] + # this_witness = witness_bytes[1:witness_length+1] + # assert len(this_witness) == witness_length + # witness_bytes = witness_bytes[witness_length+1:] + # res += [bytes(this_witness)] + # assert witness_bytes == b"", witness_bytes + # return res + + + + def test_find_path_for_payment(self): + class fake_network: + channel_db = lnrouter.ChannelDB() + class fake_ln_worker: + path_finder = lnrouter.LNPathFinder(fake_network.channel_db) + privkey = bitcoin.sha256('privkeyseed') + network = fake_network + channel_state = {} + channels = [] + invoices = {} + p = Peer(fake_ln_worker, '', 0, 'a') + p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')}) + p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')}) + p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')}) + p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')}) + p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')}) + p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')}) + o = lambda i: i.to_bytes(8, "big") + p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)}) + p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) + self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000)) + + + + def test_new_onion_packet(self): + # test vector from bolt-04 + payment_path_pubkeys = [ + bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), + bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'), + bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'), + bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), + bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'), + ] + session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') + associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') + hops_data = [ + OnionHopsDataSingle(OnionPerHop( + bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') + )), + ] + packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) + self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf'), + packet.to_bytes()) + + def test_process_onion_packet(self): + # this test is not from bolt-04, but is based on the one there; + # except here we have the privkeys for these pubkeys + payment_path_pubkeys = [ + bfh('03d75c0ee70f68d73d7d13aeb6261d8ace11416800860c7e59407afe4e2e2d42bb'), + bfh('03960a0b830c7b8e76de745b819f252c62508346196b916f5e813cdb0773283cce'), + bfh('0385620e0a571cbc3552620f8bf1bdcdab2d1a4a59c36fa10b8249114ccbdda40d'), + bfh('02ee242cf6c38b7285f0152c33804ff777f5c51fd352ca8132e845e2cf23b3d8ba'), + bfh('025c585fd2e174bf8245b2b4a119e52a417688904228643ea3edaa1728bf2a258e'), + ] + payment_path_privkeys = [ + bfh('3463a278617b3dd83f79bda7f97673f12609c54386e1f0d2b67b1c6354fda14e'), + bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'), + bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'), + bfh('3d885f374d79a5e777459b083f7818cdc9493e5c4994ac9c7b843de8b70be661'), + bfh('dd72ab44729527b7942e195e7a835e7c71f9c0ff61844eb21274d9c26166a8f8'), + ] + session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') + associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') + hops_data = [ + OnionHopsDataSingle(OnionPerHop( + bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') + )), + OnionHopsDataSingle(OnionPerHop( + bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') + )), + ] + packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) + self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a0fd57c2dbcb2c6dd4e8ef35d96db28d5a0e49b6ab3d6de31af65950723b8cddc108390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec659da15d0f6fbc7e1687485b39e9be0ec3b70164cb3618a9b546317e7c2d62ae9f0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0d16d4c662194873e8b4ecf46eb2c9d4d58d2ee2021adb19840605ac5afd8bd942dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32f74bc5e428f7fa4c3cf70e13ed91563177d94190d5149aa4b9c96d00e40d2ac35ab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b937e31e0ae40f650a7cd820bcb4996825b1cbad1ff7ccc2b513b1104524c34f6573e1b59201c005a632ee5dccd3711a32e3ba1ff00fcffbe636e4b3a84bbe491b836a57ccec138b8cc2ec733846904d872f305d538d51db8e56232ec6e07877075328874cb7b09c7e799100a9ff085dead253886b174fc408a0ea7b48bce2c5d8992285011960af088f7e006ef60089d46ac9aa15acfac6c87c3cf6904764dd785419292fbafa9cca09c8ade24a6cd63f12d1cfc83fa35cf2f1cf503c39cbf78293f06c68a3cece7177169cd872bb49bf69d933a27a887dd9daefa9239fca9f0c3e309ec61d9df947211da98cf11a6e0fb77252629cdf9f2226dd69ca73fa51be4df224592f8d471b69a1aebbdaa2f3a798b3581253d97feb0a12e6606043ca0fc5efc0f49b8061d6796eff31cd8638499e2f25ffb96eec32837438ed7ebebbe587886648f63e35d80f41869f4c308f2e6970bd65fead5e8544e3239a6acc9d996b08d1546455bcafbe88ed3ed547714841946fe2e77180e4d7bf1452414e4b1745a7897184a2c4cbc3ac46f83342a55a48e29dc8f17cf595dd28f51e297ba89fd25ed0dbd1c0081a810beaab09758a36fbfd16fbdc3daa9fe05c8a73195f244ef2743a5df761f01ee6e693eb6c7f1a7834fab3671391e5ddebf611e119a2ae4456e2cee7a6d4f27a2246cdb1f8ef35f0b3d7044b3799d8d0ed0a6470557fd807c065d6d83acba07e96e10770ada8c0b4d4921522944188d5f30086a6ee0a4795331273f32beaaa43363fc58208a257e5c5c434c7325b583642219d81c7d67b908d5263b42ac1991edc69a777da60f38eff138c844af9e549374e8b29b166211bfded24587a29394e33828b784da7e7b62ab7e49ea2693fcdd17fa96186a5ef11ef1a8adffa50f93a3119e95e6c09014f3e3b0709183fa08a826ced6deb4608b7d986ebbcf99ad58e25451d4d9d38d0059734d8501467b97182cd11e0c07c91ca50f61cc31255a3147ade654976a5989097281892aafd8df595c63bd14f1e03f5955a9398d2dd6368bbcae833ae1cc2df31eb0980b4817dfd130020ffb275743fcc01df40e3ecda1c5988e8e1bde965353b0b1bf34ea05f095000c45b6249618d275905a24d3eb58c600aeab4fb552fbf1ccdb2a5c80ace220310f89829d7e53f78c126037b6d8d500220c7a118d9621b4d6bd5379edd7e24bcf540e87aba6b88862db16fa4ee00b009fda80577be67ab94910fd8a7807dfe4ebe66b8fdcd040aa2dc17ec22639298be56b2a2c9d8940647b75f2f6d81746df16e1cb2f05e23397a8c63baea0803441ff4b7d517ff172980a056726235e2f6af85e8aa9b91ba85f14532272d6170df3166b91169dc09d4f4a251610f57ff0885a93364cfaf650bdf436c89795efed5ca934bc7ffc0a4'), + packet.to_bytes()) + for i, privkey in enumerate(payment_path_privkeys): + processed_packet = lnrouter.process_onion_packet(packet, associated_data, privkey) + self.assertEqual(hops_data[i].per_hop.to_bytes(), processed_packet.hop_data.per_hop.to_bytes()) + packet = processed_packet.next_packet + + def test_decode_onion_error(self): + # test vector from bolt-04 + payment_path_pubkeys = [ + bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), + bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'), + bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'), + bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), + bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'), + ] + session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') + error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d') + decoded_error, index_of_sender = lnrouter._decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key) + self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + decoded_error) + self.assertEqual(4, index_of_sender) diff --git a/lib/tests/test_lnbase.py b/lib/tests/test_lnutil.py similarity index 70% rename from lib/tests/test_lnbase.py rename to lib/tests/test_lnutil.py index 9ca3e621..c214c48a 100644 --- a/lib/tests/test_lnbase.py +++ b/lib/tests/test_lnutil.py @@ -1,19 +1,8 @@ -import binascii -import json import unittest - +import json +from lib import bitcoin, ecc +from lib.lnutil import RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc, 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 from lib.util import bh2u, bfh -from lib.lnbase import make_commitment, get_obscured_ctn, Peer, make_offered_htlc, make_received_htlc, make_htlc_tx -from lib.lnbase import secret_to_pubkey, derive_pubkey, derive_privkey, derive_blinded_pubkey, overall_weight -from lib.lnbase import make_htlc_tx_output, make_htlc_tx_inputs, get_per_commitment_secret_from_seed, make_htlc_tx_witness -from lib.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop -from lib.lnbase import RevocationStore -from lib.transaction import Transaction -from lib import bitcoin -import ecdsa.ellipticcurve -from ecdsa.curves import SECP256k1 -from lib.util import bfh -from lib import bitcoin, lnbase, lnrouter funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be' funding_output_index = 0 @@ -24,7 +13,7 @@ local_dust_limit_satoshi = 546 local_payment_basepoint = bytes.fromhex('034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa') remote_payment_basepoint = bytes.fromhex('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991') -obs = get_obscured_ctn(42, local_payment_basepoint, remote_payment_basepoint) +# obs = get_obscured_ctn(42, local_payment_basepoint, remote_payment_basepoint) local_funding_privkey = bytes.fromhex('30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901') local_funding_pubkey = bytes.fromhex('023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb') remote_funding_pubkey = bytes.fromhex('030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1') @@ -35,377 +24,7 @@ local_delayedpubkey = bytes.fromhex('03fd5960528dc152014952efdb702a88f71e3c1653b local_revocation_pubkey = bytes.fromhex('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19') # funding wscript = 5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae -class Test_LNBase(unittest.TestCase): - - @staticmethod - def parse_witness_list(witness_bytes): - amount_witnesses = witness_bytes[0] - witness_bytes = witness_bytes[1:] - res = [] - for i in range(amount_witnesses): - witness_length = witness_bytes[0] - this_witness = witness_bytes[1:witness_length+1] - assert len(this_witness) == witness_length - witness_bytes = witness_bytes[witness_length+1:] - res += [bytes(this_witness)] - assert witness_bytes == b"", witness_bytes - return res - - def test_simple_commitment_tx_with_no_HTLCs(self): - to_local_msat = 7000000000 - to_remote_msat = 3000000000 - local_feerate_per_kw = 15000 - # base commitment transaction fee = 10860 - # actual commitment transaction fee = 10860 - # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" - # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 - htlcs=[] - our_commit_tx = make_commitment( - commitment_number, - local_funding_pubkey, remote_funding_pubkey, remotepubkey, - local_payment_basepoint, remote_payment_basepoint, - local_revocation_pubkey, local_delayedpubkey, local_delay, - funding_tx_id, funding_output_index, funding_amount_satoshi, - to_local_msat, to_remote_msat, local_dust_limit_satoshi, - local_feerate_per_kw, True, we_are_initiator=True, htlcs=[]) - self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey) - ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220' - self.assertEqual(str(our_commit_tx), ref_commit_tx_str) - - def sign_and_insert_remote_sig(self, tx, remote_pubkey, remote_signature, pubkey, privkey): - assert type(remote_pubkey) is bytes - assert len(remote_pubkey) == 33 - assert type(remote_signature) is str - assert type(pubkey) is bytes - assert type(privkey) is bytes - assert len(pubkey) == 33 - assert len(privkey) == 33 - tx.sign({bh2u(pubkey): (privkey[:-1], True)}) - pubkeys, _x_pubkeys = tx.get_sorted_pubkeys(tx.inputs()[0]) - index_of_pubkey = pubkeys.index(bh2u(remote_pubkey)) - tx._inputs[0]["signatures"][index_of_pubkey] = remote_signature + "01" - tx.raw = None - - def test_commitment_tx_with_all_five_HTLCs_untrimmed_minimum_feerate(self): - to_local_msat = 6988000000 - to_remote_msat = 3000000000 - local_feerate_per_kw = 0 - # base commitment transaction fee = 0 - # actual commitment transaction fee = 0 - - per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 - per_commitment_point = SECP256k1.generator * per_commitment_secret - - remote_htlcpubkey = remotepubkey - local_htlcpubkey = localpubkey - - htlc2_cltv_timeout = 502 - htlc2_payment_preimage = b"\x02" * 32 - htlc2 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc2_payment_preimage)) - # HTLC 2 offered amount 2000 - ref_htlc2_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868" - self.assertEqual(htlc2, bfh(ref_htlc2_wscript)) - - htlc3_cltv_timeout = 503 - htlc3_payment_preimage = b"\x03" * 32 - htlc3 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc3_payment_preimage)) - # HTLC 3 offered amount 3000 - ref_htlc3_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868" - self.assertEqual(htlc3, bfh(ref_htlc3_wscript)) - - htlc0_cltv_timeout = 500 - htlc0_payment_preimage = b"\x00" * 32 - htlc0 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc0_payment_preimage), htlc0_cltv_timeout) - # HTLC 0 received amount 1000 - ref_htlc0_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868" - self.assertEqual(htlc0, bfh(ref_htlc0_wscript)) - - htlc1_cltv_timeout = 501 - htlc1_payment_preimage = b"\x01" * 32 - htlc1 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc1_payment_preimage), htlc1_cltv_timeout) - # HTLC 1 received amount 2000 - ref_htlc1_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868" - self.assertEqual(htlc1, bfh(ref_htlc1_wscript)) - - htlc4_cltv_timeout = 504 - htlc4_payment_preimage = b"\x04" * 32 - htlc4 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc4_payment_preimage), htlc4_cltv_timeout) - # HTLC 4 received amount 4000 - ref_htlc4_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868" - self.assertEqual(htlc4, bfh(ref_htlc4_wscript)) - - # to_local amount 6988000 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" - # local_signature = 30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06 - output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" - - htlc0_msat = 1000 * 1000 - htlc2_msat = 2000 * 1000 - htlc3_msat = 3000 * 1000 - htlc1_msat = 2000 * 1000 - htlc4_msat = 4000 * 1000 - htlcs = [(htlc2, htlc2_msat), (htlc3, htlc3_msat), (htlc0, htlc0_msat), (htlc1, htlc1_msat), (htlc4, htlc4_msat)] - - our_commit_tx = make_commitment( - commitment_number, - local_funding_pubkey, remote_funding_pubkey, remotepubkey, - local_payment_basepoint, remote_payment_basepoint, - local_revocation_pubkey, local_delayedpubkey, local_delay, - funding_tx_id, funding_output_index, funding_amount_satoshi, - to_local_msat, to_remote_msat, local_dust_limit_satoshi, - local_feerate_per_kw, True, we_are_initiator=True, htlcs=htlcs) - self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey) - self.assertEqual(str(our_commit_tx), output_commit_tx) - - # (HTLC 0) - signature_for_output_remote_htlc_0 = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6" - # (HTLC 2) - signature_for_output_remote_htlc_2 = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b" - # (HTLC 1) - signature_for_output_remote_htlc_1 = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202" - # (HTLC 3) - signature_for_output_remote_htlc_3 = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554" - # (HTLC 4) - signature_for_output_remote_htlc_4 = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d" - - - local_signature_htlc0 = "304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5" # derive ourselves - output_htlc_success_tx_0 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000" - - local_signature_htlc2 = "3045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be5" - output_htlc_timeout_tx_2 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000" - - local_signature_htlc1 = "3045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da" - output_htlc_success_tx_1 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000" - - local_signature_htlc3 = "30440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac08727" - output_htlc_timeout_tx_3 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000" - - local_signature_htlc4 = "30440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e" - output_htlc_success_tx_4 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000" - - def test_htlc_tx(htlc, htlc_output_index, amount_msat, ref_local_sig, htlc_payment_preimage, remote_htlc_sig, ref_tx, success, cltv_timeout): - our_htlc_tx_output = make_htlc_tx_output( - amount_msat=amount_msat, - local_feerate=local_feerate_per_kw, - revocationpubkey=local_revocation_pubkey, - local_delayedpubkey=local_delayedpubkey, - success=success, - to_self_delay=local_delay) - our_htlc_tx_inputs = make_htlc_tx_inputs( - htlc_output_txid=our_commit_tx.txid(), - htlc_output_index=htlc_output_index, - revocationpubkey=local_revocation_pubkey, - local_delayedpubkey=local_delayedpubkey, - amount_msat=amount_msat, - witness_script=bh2u(htlc)) - our_htlc_tx = make_htlc_tx(cltv_timeout, - inputs=our_htlc_tx_inputs, - output=our_htlc_tx_output) - - local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1]) - #self.assertEqual(ref_local_sig + "01", local_sig) # commented out as it is sufficient to compare the serialized txn - - our_htlc_tx_witness = make_htlc_tx_witness( # FIXME only correct for success=True - remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL - localhtlcsig=bfh(local_sig), - payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout - witness_script=htlc) - our_htlc_tx._inputs[0]['witness'] = bh2u(our_htlc_tx_witness) - self.assertEqual(ref_tx, str(our_htlc_tx)) - - test_htlc_tx(htlc=htlc0, htlc_output_index=0, - amount_msat=htlc0_msat, - ref_local_sig=local_signature_htlc0, - htlc_payment_preimage=htlc0_payment_preimage, - remote_htlc_sig=signature_for_output_remote_htlc_0, - ref_tx=output_htlc_success_tx_0, - success=True, cltv_timeout=0) - test_htlc_tx(htlc=htlc1, htlc_output_index=2, - amount_msat=htlc1_msat, - ref_local_sig=local_signature_htlc1, - htlc_payment_preimage=htlc1_payment_preimage, - remote_htlc_sig=signature_for_output_remote_htlc_1, - ref_tx=output_htlc_success_tx_1, - success=True, cltv_timeout=0) - test_htlc_tx(htlc=htlc2, htlc_output_index=1, - amount_msat=htlc2_msat, - ref_local_sig=local_signature_htlc2, - htlc_payment_preimage=htlc2_payment_preimage, - remote_htlc_sig=signature_for_output_remote_htlc_2, - ref_tx=output_htlc_timeout_tx_2, - success=False, cltv_timeout=htlc2_cltv_timeout) - test_htlc_tx(htlc=htlc3, htlc_output_index=3, - amount_msat=htlc3_msat, - ref_local_sig=local_signature_htlc3, - htlc_payment_preimage=htlc3_payment_preimage, - remote_htlc_sig=signature_for_output_remote_htlc_3, - ref_tx=output_htlc_timeout_tx_3, - success=False, cltv_timeout=htlc3_cltv_timeout) - test_htlc_tx(htlc=htlc4, htlc_output_index=4, - amount_msat=htlc4_msat, - ref_local_sig=local_signature_htlc4, - htlc_payment_preimage=htlc4_payment_preimage, - remote_htlc_sig=signature_for_output_remote_htlc_4, - ref_tx=output_htlc_success_tx_4, - success=True, cltv_timeout=0) - - def test_find_path_for_payment(self): - class fake_ln_worker: - channel_db = lnrouter.ChannelDB() - path_finder = lnrouter.LNPathFinder(channel_db) - privkey = bitcoin.sha256('privkeyseed') - network = None - channel_state = {} - channels = [] - invoices = {} - p = Peer(fake_ln_worker, '', 0, 'a') - p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')}) - p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')}) - p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')}) - p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')}) - p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')}) - p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')}) - o = lambda i: i.to_bytes(8, "big") - p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)}) - p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)}) - self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000)) - - def test_key_derivation(self): - # BOLT3, Appendix E - base_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f - per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 - revocation_basepoint_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f - base_point = secret_to_pubkey(base_secret) - self.assertEqual(base_point, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2')) - per_commitment_point = secret_to_pubkey(per_commitment_secret) - self.assertEqual(per_commitment_point, bfh('025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486')) - localpubkey = derive_pubkey(base_point, per_commitment_point) - self.assertEqual(localpubkey, bfh('0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5')) - localprivkey = derive_privkey(base_secret, per_commitment_point) - self.assertEqual(localprivkey, 0xcbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f) - revocation_basepoint = secret_to_pubkey(revocation_basepoint_secret) - self.assertEqual(revocation_basepoint, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2')) - revocationpubkey = derive_blinded_pubkey(revocation_basepoint, per_commitment_point) - self.assertEqual(revocationpubkey, bfh('02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0')) - - def test_per_commitment_secret_from_seed(self): - self.assertEqual(0x02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148.to_bytes(byteorder="big", length=32), - get_per_commitment_secret_from_seed(0x0000000000000000000000000000000000000000000000000000000000000000.to_bytes(byteorder="big", length=32), 281474976710655)) - self.assertEqual(0x7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc.to_bytes(byteorder="big", length=32), - get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 281474976710655)) - self.assertEqual(0x56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528.to_bytes(byteorder="big", length=32), - get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0xaaaaaaaaaaa)) - self.assertEqual(0x9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31.to_bytes(byteorder="big", length=32), - get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0x555555555555)) - self.assertEqual(0x915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c.to_bytes(byteorder="big", length=32), - get_per_commitment_secret_from_seed(0x0101010101010101010101010101010101010101010101010101010101010101.to_bytes(byteorder="big", length=32), 1)) - - def test_new_onion_packet(self): - # test vector from bolt-04 - payment_path_pubkeys = [ - bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), - bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'), - bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'), - bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), - bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'), - ] - session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') - associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') - hops_data = [ - OnionHopsDataSingle(OnionPerHop( - bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') - )), - ] - packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) - self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf'), - packet.to_bytes()) - - def test_process_onion_packet(self): - # this test is not from bolt-04, but is based on the one there; - # except here we have the privkeys for these pubkeys - payment_path_pubkeys = [ - bfh('03d75c0ee70f68d73d7d13aeb6261d8ace11416800860c7e59407afe4e2e2d42bb'), - bfh('03960a0b830c7b8e76de745b819f252c62508346196b916f5e813cdb0773283cce'), - bfh('0385620e0a571cbc3552620f8bf1bdcdab2d1a4a59c36fa10b8249114ccbdda40d'), - bfh('02ee242cf6c38b7285f0152c33804ff777f5c51fd352ca8132e845e2cf23b3d8ba'), - bfh('025c585fd2e174bf8245b2b4a119e52a417688904228643ea3edaa1728bf2a258e'), - ] - payment_path_privkeys = [ - bfh('3463a278617b3dd83f79bda7f97673f12609c54386e1f0d2b67b1c6354fda14e'), - bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'), - bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'), - bfh('3d885f374d79a5e777459b083f7818cdc9493e5c4994ac9c7b843de8b70be661'), - bfh('dd72ab44729527b7942e195e7a835e7c71f9c0ff61844eb21274d9c26166a8f8'), - ] - session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') - associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242') - hops_data = [ - OnionHopsDataSingle(OnionPerHop( - bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003') - )), - OnionHopsDataSingle(OnionPerHop( - bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004') - )), - ] - packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data) - self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368661954176cd9869da33d713aa219fcef1e5c806fef11e696bcc66844de8271c27974a0fd57c2dbcb2c6dd4e8ef35d96db28d5a0e49b6ab3d6de31af65950723b8cddc108390bebf8d149002e31bdc283056477ba27c8054c248ad7306de31663a7c99ec659da15d0f6fbc7e1687485b39e9be0ec3b70164cb3618a9b546317e7c2d62ae9f0f840704535729262d30c6132d1b390f073edec8fa057176c6268b6ad06a82ff0d16d4c662194873e8b4ecf46eb2c9d4d58d2ee2021adb19840605ac5afd8bd942dd71e8244c83e28b2ed5a3b09e9e7df5c8c747e5765ba366a4f7407a6c6b0a32f74bc5e428f7fa4c3cf70e13ed91563177d94190d5149aa4b9c96d00e40d2ac35ab9c4a621ce0f6f5df7d64a9c8d435db19de192d9db522c7f7b4e201fc1b61a9bd3efd062ae24455d463818b01e2756c7d0691bc3ac4c017be34c9a8b2913bb1b937e31e0ae40f650a7cd820bcb4996825b1cbad1ff7ccc2b513b1104524c34f6573e1b59201c005a632ee5dccd3711a32e3ba1ff00fcffbe636e4b3a84bbe491b836a57ccec138b8cc2ec733846904d872f305d538d51db8e56232ec6e07877075328874cb7b09c7e799100a9ff085dead253886b174fc408a0ea7b48bce2c5d8992285011960af088f7e006ef60089d46ac9aa15acfac6c87c3cf6904764dd785419292fbafa9cca09c8ade24a6cd63f12d1cfc83fa35cf2f1cf503c39cbf78293f06c68a3cece7177169cd872bb49bf69d933a27a887dd9daefa9239fca9f0c3e309ec61d9df947211da98cf11a6e0fb77252629cdf9f2226dd69ca73fa51be4df224592f8d471b69a1aebbdaa2f3a798b3581253d97feb0a12e6606043ca0fc5efc0f49b8061d6796eff31cd8638499e2f25ffb96eec32837438ed7ebebbe587886648f63e35d80f41869f4c308f2e6970bd65fead5e8544e3239a6acc9d996b08d1546455bcafbe88ed3ed547714841946fe2e77180e4d7bf1452414e4b1745a7897184a2c4cbc3ac46f83342a55a48e29dc8f17cf595dd28f51e297ba89fd25ed0dbd1c0081a810beaab09758a36fbfd16fbdc3daa9fe05c8a73195f244ef2743a5df761f01ee6e693eb6c7f1a7834fab3671391e5ddebf611e119a2ae4456e2cee7a6d4f27a2246cdb1f8ef35f0b3d7044b3799d8d0ed0a6470557fd807c065d6d83acba07e96e10770ada8c0b4d4921522944188d5f30086a6ee0a4795331273f32beaaa43363fc58208a257e5c5c434c7325b583642219d81c7d67b908d5263b42ac1991edc69a777da60f38eff138c844af9e549374e8b29b166211bfded24587a29394e33828b784da7e7b62ab7e49ea2693fcdd17fa96186a5ef11ef1a8adffa50f93a3119e95e6c09014f3e3b0709183fa08a826ced6deb4608b7d986ebbcf99ad58e25451d4d9d38d0059734d8501467b97182cd11e0c07c91ca50f61cc31255a3147ade654976a5989097281892aafd8df595c63bd14f1e03f5955a9398d2dd6368bbcae833ae1cc2df31eb0980b4817dfd130020ffb275743fcc01df40e3ecda1c5988e8e1bde965353b0b1bf34ea05f095000c45b6249618d275905a24d3eb58c600aeab4fb552fbf1ccdb2a5c80ace220310f89829d7e53f78c126037b6d8d500220c7a118d9621b4d6bd5379edd7e24bcf540e87aba6b88862db16fa4ee00b009fda80577be67ab94910fd8a7807dfe4ebe66b8fdcd040aa2dc17ec22639298be56b2a2c9d8940647b75f2f6d81746df16e1cb2f05e23397a8c63baea0803441ff4b7d517ff172980a056726235e2f6af85e8aa9b91ba85f14532272d6170df3166b91169dc09d4f4a251610f57ff0885a93364cfaf650bdf436c89795efed5ca934bc7ffc0a4'), - packet.to_bytes()) - for i, privkey in enumerate(payment_path_privkeys): - processed_packet = lnrouter.process_onion_packet(packet, associated_data, privkey) - self.assertEqual(hops_data[i].per_hop.to_bytes(), processed_packet.hop_data.per_hop.to_bytes()) - packet = processed_packet.next_packet - - def test_decode_onion_error(self): - # test vector from bolt-04 - payment_path_pubkeys = [ - bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), - bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'), - bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'), - bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), - bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'), - ] - session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') - error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d') - decoded_error, index_of_sender = lnrouter._decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key) - self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), - decoded_error) - self.assertEqual(4, index_of_sender) - +class TestLNUtil(unittest.TestCase): def test_shachain_store(self): tests = [ { @@ -817,3 +436,235 @@ class Test_LNBase(unittest.TestCase): except Exception as e: raise Exception("iteration " + str(i) + ": " + str(e)) if i % 1000 == 0: self.assertEqual(consumer.serialize(), RevocationStore.from_json_obj(json.loads(json.dumps(consumer.serialize()))).serialize()) + + def test_commitment_tx_with_all_five_HTLCs_untrimmed_minimum_feerate(self): + to_local_msat = 6988000000 + to_remote_msat = 3000000000 + local_feerate_per_kw = 0 + # base commitment transaction fee = 0 + # actual commitment transaction fee = 0 + + per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 + per_commitment_point = (ecc.generator() * per_commitment_secret).get_public_key_bytes() + + remote_htlcpubkey = remotepubkey + local_htlcpubkey = localpubkey + + htlc2_cltv_timeout = 502 + htlc2_payment_preimage = b"\x02" * 32 + htlc2 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc2_payment_preimage)) + # HTLC 2 offered amount 2000 + ref_htlc2_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868" + self.assertEqual(htlc2, bfh(ref_htlc2_wscript)) + + htlc3_cltv_timeout = 503 + htlc3_payment_preimage = b"\x03" * 32 + htlc3 = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc3_payment_preimage)) + # HTLC 3 offered amount 3000 + ref_htlc3_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868" + self.assertEqual(htlc3, bfh(ref_htlc3_wscript)) + + htlc0_cltv_timeout = 500 + htlc0_payment_preimage = b"\x00" * 32 + htlc0 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc0_payment_preimage), htlc0_cltv_timeout) + # HTLC 0 received amount 1000 + ref_htlc0_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868" + self.assertEqual(htlc0, bfh(ref_htlc0_wscript)) + + htlc1_cltv_timeout = 501 + htlc1_payment_preimage = b"\x01" * 32 + htlc1 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc1_payment_preimage), htlc1_cltv_timeout) + # HTLC 1 received amount 2000 + ref_htlc1_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868" + self.assertEqual(htlc1, bfh(ref_htlc1_wscript)) + + htlc4_cltv_timeout = 504 + htlc4_payment_preimage = b"\x04" * 32 + htlc4 = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc4_payment_preimage), htlc4_cltv_timeout) + # HTLC 4 received amount 4000 + ref_htlc4_wscript = "76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868" + self.assertEqual(htlc4, bfh(ref_htlc4_wscript)) + + # to_local amount 6988000 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac + # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) + remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" + # local_signature = 30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06 + output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + + htlc0_msat = 1000 * 1000 + htlc2_msat = 2000 * 1000 + htlc3_msat = 3000 * 1000 + htlc1_msat = 2000 * 1000 + htlc4_msat = 4000 * 1000 + htlcs = [(htlc2, htlc2_msat), (htlc3, htlc3_msat), (htlc0, htlc0_msat), (htlc1, htlc1_msat), (htlc4, htlc4_msat)] + + our_commit_tx = make_commitment( + commitment_number, + local_funding_pubkey, remote_funding_pubkey, remotepubkey, + local_payment_basepoint, remote_payment_basepoint, + local_revocation_pubkey, local_delayedpubkey, local_delay, + funding_tx_id, funding_output_index, funding_amount_satoshi, + to_local_msat, to_remote_msat, local_dust_limit_satoshi, + local_feerate_per_kw, True, we_are_initiator=True, htlcs=htlcs) + self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey) + self.assertEqual(str(our_commit_tx), output_commit_tx) + + # (HTLC 0) + signature_for_output_remote_htlc_0 = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6" + # (HTLC 2) + signature_for_output_remote_htlc_2 = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b" + # (HTLC 1) + signature_for_output_remote_htlc_1 = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202" + # (HTLC 3) + signature_for_output_remote_htlc_3 = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554" + # (HTLC 4) + signature_for_output_remote_htlc_4 = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d" + + + local_signature_htlc0 = "304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5" # derive ourselves + output_htlc_success_tx_0 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000" + + local_signature_htlc2 = "3045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be5" + output_htlc_timeout_tx_2 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000" + + local_signature_htlc1 = "3045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da" + output_htlc_success_tx_1 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000" + + local_signature_htlc3 = "30440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac08727" + output_htlc_timeout_tx_3 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000" + + local_signature_htlc4 = "30440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e" + output_htlc_success_tx_4 = "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000" + + def test_htlc_tx(htlc, htlc_output_index, amount_msat, ref_local_sig, htlc_payment_preimage, remote_htlc_sig, ref_tx, success, cltv_timeout): + our_htlc_tx_output = make_htlc_tx_output( + amount_msat=amount_msat, + local_feerate=local_feerate_per_kw, + revocationpubkey=local_revocation_pubkey, + local_delayedpubkey=local_delayedpubkey, + success=success, + to_self_delay=local_delay) + our_htlc_tx_inputs = make_htlc_tx_inputs( + htlc_output_txid=our_commit_tx.txid(), + htlc_output_index=htlc_output_index, + revocationpubkey=local_revocation_pubkey, + local_delayedpubkey=local_delayedpubkey, + amount_msat=amount_msat, + witness_script=bh2u(htlc)) + our_htlc_tx = make_htlc_tx(cltv_timeout, + inputs=our_htlc_tx_inputs, + output=our_htlc_tx_output) + + local_sig = our_htlc_tx.sign_txin(0, local_privkey[:-1]) + #self.assertEqual(ref_local_sig + "01", local_sig) # commented out as it is sufficient to compare the serialized txn + + our_htlc_tx_witness = make_htlc_tx_witness( # FIXME only correct for success=True + remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL + localhtlcsig=bfh(local_sig), + payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout + witness_script=htlc) + our_htlc_tx._inputs[0]['witness'] = bh2u(our_htlc_tx_witness) + self.assertEqual(ref_tx, str(our_htlc_tx)) + + test_htlc_tx(htlc=htlc0, htlc_output_index=0, + amount_msat=htlc0_msat, + ref_local_sig=local_signature_htlc0, + htlc_payment_preimage=htlc0_payment_preimage, + remote_htlc_sig=signature_for_output_remote_htlc_0, + ref_tx=output_htlc_success_tx_0, + success=True, cltv_timeout=0) + test_htlc_tx(htlc=htlc1, htlc_output_index=2, + amount_msat=htlc1_msat, + ref_local_sig=local_signature_htlc1, + htlc_payment_preimage=htlc1_payment_preimage, + remote_htlc_sig=signature_for_output_remote_htlc_1, + ref_tx=output_htlc_success_tx_1, + success=True, cltv_timeout=0) + test_htlc_tx(htlc=htlc2, htlc_output_index=1, + amount_msat=htlc2_msat, + ref_local_sig=local_signature_htlc2, + htlc_payment_preimage=htlc2_payment_preimage, + remote_htlc_sig=signature_for_output_remote_htlc_2, + ref_tx=output_htlc_timeout_tx_2, + success=False, cltv_timeout=htlc2_cltv_timeout) + test_htlc_tx(htlc=htlc3, htlc_output_index=3, + amount_msat=htlc3_msat, + ref_local_sig=local_signature_htlc3, + htlc_payment_preimage=htlc3_payment_preimage, + remote_htlc_sig=signature_for_output_remote_htlc_3, + ref_tx=output_htlc_timeout_tx_3, + success=False, cltv_timeout=htlc3_cltv_timeout) + test_htlc_tx(htlc=htlc4, htlc_output_index=4, + amount_msat=htlc4_msat, + ref_local_sig=local_signature_htlc4, + htlc_payment_preimage=htlc4_payment_preimage, + remote_htlc_sig=signature_for_output_remote_htlc_4, + ref_tx=output_htlc_success_tx_4, + success=True, cltv_timeout=0) + + def test_per_commitment_secret_from_seed(self): + self.assertEqual(0x02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148.to_bytes(byteorder="big", length=32), + get_per_commitment_secret_from_seed(0x0000000000000000000000000000000000000000000000000000000000000000.to_bytes(byteorder="big", length=32), 281474976710655)) + self.assertEqual(0x7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc.to_bytes(byteorder="big", length=32), + get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 281474976710655)) + self.assertEqual(0x56f4008fb007ca9acf0e15b054d5c9fd12ee06cea347914ddbaed70d1c13a528.to_bytes(byteorder="big", length=32), + get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0xaaaaaaaaaaa)) + self.assertEqual(0x9015daaeb06dba4ccc05b91b2f73bd54405f2be9f217fbacd3c5ac2e62327d31.to_bytes(byteorder="big", length=32), + get_per_commitment_secret_from_seed(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.to_bytes(byteorder="big", length=32), 0x555555555555)) + self.assertEqual(0x915c75942a26bb3a433a8ce2cb0427c29ec6c1775cfc78328b57f6ba7bfeaa9c.to_bytes(byteorder="big", length=32), + get_per_commitment_secret_from_seed(0x0101010101010101010101010101010101010101010101010101010101010101.to_bytes(byteorder="big", length=32), 1)) + + def test_key_derivation(self): + # BOLT3, Appendix E + base_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f + per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 + revocation_basepoint_secret = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f + base_point = secret_to_pubkey(base_secret) + self.assertEqual(base_point, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2')) + per_commitment_point = secret_to_pubkey(per_commitment_secret) + self.assertEqual(per_commitment_point, bfh('025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486')) + localpubkey = derive_pubkey(base_point, per_commitment_point) + self.assertEqual(localpubkey, bfh('0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5')) + localprivkey = derive_privkey(base_secret, per_commitment_point) + self.assertEqual(localprivkey, 0xcbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f) + revocation_basepoint = secret_to_pubkey(revocation_basepoint_secret) + self.assertEqual(revocation_basepoint, bfh('036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2')) + revocationpubkey = derive_blinded_pubkey(revocation_basepoint, per_commitment_point) + self.assertEqual(revocationpubkey, bfh('02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0')) + + def test_simple_commitment_tx_with_no_HTLCs(self): + to_local_msat = 7000000000 + to_remote_msat = 3000000000 + local_feerate_per_kw = 15000 + # base commitment transaction fee = 10860 + # actual commitment transaction fee = 10860 + # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac + # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) + remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" + # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 + htlcs=[] + our_commit_tx = make_commitment( + commitment_number, + local_funding_pubkey, remote_funding_pubkey, remotepubkey, + local_payment_basepoint, remote_payment_basepoint, + local_revocation_pubkey, local_delayedpubkey, local_delay, + funding_tx_id, funding_output_index, funding_amount_satoshi, + to_local_msat, to_remote_msat, local_dust_limit_satoshi, + local_feerate_per_kw, True, we_are_initiator=True, htlcs=[]) + self.sign_and_insert_remote_sig(our_commit_tx, remote_funding_pubkey, remote_signature, local_funding_pubkey, local_funding_privkey) + ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220' + self.assertEqual(str(our_commit_tx), ref_commit_tx_str) + + def sign_and_insert_remote_sig(self, tx, remote_pubkey, remote_signature, pubkey, privkey): + assert type(remote_pubkey) is bytes + assert len(remote_pubkey) == 33 + assert type(remote_signature) is str + assert type(pubkey) is bytes + assert type(privkey) is bytes + assert len(pubkey) == 33 + assert len(privkey) == 33 + tx.sign({bh2u(pubkey): (privkey[:-1], True)}) + pubkeys, _x_pubkeys = tx.get_sorted_pubkeys(tx.inputs()[0]) + index_of_pubkey = pubkeys.index(bh2u(remote_pubkey)) + tx._inputs[0]["signatures"][index_of_pubkey] = remote_signature + "01" + tx.raw = None From 5b05e37c154f605a2301860bc95ab63fd13ef6f8 Mon Sep 17 00:00:00 2001 From: Janus Date: Thu, 28 Jun 2018 16:04:25 +0200 Subject: [PATCH 5/5] ln: avoid code duplication --- lib/lnutil.py | 4 ++-- lib/tests/test_lnutil.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/lnutil.py b/lib/lnutil.py index 934122d2..c5e0ca80 100644 --- a/lib/lnutil.py +++ b/lib/lnutil.py @@ -3,7 +3,7 @@ from .crypto import sha256 import json from collections import namedtuple from .transaction import Transaction -from .ecc import CURVE_ORDER, generator, sig_string_from_der_sig, ECPubkey, string_to_number +from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number from . import ecc, bitcoin, crypto, transaction from .transaction import opcodes from .bitcoin import push_script @@ -86,7 +86,7 @@ def derive_pubkey(basepoint, per_commitment_point): def derive_privkey(secret, per_commitment_point): assert type(secret) is int - basepoint = (ecc.generator() * secret).get_public_key_bytes() + basepoint = secret_to_pubkey(secret) basepoint = secret + ecc.string_to_number(sha256(per_commitment_point + basepoint)) basepoint %= CURVE_ORDER return basepoint diff --git a/lib/tests/test_lnutil.py b/lib/tests/test_lnutil.py index c214c48a..9fb1b965 100644 --- a/lib/tests/test_lnutil.py +++ b/lib/tests/test_lnutil.py @@ -1,6 +1,6 @@ import unittest import json -from lib import bitcoin, ecc +from lib import bitcoin from lib.lnutil import RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc, 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 from lib.util import bh2u, bfh @@ -445,7 +445,7 @@ class TestLNUtil(unittest.TestCase): # actual commitment transaction fee = 0 per_commitment_secret = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100 - per_commitment_point = (ecc.generator() * per_commitment_secret).get_public_key_bytes() + per_commitment_point = secret_to_pubkey(per_commitment_secret) remote_htlcpubkey = remotepubkey local_htlcpubkey = localpubkey