electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 35adc3231b297d03ad0a0534d65665ad14c0d9f6
parent 1db7a8334afc8b2b60c7b87be1a12917439c1549
Author: Janus <ysangkok@gmail.com>
Date:   Fri, 13 Jul 2018 17:05:04 +0200

lightning: fixup after rebasing on restructured master

Diffstat:
Aelectrum/gui/kivy/uix/dialogs/lightning_channels.py | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelectrum/gui/kivy/uix/dialogs/lightning_payer.py | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rgui/qt/channels_list.py -> electrum/gui/qt/channels_list.py | 0
Rlib/lightning.json -> electrum/lightning.json | 0
Rlib/lnaddr.py -> electrum/lnaddr.py | 0
Rlib/lnbase.py -> electrum/lnbase.py | 0
Rlib/lnhtlc.py -> electrum/lnhtlc.py | 0
Rlib/lnrouter.py -> electrum/lnrouter.py | 0
Rlib/lnutil.py -> electrum/lnutil.py | 0
Rlib/lnwatcher.py -> electrum/lnwatcher.py | 0
Rlib/lnworker.py -> electrum/lnworker.py | 0
Aelectrum/tests/test_bolt11.py | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelectrum/tests/test_lnhtlc.py | 344+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelectrum/tests/test_lnrouter.py | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelectrum/tests/test_lnutil.py | 677+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dgui/kivy/uix/dialogs/lightning_channels.py | 123-------------------------------------------------------------------------------
Dgui/kivy/uix/dialogs/lightning_payer.py | 93-------------------------------------------------------------------------------
Dlib/tests/test_bolt11.py | 97-------------------------------------------------------------------------------
Dlib/tests/test_lnhtlc.py | 344-------------------------------------------------------------------------------
Dlib/tests/test_lnrouter.py | 151------------------------------------------------------------------------------
Dlib/tests/test_lnutil.py | 677-------------------------------------------------------------------------------
21 files changed, 1485 insertions(+), 1485 deletions(-)

diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -0,0 +1,123 @@ +import binascii +from kivy.lang import Builder +from kivy.factory import Factory +from kivy.uix.popup import Popup +from kivy.clock import Clock +from electrum.gui.kivy.uix.context_menu import ContextMenu + +Builder.load_string(''' +<LightningChannelItem@CardItem> + details: {} + active: False + channelId: '<channelId not set>' + Label: + text: root.channelId + +<LightningChannelsDialog@Popup>: + name: 'lightning_channels' + BoxLayout: + id: box + orientation: 'vertical' + spacing: '1dp' + ScrollView: + GridLayout: + cols: 1 + id: lightning_channels_container + size_hint: 1, None + height: self.minimum_height + spacing: '2dp' + padding: '12dp' + +<ChannelDetailsItem@BoxLayout>: + canvas.before: + Color: + rgba: 0.5, 0.5, 0.5, 1 + Rectangle: + size: self.size + pos: self.pos + value: '' + Label: + text: root.value + text_size: self.size # this makes the text not overflow, but wrap + +<ChannelDetailsRow@BoxLayout>: + keyName: '' + value: '' + ChannelDetailsItem: + value: root.keyName + size_hint_x: 0.5 # this makes the column narrower + + # see https://blog.kivy.org/2014/07/wrapping-text-in-kivys-label/ + ScrollView: + Label: + text: root.value + size_hint_y: None + text_size: self.width, None + height: self.texture_size[1] + +<ChannelDetailsList@RecycleView>: + scroll_type: ['bars', 'content'] + scroll_wheel_distance: dp(114) + bar_width: dp(10) + viewclass: 'ChannelDetailsRow' + RecycleBoxLayout: + default_size: None, dp(56) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + orientation: 'vertical' + spacing: dp(2) + +<ChannelDetailsPopup@Popup>: + id: popuproot + data: [] + ChannelDetailsList: + data: popuproot.data +''') + +class ChannelDetailsPopup(Popup): + def __init__(self, data, **kwargs): + super(ChanenlDetailsPopup,self).__init__(**kwargs) + self.data = data + +class LightningChannelsDialog(Factory.Popup): + def __init__(self, app): + super(LightningChannelsDialog, self).__init__() + self.clocks = [] + self.app = app + self.context_menu = None + self.app.wallet.lnworker.subscribe_channel_list_updates_from_other_thread(self.rpc_result_handler) + + def show_channel_details(self, obj): + p = Factory.ChannelDetailsPopup() + p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()] + p.open() + + def close_channel(self, obj): + print("UNIMPLEMENTED asked to close channel", obj.channelId) # TODO + + def show_menu(self, obj): + self.hide_menu() + self.context_menu = ContextMenu(obj, [("Close", self.close_channel), + ("Details", self.show_channel_details)]) + self.ids.box.add_widget(self.context_menu) + + def hide_menu(self): + if self.context_menu is not None: + self.ids.box.remove_widget(self.context_menu) + self.context_menu = None + + def rpc_result_handler(self, res): + channel_cards = self.ids.lightning_channels_container + channel_cards.clear_widgets() + if "channels" in res: + for i in res["channels"]: + item = Factory.LightningChannelItem() + item.screen = self + print(i) + item.channelId = i["chan_id"] + item.active = i["active"] + item.details = i + channel_cards.add_widget(item) + else: + self.app.show_info(res) diff --git a/electrum/gui/kivy/uix/dialogs/lightning_payer.py b/electrum/gui/kivy/uix/dialogs/lightning_payer.py @@ -0,0 +1,93 @@ +import binascii +from kivy.lang import Builder +from kivy.factory import Factory +from electrum.gui.kivy.i18n import _ +from kivy.clock import mainthread +from electrum.lnaddr import lndecode + +Builder.load_string(''' +<LightningPayerDialog@Popup> + id: s + name: 'lightning_payer' + invoice_data: '' + BoxLayout: + orientation: "vertical" + BlueButton: + text: s.invoice_data if s.invoice_data else _('Lightning invoice') + shorten: True + on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the lightning invoice using the Paste button, or use the camera to scan a QR code.'))) + GridLayout: + cols: 4 + size_hint: 1, None + height: '48dp' + IconButton: + id: qr + on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=s.on_lightning_qr)) + icon: 'atlas://gui/kivy/theming/light/camera' + Button: + text: _('Paste') + on_release: s.do_paste() + Button: + text: _('Paste using xclip') + on_release: s.do_paste_xclip() + Button: + text: _('Clear') + on_release: s.do_clear() + Button: + size_hint: 1, None + height: '48dp' + text: _('Open channel to pubkey in invoice') + on_release: s.do_open_channel() + Button: + size_hint: 1, None + height: '48dp' + text: _('Pay pasted/scanned invoice') + on_release: s.do_pay() +''') + +class LightningPayerDialog(Factory.Popup): + def __init__(self, app): + super(LightningPayerDialog, self).__init__() + self.app = app + + #def open(self, *args, **kwargs): + # super(LightningPayerDialog, self).open(*args, **kwargs) + #def dismiss(self, *args, **kwargs): + # super(LightningPayerDialog, self).dismiss(*args, **kwargs) + + def do_paste_xclip(self): + import subprocess + proc = subprocess.run(["xclip","-sel","clipboard","-o"], stdout=subprocess.PIPE) + self.invoice_data = proc.stdout.decode("ascii") + + def do_paste(self): + contents = self.app._clipboard.paste() + if not contents: + self.app.show_info(_("Clipboard is empty")) + return + self.invoice_data = contents + + def do_clear(self): + self.invoice_data = "" + + def do_open_channel(self): + compressed_pubkey_bytes = lndecode(self.invoice_data).pubkey.serialize() + hexpubkey = binascii.hexlify(compressed_pubkey_bytes).decode("ascii") + local_amt = 200000 + push_amt = 100000 + + def on_success(pw): + # node_id, local_amt, push_amt, emit_function, get_password + self.app.wallet.lnworker.open_channel_from_other_thread(hexpubkey, local_amt, push_amt, mainthread(lambda parent: self.app.show_info(_("Channel open, waiting for locking..."))), lambda: pw) + + if self.app.wallet.has_keystore_encryption(): + # wallet, msg, on_success (Tuple[str, str] -> ()), on_failure (() -> ()) + self.app.password_dialog(self.app.wallet, _("Password needed for opening channel"), on_success, lambda: self.app.show_error(_("Failed getting password from you"))) + else: + on_success("") + + def do_pay(self): + self.app.wallet.lnworker.pay_invoice_from_other_thread(self.invoice_data) + + def on_lightning_qr(self, data): + self.invoice_data = str(data) diff --git a/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py diff --git a/lib/lightning.json b/electrum/lightning.json diff --git a/lib/lnaddr.py b/electrum/lnaddr.py diff --git a/lib/lnbase.py b/electrum/lnbase.py diff --git a/lib/lnhtlc.py b/electrum/lnhtlc.py diff --git a/lib/lnrouter.py b/electrum/lnrouter.py diff --git a/lib/lnutil.py b/electrum/lnutil.py diff --git a/lib/lnwatcher.py b/electrum/lnwatcher.py diff --git a/lib/lnworker.py b/electrum/lnworker.py diff --git a/electrum/tests/test_bolt11.py b/electrum/tests/test_bolt11.py @@ -0,0 +1,97 @@ +from hashlib import sha256 +from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5 +from decimal import Decimal +from binascii import unhexlify, hexlify +from electrum.segwit_addr import bech32_encode, bech32_decode +import pprint +import unittest + +RHASH=unhexlify('0001020304050607080900010203040506070809000102030405060708090102') +CONVERSION_RATE=1200 +PRIVKEY=unhexlify('e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734') +PUBKEY=unhexlify('03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad') + +class TestBolt11(unittest.TestCase): + def test_shorten_amount(self): + tests = { + Decimal(10)/10**12: '10p', + Decimal(1000)/10**12: '1n', + Decimal(1200)/10**12: '1200p', + Decimal(123)/10**6: '123u', + Decimal(123)/1000: '123m', + Decimal(3): '3', + } + + for i, o in tests.items(): + assert shorten_amount(i) == o + assert unshorten_amount(shorten_amount(i)) == i + + @staticmethod + def compare(a, b): + + if len([t[1] for t in a.tags if t[0] == 'h']) == 1: + h1 = sha256([t[1] for t in a.tags if t[0] == 'h'][0].encode('utf-8')).digest() + h2 = [t[1] for t in b.tags if t[0] == 'h'][0] + assert h1 == h2 + + # Need to filter out these, since they are being modified during + # encoding, i.e., hashed + a.tags = [t for t in a.tags if t[0] != 'h' and t[0] != 'n'] + b.tags = [t for t in b.tags if t[0] != 'h' and t[0] != 'n'] + + assert b.pubkey.serialize() == PUBKEY, (hexlify(b.pubkey.serialize()), hexlify(PUBKEY)) + assert b.signature != None + + # Unset these, they are generated during encoding/decoding + b.pubkey = None + b.signature = None + + assert a.__dict__ == b.__dict__, (pprint.pformat([a.__dict__, b.__dict__])) + + def test_roundtrip(self): + longdescription = ('One piece of chocolate cake, one icecream cone, one' + ' pickle, one slice of swiss cheese, one slice of salami,' + ' one lollypop, one piece of cherry pie, one sausage, one' + ' cupcake, and one slice of watermelon') + + + tests = [ + LnAddr(RHASH, tags=[('d', '')]), + LnAddr(RHASH, amount=Decimal('0.001'), + tags=[('d', '1 cup coffee'), ('x', 60)]), + LnAddr(RHASH, amount=Decimal('1'), tags=[('h', longdescription)]), + LnAddr(RHASH, currency='tb', tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]), + LnAddr(RHASH, amount=24, tags=[ + ('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', '1RustyRX2oai4EYYDpQGWvEL62BBGqN9T'), ('h', longdescription)]), + LnAddr(RHASH, amount=24, tags=[('f', '3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX'), ('h', longdescription)]), + LnAddr(RHASH, amount=24, tags=[('f', 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), ('h', longdescription)]), + LnAddr(RHASH, amount=24, tags=[('f', 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'), ('h', longdescription)]), + LnAddr(RHASH, amount=24, tags=[('n', PUBKEY), ('h', longdescription)]), + ] + + # Roundtrip + for t in tests: + o = lndecode(lnencode(t, PRIVKEY), False, t.currency) + self.compare(t, o) + + def test_n_decoding(self): + # We flip the signature recovery bit, which would normally give a different + # pubkey. + hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24, tags=[('d', '')]), PRIVKEY), True) + databits = u5_to_bitarray(data) + databits.invert(-1) + lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True) + assert lnaddr.pubkey.serialize() != PUBKEY + + # But not if we supply expliciy `n` specifier! + hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24, + tags=[('d', ''), + ('n', PUBKEY)]), + PRIVKEY), True) + databits = u5_to_bitarray(data) + databits.invert(-1) + lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True) + assert lnaddr.pubkey.serialize() == PUBKEY + + def test_min_final_cltv_expiry(self): + self.assertEquals(lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe", expected_hrp="sb").min_final_cltv_expiry, 144) diff --git a/electrum/tests/test_lnhtlc.py b/electrum/tests/test_lnhtlc.py @@ -0,0 +1,344 @@ +# ported from lnd 42de4400bff5105352d0552155f73589166d162b + +import unittest +import electrum.bitcoin as bitcoin +import electrum.lnbase as lnbase +import electrum.lnhtlc as lnhtlc +import electrum.lnutil as lnutil +import electrum.util as util +import os +import binascii + +def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv): + assert local_amount > 0 + assert remote_amount > 0 + channel_id, _ = lnbase.channel_id_from_funding_tx(funding_txid, funding_index) + their_revocation_store = lnbase.RevocationStore() + local_config=lnbase.ChannelConfig( + payment_basepoint=privkeys[0], + multisig_key=privkeys[1], + htlc_basepoint=privkeys[2], + delayed_basepoint=privkeys[3], + revocation_basepoint=privkeys[4], + to_self_delay=l_csv, + dust_limit_sat=l_dust, + max_htlc_value_in_flight_msat=500000 * 1000, + max_accepted_htlcs=5 + ) + remote_config=lnbase.ChannelConfig( + payment_basepoint=other_pubkeys[0], + multisig_key=other_pubkeys[1], + htlc_basepoint=other_pubkeys[2], + delayed_basepoint=other_pubkeys[3], + revocation_basepoint=other_pubkeys[4], + to_self_delay=r_csv, + dust_limit_sat=r_dust, + max_htlc_value_in_flight_msat=500000 * 1000, + max_accepted_htlcs=5 + ) + + 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, + amount_msat=remote_amount, + revocation_store=their_revocation_store, + next_htlc_id = 0, + feerate=local_feerate + ), + "local_state":lnbase.LocalState( + ctn = 0, + per_commitment_secret_seed=seed, + amount_msat=local_amount, + next_htlc_id = 0, + funding_locked_received=True, + was_announced=False, + current_commitment_signature=None, + feerate=local_feerate + ), + "constraints":lnbase.ChannelConstraints(capacity=funding_sat, 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') + xprv, xpub = bitcoin.bip32_private_derivation(xprv, "m/", sequence) + xtype, depth, fingerprint, child_number, c, k = bitcoin.deserialize_xprv(xprv) + assert len(k) == 32 + assert type(k) is bytes + return k + +def create_test_channels(): + funding_txid = binascii.hexlify(os.urandom(32)).decode("ascii") + funding_index = 0 + funding_sat = bitcoin.COIN * 10 + local_amount = (funding_sat * 1000) // 2 + remote_amount = (funding_sat * 1000) // 2 + alice_raw = [ bip32("m/" + str(i)) for i in range(5) ] + bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ] + alice_privkeys = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in alice_raw] + bob_privkeys = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in bob_raw] + alice_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys] + bob_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys] + + alice_seed = os.urandom(32) + bob_seed = os.urandom(32) + + 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( + create_channel_state(funding_txid, funding_index, funding_sat, 6000, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \ + lnhtlc.HTLCStateMachine( + create_channel_state(funding_txid, funding_index, funding_sat, 6000, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob") + +one_bitcoin_in_msat = bitcoin.COIN * 1000 + +class TestLNBaseHTLCStateMachine(unittest.TestCase): + def assertOutputExistsByValue(self, tx, amt_sat): + for typ, scr, val in tx.outputs(): + if val == amt_sat: + break + else: + self.assertFalse() + + def setUp(self): + # Create a test channel which will be used for the duration of this + # unittest. The channel will be funded evenly with Alice having 5 BTC, + # and Bob having 5 BTC. + self.alice_channel, self.bob_channel = create_test_channels() + + self.paymentPreimage = b"\x01" * 32 + paymentHash = bitcoin.sha256(self.paymentPreimage) + self.htlc = lnhtlc.UpdateAddHtlc( + payment_hash = paymentHash, + amount_msat = one_bitcoin_in_msat, + cltv_expiry = 5, + total_fee = 0 + ) + + # First Alice adds the outgoing HTLC to her local channel's state + # update log. Then Alice sends this wire message over to Bob who adds + # this htlc to his remote state update log. + self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc) + + self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc) + + def test_SimpleAddSettleWorkflow(self): + alice_channel, bob_channel = self.alice_channel, self.bob_channel + htlc = self.htlc + + # Next alice commits this change by sending a signature message. Since + # we expect the messages to be ordered, Bob will receive the HTLC we + # just sent before he receives this signature, so the signature will + # cover the HTLC. + aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment() + + self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature") + + # Bob receives this signature message, and checks that this covers the + # state he has in his remote log. This includes the HTLC just sent + # from Alice. + bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs) + + # Bob revokes his prior commitment given to him by Alice, since he now + # has a valid signature for a newer commitment. + bobRevocation, _ = bob_channel.revoke_current_commitment() + + # Bob finally send a signature for Alice's commitment transaction. + # This signature will cover the HTLC, since Bob will first send the + # revocation just created. The revocation also acks every received + # HTLC up to the point where Alice sent here signature. + bobSig, bobHtlcSigs = bob_channel.sign_next_commitment() + + # Alice then processes this revocation, sending her own revocation for + # her prior commitment transaction. Alice shouldn't have any HTLCs to + # forward since she's sending an outgoing HTLC. + alice_channel.receive_revocation(bobRevocation) + + # Alice then processes bob's signature, and since she just received + # the revocation, she expect this signature to cover everything up to + # the point where she sent her signature, including the HTLC. + alice_channel.receive_new_commitment(bobSig, bobHtlcSigs) + + # Alice then generates a revocation for bob. + aliceRevocation, _ = alice_channel.revoke_current_commitment() + + # Finally Bob processes Alice's revocation, at this point the new HTLC + # is fully locked in within both commitment transactions. Bob should + # also be able to forward an HTLC now that the HTLC has been locked + # into both commitment transactions. + bob_channel.receive_revocation(aliceRevocation) + + # At this point, both sides should have the proper number of satoshis + # sent, and commitment height updated within their local channel + # state. + aliceSent = 0 + bobSent = 0 + + self.assertEqual(alice_channel.total_msat_sent, aliceSent, "alice has incorrect milli-satoshis sent") + 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.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. + self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs())) + self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs())) + self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000) + self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000) + + # Now we'll repeat a similar exchange, this time with Bob settling the + # HTLC once he learns of the preimage. + preimage = self.paymentPreimage + bob_channel.settle_htlc(preimage, self.bobHtlcIndex) + + alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex) + + bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment() + alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2) + + aliceRevocation2, _ = alice_channel.revoke_current_commitment() + aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment() + self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures") + + bob_channel.receive_revocation(aliceRevocation2) + + bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2) + + bobRevocation2, _ = bob_channel.revoke_current_commitment() + alice_channel.receive_revocation(bobRevocation2) + + # At this point, Bob should have 6 BTC settled, with Alice still having + # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel + # should show 1 BTC received. They should also be at commitment height + # two, with the revocation window extended by 1 (5). + mSatTransferred = one_bitcoin_in_msat + self.assertEqual(alice_channel.total_msat_sent, mSatTransferred, "alice satoshis sent incorrect %s vs %s expected"% (alice_channel.total_msat_sent, mSatTransferred)) + self.assertEqual(alice_channel.total_msat_received, 0, "alice satoshis received incorrect %s vs %s expected"% (alice_channel.total_msat_received, 0)) + self.assertEqual(bob_channel.total_msat_received, mSatTransferred, "bob satoshis received incorrect %s vs %s expected"% (bob_channel.total_msat_received, mSatTransferred)) + self.assertEqual(bob_channel.total_msat_sent, 0, "bob satoshis sent incorrect %s vs %s expected"% (bob_channel.total_msat_sent, 0)) + self.assertEqual(bob_channel.l_current_height, 2, "bob has incorrect commitment height, %s vs %s"% (bob_channel.l_current_height, 2)) + self.assertEqual(alice_channel.l_current_height, 2, "alice has incorrect commitment height, %s vs %s"% (alice_channel.l_current_height, 2)) + + # The logs of both sides should now be cleared since the entry adding + # the HTLC should have been removed once both sides receive the + # revocation. + self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log)) + self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log)) + + def alice_to_bob_fee_update(self): + fee = 111 + self.alice_channel.update_fee(fee) + self.bob_channel.receive_update_fee(fee) + return fee + + def test_UpdateFeeSenderCommits(self): + fee = self.alice_to_bob_fee_update() + + alice_channel, bob_channel = self.alice_channel, self.bob_channel + + alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() + bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) + + self.assertNotEqual(fee, bob_channel.local_state.feerate) + rev, _ = bob_channel.revoke_current_commitment() + self.assertEqual(fee, bob_channel.local_state.feerate) + + bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() + alice_channel.receive_revocation(rev) + alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) + + self.assertNotEqual(fee, alice_channel.local_state.feerate) + rev, _ = alice_channel.revoke_current_commitment() + self.assertEqual(fee, alice_channel.local_state.feerate) + + bob_channel.receive_revocation(rev) + + + def test_UpdateFeeReceiverCommits(self): + fee = self.alice_to_bob_fee_update() + + alice_channel, bob_channel = self.alice_channel, self.bob_channel + + bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() + alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) + + alice_revocation, _ = alice_channel.revoke_current_commitment() + bob_channel.receive_revocation(alice_revocation) + alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() + bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) + + self.assertNotEqual(fee, bob_channel.local_state.feerate) + bob_revocation, _ = bob_channel.revoke_current_commitment() + self.assertEqual(fee, bob_channel.local_state.feerate) + + bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() + alice_channel.receive_revocation(bob_revocation) + alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) + + self.assertNotEqual(fee, alice_channel.local_state.feerate) + alice_revocation, _ = alice_channel.revoke_current_commitment() + self.assertEqual(fee, alice_channel.local_state.feerate) + + bob_channel.receive_revocation(alice_revocation) + + + +class TestLNHTLCDust(unittest.TestCase): + def test_HTLCDustLimit(self): + alice_channel, bob_channel = create_test_channels() + + paymentPreimage = b"\x01" * 32 + paymentHash = bitcoin.sha256(paymentPreimage) + fee_per_kw = alice_channel.local_state.feerate + self.assertEqual(fee_per_kw, 6000) + htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) + self.assertEqual(htlcAmt, 4478) + htlc = lnhtlc.UpdateAddHtlc( + payment_hash = paymentHash, + amount_msat = 1000 * htlcAmt, + cltv_expiry = 5, # also in create_test_channels + total_fee = 0 + ) + + 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 + htlcAmt) + 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 force_state_transition(chanA, chanB): + chanB.receive_new_commitment(*chanA.sign_next_commitment()) + rev, _ = chanB.revoke_current_commitment() + bob_sig, bob_htlc_sigs = chanB.sign_next_commitment() + chanA.receive_revocation(rev) + chanA.receive_new_commitment(bob_sig, bob_htlc_sigs) + chanB.receive_revocation(chanA.revoke_current_commitment()[0]) + +# calcStaticFee calculates appropriate fees for commitment transactions. This +# function provides a simple way to allow test balance assertions to take fee +# calculations into account. +def calc_static_fee(numHTLCs): + commitWeight = 724 + htlcWeight = 172 + feePerKw = 24//4 * 1000 + return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000 diff --git a/electrum/tests/test_lnrouter.py b/electrum/tests/test_lnrouter.py @@ -0,0 +1,151 @@ +import unittest + +from electrum.util import bh2u, bfh +from electrum.lnbase import Peer +from electrum.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop +from electrum 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() + trigger_callback = lambda x: None + 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/electrum/tests/test_lnutil.py b/electrum/tests/test_lnutil.py @@ -0,0 +1,677 @@ +import unittest +import json +from electrum import bitcoin +from electrum.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, extract_ctn_from_tx, UnableToDeriveSecret) +from electrum.util import bh2u, bfh +from electrum.transaction import Transaction + +funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be' +funding_output_index = 0 +funding_amount_satoshi = 10000000 +commitment_number = 42 +local_delay = 144 +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) +local_funding_privkey = bytes.fromhex('30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901') +local_funding_pubkey = bytes.fromhex('023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb') +remote_funding_pubkey = bytes.fromhex('030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1') +local_privkey = bytes.fromhex('bb13b121cdc357cd2e608b0aea294afca36e2b34cf958e2e6451a2f27469449101') +localpubkey = bytes.fromhex('030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e7') +remotepubkey = bytes.fromhex('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b') +local_delayedpubkey = bytes.fromhex('03fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c') +local_revocation_pubkey = bytes.fromhex('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19') +# funding wscript = 5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae + +class TestLNUtil(unittest.TestCase): + def test_shachain_store(self): + tests = [ + { + "name": "insert_secret correct sequence", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1" +\ + "a8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab2" +\ + "1e9b506fd4998a51d54502e99116", + "successful": True + }, + { + "index": 281474976710651, + "secret": "c65716add7aa98ba7acb236352d665cab173" +\ + "45fe45b55fb879ff80e6bd0c41dd", + "successful": True + }, + { + "index": 281474976710650, + "secret": "969660042a28f32d9be17344e09374b37996" +\ + "2d03db1574df5a8a5a47e19ce3f2", + "successful": True + }, + { + "index": 281474976710649, + "secret": "a5a64476122ca0925fb344bdc1854c1c0a59" +\ + "fc614298e50a33e331980a220f32", + "successful": True + }, + { + "index": 281474976710648, + "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ + "31447732e3802e1f7ac44b650e17", + "successful": True + } + ] + }, + { + "name": "insert_secret #1 incorrect", + "inserts": [ + { + "index": 281474976710655, + "secret": "02a40c85b6f28da08dfdbe0926c53fab2d" +\ + "e6d28c10301f8f7c4073d5e42e3148", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659" +\ + "c1a8b4b5bec0c4b872abeba4cb8964", + "successful": False + } + ] + }, + { + "name": "insert_secret #2 incorrect (#1 derived from incorrect)", + "inserts": [ + { + "index": 281474976710655, + "secret": "02a40c85b6f28da08dfdbe0926c53fab2de6" +\ + "d28c10301f8f7c4073d5e42e3148", + "successful": True + }, + { + "index": 281474976710654, + "secret": "dddc3a8d14fddf2b68fa8c7fbad274827493" +\ + "7479dd0f8930d5ebb4ab6bd866a3", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22a" +\ + "b21e9b506fd4998a51d54502e99116", + "successful": False + } + ] + }, + { + "name": "insert_secret #3 incorrect", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1" +\ + "a8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "c51a18b13e8527e579ec56365482c62f180b" +\ + "7d5760b46e9477dae59e87ed423a", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab2" +\ + "1e9b506fd4998a51d54502e99116", + "successful": False + } + ] + }, + { + "name": "insert_secret #4 incorrect (1,2,3 derived from incorrect)", + "inserts": [ + { + "index": 281474976710655, + "secret": "02a40c85b6f28da08dfdbe0926c53fab2de6" +\ + "d28c10301f8f7c4073d5e42e3148", + "successful": True + }, + { + "index": 281474976710654, + "secret": "dddc3a8d14fddf2b68fa8c7fbad274827493" +\ + "7479dd0f8930d5ebb4ab6bd866a3", + "successful": True + }, + { + "index": 281474976710653, + "secret": "c51a18b13e8527e579ec56365482c62f18" +\ + "0b7d5760b46e9477dae59e87ed423a", + "successful": True + }, + { + "index": 281474976710652, + "secret": "ba65d7b0ef55a3ba300d4e87af29868f39" +\ + "4f8f138d78a7011669c79b37b936f4", + "successful": True + }, + { + "index": 281474976710651, + "secret": "c65716add7aa98ba7acb236352d665cab1" +\ + "7345fe45b55fb879ff80e6bd0c41dd", + "successful": True + }, + { + "index": 281474976710650, + "secret": "969660042a28f32d9be17344e09374b379" +\ + "962d03db1574df5a8a5a47e19ce3f2", + "successful": True + }, + { + "index": 281474976710649, + "secret": "a5a64476122ca0925fb344bdc1854c1c0a" +\ + "59fc614298e50a33e331980a220f32", + "successful": True + }, + { + "index": 281474976710649, + "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ + "31447732e3802e1f7ac44b650e17", + "successful": False + } + ] + }, + { + "name": "insert_secret #5 incorrect", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ + "8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ + "e9b506fd4998a51d54502e99116", + "successful": True + }, + { + "index": 281474976710651, + "secret": "631373ad5f9ef654bb3dade742d09504c567" +\ + "edd24320d2fcd68e3cc47e2ff6a6", + "successful": True + }, + { + "index": 281474976710650, + "secret": "969660042a28f32d9be17344e09374b37996" +\ + "2d03db1574df5a8a5a47e19ce3f2", + "successful": False + } + ] + }, + { + "name": "insert_secret #6 incorrect (5 derived from incorrect)", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ + "8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ + "e9b506fd4998a51d54502e99116", + "successful": True + }, + { + "index": 281474976710651, + "secret": "631373ad5f9ef654bb3dade742d09504c567" +\ + "edd24320d2fcd68e3cc47e2ff6a6", + "successful": True + }, + { + "index": 281474976710650, + "secret": "b7e76a83668bde38b373970155c868a65330" +\ + "4308f9896692f904a23731224bb1", + "successful": True + }, + { + "index": 281474976710649, + "secret": "a5a64476122ca0925fb344bdc1854c1c0a59f" +\ + "c614298e50a33e331980a220f32", + "successful": True + }, + { + "index": 281474976710648, + "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ + "31447732e3802e1f7ac44b650e17", + "successful": False + } + ] + }, + { + "name": "insert_secret #7 incorrect", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ + "8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ + "e9b506fd4998a51d54502e99116", + "successful": True + }, + { + "index": 281474976710651, + "secret": "c65716add7aa98ba7acb236352d665cab173" +\ + "45fe45b55fb879ff80e6bd0c41dd", + "successful": True + }, + { + "index": 281474976710650, + "secret": "969660042a28f32d9be17344e09374b37996" +\ + "2d03db1574df5a8a5a47e19ce3f2", + "successful": True + }, + { + "index": 281474976710649, + "secret": "e7971de736e01da8ed58b94c2fc216cb1d" +\ + "ca9e326f3a96e7194fe8ea8af6c0a3", + "successful": True + }, + { + "index": 281474976710648, + "secret": "05cde6323d949933f7f7b78776bcc1ea6d" +\ + "9b31447732e3802e1f7ac44b650e17", + "successful": False + } + ] + }, + { + "name": "insert_secret #8 incorrect", + "inserts": [ + { + "index": 281474976710655, + "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ + "e6e8db3be6854c475621e007a5dc", + "successful": True + }, + { + "index": 281474976710654, + "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ + "8b4b5bec0c4b872abeba4cb8964", + "successful": True + }, + { + "index": 281474976710653, + "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ + "cbf9cd7c043a7d6456b7fc275ad8", + "successful": True + }, + { + "index": 281474976710652, + "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ + "e9b506fd4998a51d54502e99116", + "successful": True + }, + { + "index": 281474976710651, + "secret": "c65716add7aa98ba7acb236352d665cab173" +\ + "45fe45b55fb879ff80e6bd0c41dd", + "successful": True + }, + { + "index": 281474976710650, + "secret": "969660042a28f32d9be17344e09374b37996" +\ + "2d03db1574df5a8a5a47e19ce3f2", + "successful": True + }, + { + "index": 281474976710649, + "secret": "a5a64476122ca0925fb344bdc1854c1c0a" +\ + "59fc614298e50a33e331980a220f32", + "successful": True + }, + { + "index": 281474976710648, + "secret": "a7efbc61aac46d34f77778bac22c8a20c6" +\ + "a46ca460addc49009bda875ec88fa4", + "successful": False + } + ] + } + ] + + for test in tests: + receiver = RevocationStore() + for insert in test["inserts"]: + secret = bytes.fromhex(insert["secret"]) + + try: + receiver.add_next_entry(secret) + except Exception as e: + if insert["successful"]: + raise Exception("Failed ({}): error was received but it shouldn't: {}".format(test["name"], e)) + else: + if not insert["successful"]: + raise Exception("Failed ({}): error wasn't received".format(test["name"])) + + for insert in test["inserts"]: + secret = bytes.fromhex(insert["secret"]) + index = insert["index"] + if insert["successful"]: + self.assertEqual(secret, receiver.retrieve_secret(index)) + + print("Passed ({})".format(test["name"])) + + def test_shachain_produce_consume(self): + seed = bitcoin.sha256(b"shachaintest") + consumer = RevocationStore() + for i in range(10000): + secret = get_per_commitment_secret_from_seed(seed, 2**48 - i - 1) + try: + consumer.add_next_entry(secret) + 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 = secret_to_pubkey(per_commitment_secret) + + remote_htlcpubkey = remotepubkey + local_htlcpubkey = localpubkey + + htlc_cltv_timeout = {} + htlc_payment_preimage = {} + htlc = {} + + htlc_cltv_timeout[2] = 502 + htlc_payment_preimage[2] = b"\x02" * 32 + htlc[2] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[2])) + + htlc_cltv_timeout[3] = 503 + htlc_payment_preimage[3] = b"\x03" * 32 + htlc[3] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[3])) + + htlc_cltv_timeout[0] = 500 + htlc_payment_preimage[0] = b"\x00" * 32 + htlc[0] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[0]), htlc_cltv_timeout[0]) + + htlc_cltv_timeout[1] = 501 + htlc_payment_preimage[1] = b"\x01" * 32 + htlc[1] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[1]), htlc_cltv_timeout[1]) + + htlc_cltv_timeout[4] = 504 + htlc_payment_preimage[4] = b"\x04" * 32 + htlc[4] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[4]), htlc_cltv_timeout[4]) + + remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" + output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + + htlc_msat = {} + htlc_msat[0] = 1000 * 1000 + htlc_msat[2] = 2000 * 1000 + htlc_msat[1] = 2000 * 1000 + htlc_msat[3] = 3000 * 1000 + htlc_msat[4] = 4000 * 1000 + htlcs = [(htlc[x], htlc_msat[x]) for x in range(5)] + + 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) + + signature_for_output_remote_htlc = {} + signature_for_output_remote_htlc[0] = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6" + signature_for_output_remote_htlc[2] = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b" + signature_for_output_remote_htlc[1] = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202" + signature_for_output_remote_htlc[3] = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554" + signature_for_output_remote_htlc[4] = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d" + + output_htlc_tx = {} + SUCCESS = True + TIMEOUT = False + output_htlc_tx[0] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000") + + output_htlc_tx[2] = (TIMEOUT, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000") + + output_htlc_tx[1] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000") + + output_htlc_tx[3] = (TIMEOUT, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000") + + output_htlc_tx[4] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000") + + htlc_output_index = {0: 0, 1: 2, 2: 1, 3: 3, 4: 4} + + for i in range(5): + self.assertEqual(output_htlc_tx[i][1], self.htlc_tx(htlc[i], htlc_output_index[i], + htlc_msat[i], + htlc_payment_preimage[i], + signature_for_output_remote_htlc[i], + output_htlc_tx[i][0], htlc_cltv_timeout[i] if not output_htlc_tx[i][0] else 0, + local_feerate_per_kw, + our_commit_tx)) + + def htlc_tx(self, htlc, htlc_output_index, amount_msat, htlc_payment_preimage, remote_htlc_sig, success, cltv_timeout, local_feerate_per_kw, our_commit_tx): + 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]) + + our_htlc_tx_witness = make_htlc_tx_witness( + 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) + return str(our_htlc_tx) + + def test_commitment_tx_with_one_output(self): + to_local_msat= 6988000000 + to_remote_msat= 3000000000 + local_feerate_per_kw= 9651181 + remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" + output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + + 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) + + self.assertEqual(str(our_commit_tx), output_commit_tx) + + def test_commitment_tx_with_fee_greater_than_funder_amount(self): + to_local_msat= 6988000000 + to_remote_msat= 3000000000 + local_feerate_per_kw= 9651936 + remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" + output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + + 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) + + self.assertEqual(str(our_commit_tx), output_commit_tx) + + def test_extract_commitment_number_from_tx(self): + raw_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" + tx = Transaction(raw_tx) + self.assertEqual(commitment_number, extract_ctn_from_tx(tx, 0, local_payment_basepoint, remote_payment_basepoint)) + + 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 diff --git a/gui/kivy/uix/dialogs/lightning_channels.py b/gui/kivy/uix/dialogs/lightning_channels.py @@ -1,123 +0,0 @@ -import binascii -from kivy.lang import Builder -from kivy.factory import Factory -from kivy.uix.popup import Popup -from kivy.clock import Clock -from electrum_gui.kivy.uix.context_menu import ContextMenu - -Builder.load_string(''' -<LightningChannelItem@CardItem> - details: {} - active: False - channelId: '<channelId not set>' - Label: - text: root.channelId - -<LightningChannelsDialog@Popup>: - name: 'lightning_channels' - BoxLayout: - id: box - orientation: 'vertical' - spacing: '1dp' - ScrollView: - GridLayout: - cols: 1 - id: lightning_channels_container - size_hint: 1, None - height: self.minimum_height - spacing: '2dp' - padding: '12dp' - -<ChannelDetailsItem@BoxLayout>: - canvas.before: - Color: - rgba: 0.5, 0.5, 0.5, 1 - Rectangle: - size: self.size - pos: self.pos - value: '' - Label: - text: root.value - text_size: self.size # this makes the text not overflow, but wrap - -<ChannelDetailsRow@BoxLayout>: - keyName: '' - value: '' - ChannelDetailsItem: - value: root.keyName - size_hint_x: 0.5 # this makes the column narrower - - # see https://blog.kivy.org/2014/07/wrapping-text-in-kivys-label/ - ScrollView: - Label: - text: root.value - size_hint_y: None - text_size: self.width, None - height: self.texture_size[1] - -<ChannelDetailsList@RecycleView>: - scroll_type: ['bars', 'content'] - scroll_wheel_distance: dp(114) - bar_width: dp(10) - viewclass: 'ChannelDetailsRow' - RecycleBoxLayout: - default_size: None, dp(56) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: 'vertical' - spacing: dp(2) - -<ChannelDetailsPopup@Popup>: - id: popuproot - data: [] - ChannelDetailsList: - data: popuproot.data -''') - -class ChannelDetailsPopup(Popup): - def __init__(self, data, **kwargs): - super(ChanenlDetailsPopup,self).__init__(**kwargs) - self.data = data - -class LightningChannelsDialog(Factory.Popup): - def __init__(self, app): - super(LightningChannelsDialog, self).__init__() - self.clocks = [] - self.app = app - self.context_menu = None - self.app.wallet.lnworker.subscribe_channel_list_updates_from_other_thread(self.rpc_result_handler) - - def show_channel_details(self, obj): - p = Factory.ChannelDetailsPopup() - p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()] - p.open() - - def close_channel(self, obj): - print("UNIMPLEMENTED asked to close channel", obj.channelId) # TODO - - def show_menu(self, obj): - self.hide_menu() - self.context_menu = ContextMenu(obj, [("Close", self.close_channel), - ("Details", self.show_channel_details)]) - self.ids.box.add_widget(self.context_menu) - - def hide_menu(self): - if self.context_menu is not None: - self.ids.box.remove_widget(self.context_menu) - self.context_menu = None - - def rpc_result_handler(self, res): - channel_cards = self.ids.lightning_channels_container - channel_cards.clear_widgets() - if "channels" in res: - for i in res["channels"]: - item = Factory.LightningChannelItem() - item.screen = self - print(i) - item.channelId = i["chan_id"] - item.active = i["active"] - item.details = i - channel_cards.add_widget(item) - else: - self.app.show_info(res) diff --git a/gui/kivy/uix/dialogs/lightning_payer.py b/gui/kivy/uix/dialogs/lightning_payer.py @@ -1,93 +0,0 @@ -import binascii -from kivy.lang import Builder -from kivy.factory import Factory -from electrum_gui.kivy.i18n import _ -from kivy.clock import mainthread -from electrum.lnaddr import lndecode - -Builder.load_string(''' -<LightningPayerDialog@Popup> - id: s - name: 'lightning_payer' - invoice_data: '' - BoxLayout: - orientation: "vertical" - BlueButton: - text: s.invoice_data if s.invoice_data else _('Lightning invoice') - shorten: True - on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the lightning invoice using the Paste button, or use the camera to scan a QR code.'))) - GridLayout: - cols: 4 - size_hint: 1, None - height: '48dp' - IconButton: - id: qr - on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=s.on_lightning_qr)) - icon: 'atlas://gui/kivy/theming/light/camera' - Button: - text: _('Paste') - on_release: s.do_paste() - Button: - text: _('Paste using xclip') - on_release: s.do_paste_xclip() - Button: - text: _('Clear') - on_release: s.do_clear() - Button: - size_hint: 1, None - height: '48dp' - text: _('Open channel to pubkey in invoice') - on_release: s.do_open_channel() - Button: - size_hint: 1, None - height: '48dp' - text: _('Pay pasted/scanned invoice') - on_release: s.do_pay() -''') - -class LightningPayerDialog(Factory.Popup): - def __init__(self, app): - super(LightningPayerDialog, self).__init__() - self.app = app - - #def open(self, *args, **kwargs): - # super(LightningPayerDialog, self).open(*args, **kwargs) - #def dismiss(self, *args, **kwargs): - # super(LightningPayerDialog, self).dismiss(*args, **kwargs) - - def do_paste_xclip(self): - import subprocess - proc = subprocess.run(["xclip","-sel","clipboard","-o"], stdout=subprocess.PIPE) - self.invoice_data = proc.stdout.decode("ascii") - - def do_paste(self): - contents = self.app._clipboard.paste() - if not contents: - self.app.show_info(_("Clipboard is empty")) - return - self.invoice_data = contents - - def do_clear(self): - self.invoice_data = "" - - def do_open_channel(self): - compressed_pubkey_bytes = lndecode(self.invoice_data).pubkey.serialize() - hexpubkey = binascii.hexlify(compressed_pubkey_bytes).decode("ascii") - local_amt = 200000 - push_amt = 100000 - - def on_success(pw): - # node_id, local_amt, push_amt, emit_function, get_password - self.app.wallet.lnworker.open_channel_from_other_thread(hexpubkey, local_amt, push_amt, mainthread(lambda parent: self.app.show_info(_("Channel open, waiting for locking..."))), lambda: pw) - - if self.app.wallet.has_keystore_encryption(): - # wallet, msg, on_success (Tuple[str, str] -> ()), on_failure (() -> ()) - self.app.password_dialog(self.app.wallet, _("Password needed for opening channel"), on_success, lambda: self.app.show_error(_("Failed getting password from you"))) - else: - on_success("") - - def do_pay(self): - self.app.wallet.lnworker.pay_invoice_from_other_thread(self.invoice_data) - - def on_lightning_qr(self, data): - self.invoice_data = str(data) diff --git a/lib/tests/test_bolt11.py b/lib/tests/test_bolt11.py @@ -1,97 +0,0 @@ -from hashlib import sha256 -from lib.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5 -from decimal import Decimal -from binascii import unhexlify, hexlify -from lib.segwit_addr import bech32_encode, bech32_decode -import pprint -import unittest - -RHASH=unhexlify('0001020304050607080900010203040506070809000102030405060708090102') -CONVERSION_RATE=1200 -PRIVKEY=unhexlify('e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734') -PUBKEY=unhexlify('03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad') - -class TestBolt11(unittest.TestCase): - def test_shorten_amount(self): - tests = { - Decimal(10)/10**12: '10p', - Decimal(1000)/10**12: '1n', - Decimal(1200)/10**12: '1200p', - Decimal(123)/10**6: '123u', - Decimal(123)/1000: '123m', - Decimal(3): '3', - } - - for i, o in tests.items(): - assert shorten_amount(i) == o - assert unshorten_amount(shorten_amount(i)) == i - - @staticmethod - def compare(a, b): - - if len([t[1] for t in a.tags if t[0] == 'h']) == 1: - h1 = sha256([t[1] for t in a.tags if t[0] == 'h'][0].encode('utf-8')).digest() - h2 = [t[1] for t in b.tags if t[0] == 'h'][0] - assert h1 == h2 - - # Need to filter out these, since they are being modified during - # encoding, i.e., hashed - a.tags = [t for t in a.tags if t[0] != 'h' and t[0] != 'n'] - b.tags = [t for t in b.tags if t[0] != 'h' and t[0] != 'n'] - - assert b.pubkey.serialize() == PUBKEY, (hexlify(b.pubkey.serialize()), hexlify(PUBKEY)) - assert b.signature != None - - # Unset these, they are generated during encoding/decoding - b.pubkey = None - b.signature = None - - assert a.__dict__ == b.__dict__, (pprint.pformat([a.__dict__, b.__dict__])) - - def test_roundtrip(self): - longdescription = ('One piece of chocolate cake, one icecream cone, one' - ' pickle, one slice of swiss cheese, one slice of salami,' - ' one lollypop, one piece of cherry pie, one sausage, one' - ' cupcake, and one slice of watermelon') - - - tests = [ - LnAddr(RHASH, tags=[('d', '')]), - LnAddr(RHASH, amount=Decimal('0.001'), - tags=[('d', '1 cup coffee'), ('x', 60)]), - LnAddr(RHASH, amount=Decimal('1'), tags=[('h', longdescription)]), - LnAddr(RHASH, currency='tb', tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]), - LnAddr(RHASH, amount=24, tags=[ - ('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', '1RustyRX2oai4EYYDpQGWvEL62BBGqN9T'), ('h', longdescription)]), - LnAddr(RHASH, amount=24, tags=[('f', '3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX'), ('h', longdescription)]), - LnAddr(RHASH, amount=24, tags=[('f', 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), ('h', longdescription)]), - LnAddr(RHASH, amount=24, tags=[('f', 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'), ('h', longdescription)]), - LnAddr(RHASH, amount=24, tags=[('n', PUBKEY), ('h', longdescription)]), - ] - - # Roundtrip - for t in tests: - o = lndecode(lnencode(t, PRIVKEY), False, t.currency) - self.compare(t, o) - - def test_n_decoding(self): - # We flip the signature recovery bit, which would normally give a different - # pubkey. - hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24, tags=[('d', '')]), PRIVKEY), True) - databits = u5_to_bitarray(data) - databits.invert(-1) - lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True) - assert lnaddr.pubkey.serialize() != PUBKEY - - # But not if we supply expliciy `n` specifier! - hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24, - tags=[('d', ''), - ('n', PUBKEY)]), - PRIVKEY), True) - databits = u5_to_bitarray(data) - databits.invert(-1) - lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True) - assert lnaddr.pubkey.serialize() == PUBKEY - - def test_min_final_cltv_expiry(self): - self.assertEquals(lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe", expected_hrp="sb").min_final_cltv_expiry, 144) diff --git a/lib/tests/test_lnhtlc.py b/lib/tests/test_lnhtlc.py @@ -1,344 +0,0 @@ -# ported from lnd 42de4400bff5105352d0552155f73589166d162b - -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 - -def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv): - assert local_amount > 0 - assert remote_amount > 0 - channel_id, _ = lnbase.channel_id_from_funding_tx(funding_txid, funding_index) - their_revocation_store = lnbase.RevocationStore() - local_config=lnbase.ChannelConfig( - payment_basepoint=privkeys[0], - multisig_key=privkeys[1], - htlc_basepoint=privkeys[2], - delayed_basepoint=privkeys[3], - revocation_basepoint=privkeys[4], - to_self_delay=l_csv, - dust_limit_sat=l_dust, - max_htlc_value_in_flight_msat=500000 * 1000, - max_accepted_htlcs=5 - ) - remote_config=lnbase.ChannelConfig( - payment_basepoint=other_pubkeys[0], - multisig_key=other_pubkeys[1], - htlc_basepoint=other_pubkeys[2], - delayed_basepoint=other_pubkeys[3], - revocation_basepoint=other_pubkeys[4], - to_self_delay=r_csv, - dust_limit_sat=r_dust, - max_htlc_value_in_flight_msat=500000 * 1000, - max_accepted_htlcs=5 - ) - - 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, - amount_msat=remote_amount, - revocation_store=their_revocation_store, - next_htlc_id = 0, - feerate=local_feerate - ), - "local_state":lnbase.LocalState( - ctn = 0, - per_commitment_secret_seed=seed, - amount_msat=local_amount, - next_htlc_id = 0, - funding_locked_received=True, - was_announced=False, - current_commitment_signature=None, - feerate=local_feerate - ), - "constraints":lnbase.ChannelConstraints(capacity=funding_sat, 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') - xprv, xpub = bitcoin.bip32_private_derivation(xprv, "m/", sequence) - xtype, depth, fingerprint, child_number, c, k = bitcoin.deserialize_xprv(xprv) - assert len(k) == 32 - assert type(k) is bytes - return k - -def create_test_channels(): - funding_txid = binascii.hexlify(os.urandom(32)).decode("ascii") - funding_index = 0 - funding_sat = bitcoin.COIN * 10 - local_amount = (funding_sat * 1000) // 2 - remote_amount = (funding_sat * 1000) // 2 - alice_raw = [ bip32("m/" + str(i)) for i in range(5) ] - bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ] - alice_privkeys = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in alice_raw] - bob_privkeys = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in bob_raw] - alice_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys] - bob_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys] - - alice_seed = os.urandom(32) - bob_seed = os.urandom(32) - - 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( - create_channel_state(funding_txid, funding_index, funding_sat, 6000, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \ - lnhtlc.HTLCStateMachine( - create_channel_state(funding_txid, funding_index, funding_sat, 6000, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob") - -one_bitcoin_in_msat = bitcoin.COIN * 1000 - -class TestLNBaseHTLCStateMachine(unittest.TestCase): - def assertOutputExistsByValue(self, tx, amt_sat): - for typ, scr, val in tx.outputs(): - if val == amt_sat: - break - else: - self.assertFalse() - - def setUp(self): - # Create a test channel which will be used for the duration of this - # unittest. The channel will be funded evenly with Alice having 5 BTC, - # and Bob having 5 BTC. - self.alice_channel, self.bob_channel = create_test_channels() - - self.paymentPreimage = b"\x01" * 32 - paymentHash = bitcoin.sha256(self.paymentPreimage) - self.htlc = lnhtlc.UpdateAddHtlc( - payment_hash = paymentHash, - amount_msat = one_bitcoin_in_msat, - cltv_expiry = 5, - total_fee = 0 - ) - - # First Alice adds the outgoing HTLC to her local channel's state - # update log. Then Alice sends this wire message over to Bob who adds - # this htlc to his remote state update log. - self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc) - - self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc) - - def test_SimpleAddSettleWorkflow(self): - alice_channel, bob_channel = self.alice_channel, self.bob_channel - htlc = self.htlc - - # Next alice commits this change by sending a signature message. Since - # we expect the messages to be ordered, Bob will receive the HTLC we - # just sent before he receives this signature, so the signature will - # cover the HTLC. - aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment() - - self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature") - - # Bob receives this signature message, and checks that this covers the - # state he has in his remote log. This includes the HTLC just sent - # from Alice. - bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs) - - # Bob revokes his prior commitment given to him by Alice, since he now - # has a valid signature for a newer commitment. - bobRevocation, _ = bob_channel.revoke_current_commitment() - - # Bob finally send a signature for Alice's commitment transaction. - # This signature will cover the HTLC, since Bob will first send the - # revocation just created. The revocation also acks every received - # HTLC up to the point where Alice sent here signature. - bobSig, bobHtlcSigs = bob_channel.sign_next_commitment() - - # Alice then processes this revocation, sending her own revocation for - # her prior commitment transaction. Alice shouldn't have any HTLCs to - # forward since she's sending an outgoing HTLC. - alice_channel.receive_revocation(bobRevocation) - - # Alice then processes bob's signature, and since she just received - # the revocation, she expect this signature to cover everything up to - # the point where she sent her signature, including the HTLC. - alice_channel.receive_new_commitment(bobSig, bobHtlcSigs) - - # Alice then generates a revocation for bob. - aliceRevocation, _ = alice_channel.revoke_current_commitment() - - # Finally Bob processes Alice's revocation, at this point the new HTLC - # is fully locked in within both commitment transactions. Bob should - # also be able to forward an HTLC now that the HTLC has been locked - # into both commitment transactions. - bob_channel.receive_revocation(aliceRevocation) - - # At this point, both sides should have the proper number of satoshis - # sent, and commitment height updated within their local channel - # state. - aliceSent = 0 - bobSent = 0 - - self.assertEqual(alice_channel.total_msat_sent, aliceSent, "alice has incorrect milli-satoshis sent") - 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.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. - self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs())) - self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs())) - self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000) - self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000) - - # Now we'll repeat a similar exchange, this time with Bob settling the - # HTLC once he learns of the preimage. - preimage = self.paymentPreimage - bob_channel.settle_htlc(preimage, self.bobHtlcIndex) - - alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex) - - bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment() - alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2) - - aliceRevocation2, _ = alice_channel.revoke_current_commitment() - aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment() - self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures") - - bob_channel.receive_revocation(aliceRevocation2) - - bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2) - - bobRevocation2, _ = bob_channel.revoke_current_commitment() - alice_channel.receive_revocation(bobRevocation2) - - # At this point, Bob should have 6 BTC settled, with Alice still having - # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel - # should show 1 BTC received. They should also be at commitment height - # two, with the revocation window extended by 1 (5). - mSatTransferred = one_bitcoin_in_msat - self.assertEqual(alice_channel.total_msat_sent, mSatTransferred, "alice satoshis sent incorrect %s vs %s expected"% (alice_channel.total_msat_sent, mSatTransferred)) - self.assertEqual(alice_channel.total_msat_received, 0, "alice satoshis received incorrect %s vs %s expected"% (alice_channel.total_msat_received, 0)) - self.assertEqual(bob_channel.total_msat_received, mSatTransferred, "bob satoshis received incorrect %s vs %s expected"% (bob_channel.total_msat_received, mSatTransferred)) - self.assertEqual(bob_channel.total_msat_sent, 0, "bob satoshis sent incorrect %s vs %s expected"% (bob_channel.total_msat_sent, 0)) - self.assertEqual(bob_channel.l_current_height, 2, "bob has incorrect commitment height, %s vs %s"% (bob_channel.l_current_height, 2)) - self.assertEqual(alice_channel.l_current_height, 2, "alice has incorrect commitment height, %s vs %s"% (alice_channel.l_current_height, 2)) - - # The logs of both sides should now be cleared since the entry adding - # the HTLC should have been removed once both sides receive the - # revocation. - self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log)) - self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log)) - - def alice_to_bob_fee_update(self): - fee = 111 - self.alice_channel.update_fee(fee) - self.bob_channel.receive_update_fee(fee) - return fee - - def test_UpdateFeeSenderCommits(self): - fee = self.alice_to_bob_fee_update() - - alice_channel, bob_channel = self.alice_channel, self.bob_channel - - alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() - bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) - - self.assertNotEqual(fee, bob_channel.local_state.feerate) - rev, _ = bob_channel.revoke_current_commitment() - self.assertEqual(fee, bob_channel.local_state.feerate) - - bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() - alice_channel.receive_revocation(rev) - alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) - - self.assertNotEqual(fee, alice_channel.local_state.feerate) - rev, _ = alice_channel.revoke_current_commitment() - self.assertEqual(fee, alice_channel.local_state.feerate) - - bob_channel.receive_revocation(rev) - - - def test_UpdateFeeReceiverCommits(self): - fee = self.alice_to_bob_fee_update() - - alice_channel, bob_channel = self.alice_channel, self.bob_channel - - bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() - alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) - - alice_revocation, _ = alice_channel.revoke_current_commitment() - bob_channel.receive_revocation(alice_revocation) - alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() - bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) - - self.assertNotEqual(fee, bob_channel.local_state.feerate) - bob_revocation, _ = bob_channel.revoke_current_commitment() - self.assertEqual(fee, bob_channel.local_state.feerate) - - bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() - alice_channel.receive_revocation(bob_revocation) - alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) - - self.assertNotEqual(fee, alice_channel.local_state.feerate) - alice_revocation, _ = alice_channel.revoke_current_commitment() - self.assertEqual(fee, alice_channel.local_state.feerate) - - bob_channel.receive_revocation(alice_revocation) - - - -class TestLNHTLCDust(unittest.TestCase): - def test_HTLCDustLimit(self): - alice_channel, bob_channel = create_test_channels() - - paymentPreimage = b"\x01" * 32 - paymentHash = bitcoin.sha256(paymentPreimage) - fee_per_kw = alice_channel.local_state.feerate - self.assertEqual(fee_per_kw, 6000) - htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) - self.assertEqual(htlcAmt, 4478) - htlc = lnhtlc.UpdateAddHtlc( - payment_hash = paymentHash, - amount_msat = 1000 * htlcAmt, - cltv_expiry = 5, # also in create_test_channels - total_fee = 0 - ) - - 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 + htlcAmt) - 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 force_state_transition(chanA, chanB): - chanB.receive_new_commitment(*chanA.sign_next_commitment()) - rev, _ = chanB.revoke_current_commitment() - bob_sig, bob_htlc_sigs = chanB.sign_next_commitment() - chanA.receive_revocation(rev) - chanA.receive_new_commitment(bob_sig, bob_htlc_sigs) - chanB.receive_revocation(chanA.revoke_current_commitment()[0]) - -# calcStaticFee calculates appropriate fees for commitment transactions. This -# function provides a simple way to allow test balance assertions to take fee -# calculations into account. -def calc_static_fee(numHTLCs): - commitWeight = 724 - htlcWeight = 172 - feePerKw = 24//4 * 1000 - return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000 diff --git a/lib/tests/test_lnrouter.py b/lib/tests/test_lnrouter.py @@ -1,151 +0,0 @@ -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() - trigger_callback = lambda x: None - 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_lnutil.py b/lib/tests/test_lnutil.py @@ -1,677 +0,0 @@ -import unittest -import json -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, extract_ctn_from_tx, UnableToDeriveSecret) -from lib.util import bh2u, bfh -from lib.transaction import Transaction - -funding_tx_id = '8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be' -funding_output_index = 0 -funding_amount_satoshi = 10000000 -commitment_number = 42 -local_delay = 144 -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) -local_funding_privkey = bytes.fromhex('30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f374901') -local_funding_pubkey = bytes.fromhex('023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb') -remote_funding_pubkey = bytes.fromhex('030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1') -local_privkey = bytes.fromhex('bb13b121cdc357cd2e608b0aea294afca36e2b34cf958e2e6451a2f27469449101') -localpubkey = bytes.fromhex('030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e7') -remotepubkey = bytes.fromhex('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b') -local_delayedpubkey = bytes.fromhex('03fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c') -local_revocation_pubkey = bytes.fromhex('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19') -# funding wscript = 5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae - -class TestLNUtil(unittest.TestCase): - def test_shachain_store(self): - tests = [ - { - "name": "insert_secret correct sequence", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1" +\ - "a8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab2" +\ - "1e9b506fd4998a51d54502e99116", - "successful": True - }, - { - "index": 281474976710651, - "secret": "c65716add7aa98ba7acb236352d665cab173" +\ - "45fe45b55fb879ff80e6bd0c41dd", - "successful": True - }, - { - "index": 281474976710650, - "secret": "969660042a28f32d9be17344e09374b37996" +\ - "2d03db1574df5a8a5a47e19ce3f2", - "successful": True - }, - { - "index": 281474976710649, - "secret": "a5a64476122ca0925fb344bdc1854c1c0a59" +\ - "fc614298e50a33e331980a220f32", - "successful": True - }, - { - "index": 281474976710648, - "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ - "31447732e3802e1f7ac44b650e17", - "successful": True - } - ] - }, - { - "name": "insert_secret #1 incorrect", - "inserts": [ - { - "index": 281474976710655, - "secret": "02a40c85b6f28da08dfdbe0926c53fab2d" +\ - "e6d28c10301f8f7c4073d5e42e3148", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659" +\ - "c1a8b4b5bec0c4b872abeba4cb8964", - "successful": False - } - ] - }, - { - "name": "insert_secret #2 incorrect (#1 derived from incorrect)", - "inserts": [ - { - "index": 281474976710655, - "secret": "02a40c85b6f28da08dfdbe0926c53fab2de6" +\ - "d28c10301f8f7c4073d5e42e3148", - "successful": True - }, - { - "index": 281474976710654, - "secret": "dddc3a8d14fddf2b68fa8c7fbad274827493" +\ - "7479dd0f8930d5ebb4ab6bd866a3", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22a" +\ - "b21e9b506fd4998a51d54502e99116", - "successful": False - } - ] - }, - { - "name": "insert_secret #3 incorrect", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1" +\ - "a8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "c51a18b13e8527e579ec56365482c62f180b" +\ - "7d5760b46e9477dae59e87ed423a", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab2" +\ - "1e9b506fd4998a51d54502e99116", - "successful": False - } - ] - }, - { - "name": "insert_secret #4 incorrect (1,2,3 derived from incorrect)", - "inserts": [ - { - "index": 281474976710655, - "secret": "02a40c85b6f28da08dfdbe0926c53fab2de6" +\ - "d28c10301f8f7c4073d5e42e3148", - "successful": True - }, - { - "index": 281474976710654, - "secret": "dddc3a8d14fddf2b68fa8c7fbad274827493" +\ - "7479dd0f8930d5ebb4ab6bd866a3", - "successful": True - }, - { - "index": 281474976710653, - "secret": "c51a18b13e8527e579ec56365482c62f18" +\ - "0b7d5760b46e9477dae59e87ed423a", - "successful": True - }, - { - "index": 281474976710652, - "secret": "ba65d7b0ef55a3ba300d4e87af29868f39" +\ - "4f8f138d78a7011669c79b37b936f4", - "successful": True - }, - { - "index": 281474976710651, - "secret": "c65716add7aa98ba7acb236352d665cab1" +\ - "7345fe45b55fb879ff80e6bd0c41dd", - "successful": True - }, - { - "index": 281474976710650, - "secret": "969660042a28f32d9be17344e09374b379" +\ - "962d03db1574df5a8a5a47e19ce3f2", - "successful": True - }, - { - "index": 281474976710649, - "secret": "a5a64476122ca0925fb344bdc1854c1c0a" +\ - "59fc614298e50a33e331980a220f32", - "successful": True - }, - { - "index": 281474976710649, - "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ - "31447732e3802e1f7ac44b650e17", - "successful": False - } - ] - }, - { - "name": "insert_secret #5 incorrect", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ - "8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ - "e9b506fd4998a51d54502e99116", - "successful": True - }, - { - "index": 281474976710651, - "secret": "631373ad5f9ef654bb3dade742d09504c567" +\ - "edd24320d2fcd68e3cc47e2ff6a6", - "successful": True - }, - { - "index": 281474976710650, - "secret": "969660042a28f32d9be17344e09374b37996" +\ - "2d03db1574df5a8a5a47e19ce3f2", - "successful": False - } - ] - }, - { - "name": "insert_secret #6 incorrect (5 derived from incorrect)", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ - "8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ - "e9b506fd4998a51d54502e99116", - "successful": True - }, - { - "index": 281474976710651, - "secret": "631373ad5f9ef654bb3dade742d09504c567" +\ - "edd24320d2fcd68e3cc47e2ff6a6", - "successful": True - }, - { - "index": 281474976710650, - "secret": "b7e76a83668bde38b373970155c868a65330" +\ - "4308f9896692f904a23731224bb1", - "successful": True - }, - { - "index": 281474976710649, - "secret": "a5a64476122ca0925fb344bdc1854c1c0a59f" +\ - "c614298e50a33e331980a220f32", - "successful": True - }, - { - "index": 281474976710648, - "secret": "05cde6323d949933f7f7b78776bcc1ea6d9b" +\ - "31447732e3802e1f7ac44b650e17", - "successful": False - } - ] - }, - { - "name": "insert_secret #7 incorrect", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ - "8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ - "e9b506fd4998a51d54502e99116", - "successful": True - }, - { - "index": 281474976710651, - "secret": "c65716add7aa98ba7acb236352d665cab173" +\ - "45fe45b55fb879ff80e6bd0c41dd", - "successful": True - }, - { - "index": 281474976710650, - "secret": "969660042a28f32d9be17344e09374b37996" +\ - "2d03db1574df5a8a5a47e19ce3f2", - "successful": True - }, - { - "index": 281474976710649, - "secret": "e7971de736e01da8ed58b94c2fc216cb1d" +\ - "ca9e326f3a96e7194fe8ea8af6c0a3", - "successful": True - }, - { - "index": 281474976710648, - "secret": "05cde6323d949933f7f7b78776bcc1ea6d" +\ - "9b31447732e3802e1f7ac44b650e17", - "successful": False - } - ] - }, - { - "name": "insert_secret #8 incorrect", - "inserts": [ - { - "index": 281474976710655, - "secret": "7cc854b54e3e0dcdb010d7a3fee464a9687b" +\ - "e6e8db3be6854c475621e007a5dc", - "successful": True - }, - { - "index": 281474976710654, - "secret": "c7518c8ae4660ed02894df8976fa1a3659c1a" +\ - "8b4b5bec0c4b872abeba4cb8964", - "successful": True - }, - { - "index": 281474976710653, - "secret": "2273e227a5b7449b6e70f1fb4652864038b1" +\ - "cbf9cd7c043a7d6456b7fc275ad8", - "successful": True - }, - { - "index": 281474976710652, - "secret": "27cddaa5624534cb6cb9d7da077cf2b22ab21" +\ - "e9b506fd4998a51d54502e99116", - "successful": True - }, - { - "index": 281474976710651, - "secret": "c65716add7aa98ba7acb236352d665cab173" +\ - "45fe45b55fb879ff80e6bd0c41dd", - "successful": True - }, - { - "index": 281474976710650, - "secret": "969660042a28f32d9be17344e09374b37996" +\ - "2d03db1574df5a8a5a47e19ce3f2", - "successful": True - }, - { - "index": 281474976710649, - "secret": "a5a64476122ca0925fb344bdc1854c1c0a" +\ - "59fc614298e50a33e331980a220f32", - "successful": True - }, - { - "index": 281474976710648, - "secret": "a7efbc61aac46d34f77778bac22c8a20c6" +\ - "a46ca460addc49009bda875ec88fa4", - "successful": False - } - ] - } - ] - - for test in tests: - receiver = RevocationStore() - for insert in test["inserts"]: - secret = bytes.fromhex(insert["secret"]) - - try: - receiver.add_next_entry(secret) - except Exception as e: - if insert["successful"]: - raise Exception("Failed ({}): error was received but it shouldn't: {}".format(test["name"], e)) - else: - if not insert["successful"]: - raise Exception("Failed ({}): error wasn't received".format(test["name"])) - - for insert in test["inserts"]: - secret = bytes.fromhex(insert["secret"]) - index = insert["index"] - if insert["successful"]: - self.assertEqual(secret, receiver.retrieve_secret(index)) - - print("Passed ({})".format(test["name"])) - - def test_shachain_produce_consume(self): - seed = bitcoin.sha256(b"shachaintest") - consumer = RevocationStore() - for i in range(10000): - secret = get_per_commitment_secret_from_seed(seed, 2**48 - i - 1) - try: - consumer.add_next_entry(secret) - 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 = secret_to_pubkey(per_commitment_secret) - - remote_htlcpubkey = remotepubkey - local_htlcpubkey = localpubkey - - htlc_cltv_timeout = {} - htlc_payment_preimage = {} - htlc = {} - - htlc_cltv_timeout[2] = 502 - htlc_payment_preimage[2] = b"\x02" * 32 - htlc[2] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[2])) - - htlc_cltv_timeout[3] = 503 - htlc_payment_preimage[3] = b"\x03" * 32 - htlc[3] = make_offered_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[3])) - - htlc_cltv_timeout[0] = 500 - htlc_payment_preimage[0] = b"\x00" * 32 - htlc[0] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[0]), htlc_cltv_timeout[0]) - - htlc_cltv_timeout[1] = 501 - htlc_payment_preimage[1] = b"\x01" * 32 - htlc[1] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[1]), htlc_cltv_timeout[1]) - - htlc_cltv_timeout[4] = 504 - htlc_payment_preimage[4] = b"\x04" * 32 - htlc[4] = make_received_htlc(local_revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, bitcoin.sha256(htlc_payment_preimage[4]), htlc_cltv_timeout[4]) - - remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" - output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" - - htlc_msat = {} - htlc_msat[0] = 1000 * 1000 - htlc_msat[2] = 2000 * 1000 - htlc_msat[1] = 2000 * 1000 - htlc_msat[3] = 3000 * 1000 - htlc_msat[4] = 4000 * 1000 - htlcs = [(htlc[x], htlc_msat[x]) for x in range(5)] - - 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) - - signature_for_output_remote_htlc = {} - signature_for_output_remote_htlc[0] = "304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6" - signature_for_output_remote_htlc[2] = "3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b" - signature_for_output_remote_htlc[1] = "304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202" - signature_for_output_remote_htlc[3] = "3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554" - signature_for_output_remote_htlc[4] = "304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d" - - output_htlc_tx = {} - SUCCESS = True - TIMEOUT = False - output_htlc_tx[0] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000") - - output_htlc_tx[2] = (TIMEOUT, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000") - - output_htlc_tx[1] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000") - - output_htlc_tx[3] = (TIMEOUT, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000") - - output_htlc_tx[4] = (SUCCESS, "020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000") - - htlc_output_index = {0: 0, 1: 2, 2: 1, 3: 3, 4: 4} - - for i in range(5): - self.assertEqual(output_htlc_tx[i][1], self.htlc_tx(htlc[i], htlc_output_index[i], - htlc_msat[i], - htlc_payment_preimage[i], - signature_for_output_remote_htlc[i], - output_htlc_tx[i][0], htlc_cltv_timeout[i] if not output_htlc_tx[i][0] else 0, - local_feerate_per_kw, - our_commit_tx)) - - def htlc_tx(self, htlc, htlc_output_index, amount_msat, htlc_payment_preimage, remote_htlc_sig, success, cltv_timeout, local_feerate_per_kw, our_commit_tx): - 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]) - - our_htlc_tx_witness = make_htlc_tx_witness( - 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) - return str(our_htlc_tx) - - def test_commitment_tx_with_one_output(self): - to_local_msat= 6988000000 - to_remote_msat= 3000000000 - local_feerate_per_kw= 9651181 - remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" - output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" - - 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) - - self.assertEqual(str(our_commit_tx), output_commit_tx) - - def test_commitment_tx_with_fee_greater_than_funder_amount(self): - to_local_msat= 6988000000 - to_remote_msat= 3000000000 - local_feerate_per_kw= 9651936 - remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" - output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" - - 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) - - self.assertEqual(str(our_commit_tx), output_commit_tx) - - def test_extract_commitment_number_from_tx(self): - raw_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" - tx = Transaction(raw_tx) - self.assertEqual(commitment_number, extract_ctn_from_tx(tx, 0, local_payment_basepoint, remote_payment_basepoint)) - - 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