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:
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