def test_n_decoding(self): # We flip the signature recovery bit, which would normally give a different # pubkey. _, hrp, data = bech32_decode(lnencode( LnAddr(paymenthash=RHASH, amount=24, tags=[('d', '')]), PRIVKEY), ignore_long_length=True) databits = u5_to_bitarray(data) databits.invert(-1) lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(databits)), verbose=True) assert lnaddr.pubkey.serialize() != PUBKEY # But not if we supply expliciy `n` specifier! _, hrp, data = bech32_decode(lnencode( LnAddr(paymenthash=RHASH, amount=24, tags=[('d', ''), ('n', PUBKEY)]), PRIVKEY), ignore_long_length=True) databits = u5_to_bitarray(data) databits.invert(-1) lnaddr = lndecode(bech32_encode(segwit_addr.Encoding.BECH32, hrp, bitarray_to_u5(databits)), verbose=True) assert lnaddr.pubkey.serialize() == PUBKEY
def test_min_final_cltv_expiry_decoding(self): lnaddr = lndecode( "lnsmona25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeescqzyssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsj7dv5y27jkfl9y2vxu8jddmgmk4av25j80vwesnwejd089805a5z6gupm4cn23h0k6gaj8nnakm3jz4ujvcwkljgm7jr2zgu3s9uz2gqgdx8jp", expected_hrp="smona") self.assertEqual(144, lnaddr.get_min_final_cltv_expiry()) lnaddr = lndecode( "lntb15u1p0m6lzupp5zqjthgvaad9mewmdjuehwddyze9d8zyxcc43zhaddeegt37sndgsdq4xysyymr0vd4kzcmrd9hx7cqp7xqrrss9qy9qsqsp5vlhcs24hwm747w8f3uau2tlrdkvjaglffnsstwyamj84cxuhrn2s8tut3jqumepu42azyyjpgqa4w9w03204zp9h4clk499y2umstl6s29hqyj8vv4as6zt5567ux7l3f66m8pjhk65zjaq2esezk7ll2kcpljewkg", expected_hrp="tb") self.assertEqual(30, lnaddr.get_min_final_cltv_expiry())
def test_features(self): lnaddr = lndecode( "lnmona25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzszsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsw78e7nnjh75hssadjykhm85834l3q4juymunsryzewwhy43kaaeprmxnn4w8uvmpem60flrcpxr4sey558yrh2lwgdhv4z5a4lculqgqm5ng34" ) self.assertEqual(514, lnaddr.get_tag('9')) with self.assertRaises(UnknownEvenFeatureBits): lndecode( "lnmona25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qw7rhsay5tm4cuw8sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsq99ctuk92pu45k27shadaafncl0jea0m6r82mxl037s4ujv423az7ucn734zydafexf2eft3wufyyck73qz39acv9nxc50nxkvjhnpgph9l9yw" )
def test_close(self): alice_channel, bob_channel = create_test_channels() p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel) w1.network.config.set_key('dynamic_fees', False) w2.network.config.set_key('dynamic_fees', False) w1.network.config.set_key('fee_per_kb', 5000) w2.network.config.set_key('fee_per_kb', 1000) w2.enable_htlc_settle.clear() pay_req = self.prepare_invoice(w2) lnaddr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) async def pay(): await asyncio.wait_for(p1.initialized, 1) await asyncio.wait_for(p2.initialized, 1) # alice sends htlc route = await w1._create_route_from_invoice(decoded_invoice=lnaddr) htlc = p1.pay(route, alice_channel, int(lnaddr.amount * COIN * 1000), lnaddr.paymenthash, lnaddr.get_min_final_cltv_expiry()) # alice closes await p1.close_channel(alice_channel.channel_id) gath.cancel() async def set_settle(): await asyncio.sleep(0.1) w2.enable_htlc_settle.set() gath = asyncio.gather(pay(), set_settle(), p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch()) async def f(): await gath with self.assertRaises(concurrent.futures.CancelledError): run(f())
def test_min_final_cltv_expiry_roundtrip(self): for cltv in (1, 15, 16, 31, 32, 33, 150, 511, 512, 513, 1023, 1024, 1025): lnaddr = LnAddr(paymenthash=RHASH, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('c', cltv)]) invoice = lnencode(lnaddr, PRIVKEY) self.assertEqual(cltv, lndecode(invoice).get_min_final_cltv_expiry())
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_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='tmona', tags=[('f', 'mivTxWUqB6yxQdbLnAfcTSaVXouAhTUDfs'), ('h', longdescription)]), LnAddr( RHASH, amount=24, tags= [('r', [(unhexlify( '029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255' ), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify( '039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255' ), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', 'MRHx4jW2KAQeEDMuK7pGLUGWvPRQT1Epmj'), ('h', longdescription)]), LnAddr(RHASH, amount=24, tags=[('f', 'PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg'), ('h', longdescription)]), LnAddr(RHASH, amount=24, tags=[('f', 'mona1quunc907zfyj7cyxhnp9584rj0wmdka2ec9w3af'), ('h', longdescription)]), LnAddr( RHASH, amount=24, tags= [('f', 'mona1qp8f842ywwr9h5rdxyzggex7q3trvvvaarfssxccju52rj6htfzfsqr79j2' ), ('h', longdescription)]), LnAddr(RHASH, amount=24, tags=[('n', PUBKEY), ('h', longdescription)]), ] # Roundtrip for t in tests: o = lndecode(lnencode(t, PRIVKEY), expected_hrp=t.currency) self.compare(t, o)
def set_ln_invoice(self, invoice): try: lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) except Exception as e: self.app.show_info(invoice + _(" is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == '' return self.screen.address = invoice self.screen.message = dict(lnaddr.tags).get('d', None) self.screen.amount = self.app.format_amount_and_units(lnaddr.amount * bitcoin.COIN) if lnaddr.amount else '' self.payment_request = None self.screen.is_lightning = True
def set_ln_invoice(self, invoice: str): try: invoice = str(invoice).lower() lnaddr = lndecode(invoice) except LnInvoiceException as e: self.app.show_info( _("Invoice is not a valid Lightning invoice: ") + repr(e)) # repr because str(Exception()) == '' return self.address = invoice self.message = lnaddr.get_description() self.amount = self.app.format_amount_and_units( lnaddr.amount * bitcoin.COIN) if lnaddr.amount else '' self.payment_request = None self.is_lightning = True
def make_model(self, htlcs) -> QtGui.QStandardItemModel: model = QtGui.QStandardItemModel(0, 2) model.setHorizontalHeaderLabels(['HTLC', 'Property value']) parentItem = model.invisibleRootItem() folder_types = {'settled': _('Fulfilled HTLCs'), 'inflight': _('HTLCs in current commitment transaction')} self.folders = {} self.keyname_rows = {} for keyname, i in folder_types.items(): myFont=QtGui.QFont() myFont.setBold(True) folder = HTLCItem(i) folder.setFont(myFont) parentItem.appendRow(folder) self.folders[keyname] = folder mapping = {} num = 0 invoices = dict(self.window.wallet.lnworker.invoices) for pay_hash, item in htlcs.items(): chan_id, i, direction, status = item lnaddr = None if pay_hash in invoices: invoice = invoices[pay_hash][0] lnaddr = lndecode(invoice) if status == 'inflight': if lnaddr is not None: it = self.make_inflight(lnaddr, i, direction) else: it = self.make_htlc_item(i, direction) elif status == 'settled': it = self.make_htlc_item(i, direction) # if we made the invoice and still have it, we can show more info if lnaddr is not None: self.append_lnaddr(it, lnaddr) self.folders[status].appendRow(it) mapping[i.payment_hash] = num num += 1 self.keyname_rows[keyname] = mapping return model
class TestPeer(ElectrumTestCase): @classmethod def setUpClass(cls): super().setUpClass() console_stderr_handler.setLevel(logging.DEBUG) def setUp(self): super().setUp() self.asyncio_loop, self._stop_loop, self._loop_thread = create_and_start_event_loop() self._lnworkers_created = [] # type: List[MockLNWallet] def tearDown(self): async def cleanup_lnworkers(): async with TaskGroup() as group: for lnworker in self._lnworkers_created: await group.spawn(lnworker.stop()) self._lnworkers_created.clear() run(cleanup_lnworkers()) self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1) self._loop_thread.join(timeout=1) super().tearDown() def prepare_peers(self, alice_channel, bob_channel): k1, k2 = keypair(), keypair() alice_channel.node_id = k2.pubkey bob_channel.node_id = k1.pubkey t1, t2 = transport_pair(k1, k2, alice_channel.name, bob_channel.name) q1, q2 = asyncio.Queue(), asyncio.Queue() w1 = MockLNWallet(local_keypair=k1, chans=[alice_channel], tx_queue=q1, name=bob_channel.name) w2 = MockLNWallet(local_keypair=k2, chans=[bob_channel], tx_queue=q2, name=alice_channel.name) self._lnworkers_created.extend([w1, w2]) p1 = Peer(w1, k2.pubkey, t1) p2 = Peer(w2, k1.pubkey, t2) w1._peers[p1.pubkey] = p1 w2._peers[p2.pubkey] = p2 # mark_open won't work if state is already OPEN. # so set it to FUNDED alice_channel._state = ChannelState.FUNDED bob_channel._state = ChannelState.FUNDED # this populates the channel graph: p1.mark_open(alice_channel) p2.mark_open(bob_channel) return p1, p2, w1, w2, q1, q2 def prepare_chans_and_peers_in_square(self) -> SquareGraph: key_a, key_b, key_c, key_d = [keypair() for i in range(4)] chan_ab, chan_ba = create_test_channels(alice_name="alice", bob_name="bob", alice_pubkey=key_a.pubkey, bob_pubkey=key_b.pubkey) chan_ac, chan_ca = create_test_channels(alice_name="alice", bob_name="carol", alice_pubkey=key_a.pubkey, bob_pubkey=key_c.pubkey) chan_bd, chan_db = create_test_channels(alice_name="bob", bob_name="dave", alice_pubkey=key_b.pubkey, bob_pubkey=key_d.pubkey) chan_cd, chan_dc = create_test_channels(alice_name="carol", bob_name="dave", alice_pubkey=key_c.pubkey, bob_pubkey=key_d.pubkey) trans_ab, trans_ba = transport_pair(key_a, key_b, chan_ab.name, chan_ba.name) trans_ac, trans_ca = transport_pair(key_a, key_c, chan_ac.name, chan_ca.name) trans_bd, trans_db = transport_pair(key_b, key_d, chan_bd.name, chan_db.name) trans_cd, trans_dc = transport_pair(key_c, key_d, chan_cd.name, chan_dc.name) txq_a, txq_b, txq_c, txq_d = [asyncio.Queue() for i in range(4)] w_a = MockLNWallet(local_keypair=key_a, chans=[chan_ab, chan_ac], tx_queue=txq_a, name="alice") w_b = MockLNWallet(local_keypair=key_b, chans=[chan_ba, chan_bd], tx_queue=txq_b, name="bob") w_c = MockLNWallet(local_keypair=key_c, chans=[chan_ca, chan_cd], tx_queue=txq_c, name="carol") w_d = MockLNWallet(local_keypair=key_d, chans=[chan_db, chan_dc], tx_queue=txq_d, name="dave") self._lnworkers_created.extend([w_a, w_b, w_c, w_d]) peer_ab = Peer(w_a, key_b.pubkey, trans_ab) peer_ac = Peer(w_a, key_c.pubkey, trans_ac) peer_ba = Peer(w_b, key_a.pubkey, trans_ba) peer_bd = Peer(w_b, key_d.pubkey, trans_bd) peer_ca = Peer(w_c, key_a.pubkey, trans_ca) peer_cd = Peer(w_c, key_d.pubkey, trans_cd) peer_db = Peer(w_d, key_b.pubkey, trans_db) peer_dc = Peer(w_d, key_c.pubkey, trans_dc) w_a._peers[peer_ab.pubkey] = peer_ab w_a._peers[peer_ac.pubkey] = peer_ac w_b._peers[peer_ba.pubkey] = peer_ba w_b._peers[peer_bd.pubkey] = peer_bd w_c._peers[peer_ca.pubkey] = peer_ca w_c._peers[peer_cd.pubkey] = peer_cd w_d._peers[peer_db.pubkey] = peer_db w_d._peers[peer_dc.pubkey] = peer_dc w_b.network.config.set_key('lightning_forward_payments', True) w_c.network.config.set_key('lightning_forward_payments', True) # forwarding fees, etc chan_ab.forwarding_fee_proportional_millionths *= 500 chan_ab.forwarding_fee_base_msat *= 500 chan_ba.forwarding_fee_proportional_millionths *= 500 chan_ba.forwarding_fee_base_msat *= 500 chan_bd.forwarding_fee_proportional_millionths *= 500 chan_bd.forwarding_fee_base_msat *= 500 chan_db.forwarding_fee_proportional_millionths *= 500 chan_db.forwarding_fee_base_msat *= 500 # mark_open won't work if state is already OPEN. # so set it to FUNDED for chan in [chan_ab, chan_ac, chan_ba, chan_bd, chan_ca, chan_cd, chan_db, chan_dc]: chan._state = ChannelState.FUNDED # this populates the channel graph: peer_ab.mark_open(chan_ab) peer_ac.mark_open(chan_ac) peer_ba.mark_open(chan_ba) peer_bd.mark_open(chan_bd) peer_ca.mark_open(chan_ca) peer_cd.mark_open(chan_cd) peer_db.mark_open(chan_db) peer_dc.mark_open(chan_dc) return SquareGraph( w_a=w_a, w_b=w_b, w_c=w_c, w_d=w_d, peer_ab=peer_ab, peer_ac=peer_ac, peer_ba=peer_ba, peer_bd=peer_bd, peer_ca=peer_ca, peer_cd=peer_cd, peer_db=peer_db, peer_dc=peer_dc, chan_ab=chan_ab, chan_ac=chan_ac, chan_ba=chan_ba, chan_bd=chan_bd, chan_ca=chan_ca, chan_cd=chan_cd, chan_db=chan_db, chan_dc=chan_dc, ) @staticmethod async def prepare_invoice( w2: MockLNWallet, # receiver *, amount_msat=100_000_000, include_routing_hints=False, ) -> Tuple[LnAddr, str]: amount_btc = amount_msat/Decimal(COIN*1000) payment_preimage = os.urandom(32) RHASH = sha256(payment_preimage) info = PaymentInfo(RHASH, amount_msat, RECEIVED, PR_UNPAID) w2.save_preimage(RHASH, payment_preimage) w2.save_payment_info(info) if include_routing_hints: routing_hints = await w2._calc_routing_hints_for_invoice(amount_msat) else: routing_hints = [] trampoline_hints = [] for r in routing_hints: node_id, short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta = r[1][0] if len(r[1])== 1 and w2.is_trampoline_peer(node_id): trampoline_hints.append(('t', (node_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta))) invoice_features = w2.features.for_invoice() if invoice_features.supports(LnFeatures.PAYMENT_SECRET_OPT): payment_secret = derive_payment_secret_from_payment_preimage(payment_preimage) else: payment_secret = None lnaddr1 = LnAddr( paymenthash=RHASH, amount=amount_btc, tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE), ('d', 'coffee'), ('9', invoice_features), ] + routing_hints + trampoline_hints, payment_secret=payment_secret, ) invoice = lnencode(lnaddr1, w2.node_keypair.privkey) lnaddr2 = lndecode(invoice) # unlike lnaddr1, this now has a pubkey set return lnaddr2, invoice
def test_payment_secret(self): lnaddr = lndecode( "lnmona25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q5sqqqqqqqqqqqqqqqpqsqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsguc039ze757wlyqcsr0wtqwpp05mtf9af77e65x2tv55q8cg09682rlyll2u8gdluztskrypzdhl468m6dl7dxzaujvfvlj906njlrcplhts8n" ) self.assertEqual((1 << 9) + (1 << 15) + (1 << 99), lnaddr.get_tag('9')) self.assertEqual(b"\x11" * 32, lnaddr.payment_secret)
def test_min_final_cltv_expiry_decoding(self): self.assertEqual(144, lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe", expected_hrp="sb").get_min_final_cltv_expiry())
def test_min_final_cltv_expiry_roundtrip(self): lnaddr = LnAddr(RHASH, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60), ('c', 150)]) invoice = lnencode(lnaddr, PRIVKEY) self.assertEqual(150, lndecode(invoice).get_min_final_cltv_expiry())
def test_min_final_cltv_expiry_decoding(self): lnaddr = lndecode( "lnsmona25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeescqzyssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsj7dv5y27jkfl9y2vxu8jddmgmk4av25j80vwesnwejd089805a5z6gupm4cn23h0k6gaj8nnakm3jz4ujvcwkljgm7jr2zgu3s9uz2gqgdx8jp", expected_hrp="smona") self.assertEqual(144, lnaddr.get_min_final_cltv_expiry())
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') timestamp = 1615922274 tests = [ (LnAddr(date=timestamp, paymenthash=RHASH, tags=[('d', '')]), "lnmona1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqyhhfge73xtqkqr3ca22rvu3hyxfmesxahwyxdh3tya02p7x7ah3qt6zz4s5yn25z6hh4urf8c3598sdl8zxjpph0fg35y0kfn8vq6vsqd340h9" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('0.001'), tags=[('d', '1 cup coffee'), ('x', 60)]), "lnmona1m1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu206axye9c7fz0sc6y6f2e97l48mgpxv3r6v5k8rt7ce082fl64vskpcxvypnmjeg5amxe79sylp900rxgv2k8el535c0y22807v4ggsqglal97" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=Decimal('1'), tags=[('h', longdescription)]), "lnmona11ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs2hgyzzwfazhwxpj3edw9rw3gmsmcl3ktrzdeh3nkldnhzc0g6gfpkg3sqzx2dhuzetqsq6weypkegn3sfcg2tqyv0eyf6u7yq8rqw3gp764v5x" ), (LnAddr(date=timestamp, paymenthash=RHASH, net=constants.BitcoinTestnet, tags=[('f', 'mivTxWUqB6yxQdbLnAfcTSaVXouAhTUDfs'), ('h', longdescription)]), "lntmona1ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3y4dtfd4mcpuul3wmteaxatgldveymxxyhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsq06ue05mjfwgnra27g6tnhzywtj7qh5rhuutf77apa7dclddp4nz0zwjcvqm4j8fd2pm2u8rwultx232yvdxsmh5fueffjexfuzn40gpaz4tq5" ), (LnAddr( date=timestamp, paymenthash=RHASH, amount=24, tags= [('r', [(unhexlify( '029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255' ), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify( '039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255' ), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', 'MRHx4jW2KAQeEDMuK7pGLUGWvPRQT1Epmj'), ('h', longdescription)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqfpp3hmyccldvzvekns7z4cmvu0lsg7stk9r2hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs0m55wzqx4wrwhx27e9q830ua7gf0q8ft8axr6td50ztezsh84ra4ql6za2yk8462x4c7n3agqvtc6rxaug7f8udpv7faq0czepz3q0gp5zm5qk" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('f', 'PHjTKtgYLTJ9D2Bzw2f6xBB41KBm2HeGfg'), ('h', longdescription)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppjv3yl26xfe53hsyu0ycmwz4n3z2scf20ghp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs94ex52vmwdkq0rsxjr8r7k8k40egy745aqlws0yk7j793zv5tqfq4arqjlw7peu8r62hvpkcqjquuztgst47vpzqzt8ju8c36gqx2tgp3jmyzn" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('f', 'mona1quunc907zfyj7cyxhnp9584rj0wmdka2ec9w3af'), ('h', longdescription)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppquunc907zfyj7cyxhnp9584rj0wmdka2ehp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsqvn67ssxdtqakqvxfy4lsen8sqw4pzd2j230lz7dx6chd737csyjcfe93dc4s099faaeg37us2dzt0pwfrtfnp429k40vfu06qpf28squq5wy0" ), (LnAddr( date=timestamp, paymenthash=RHASH, amount=24, tags= [('f', 'mona1qp8f842ywwr9h5rdxyzggex7q3trvvvaarfssxccju52rj6htfzfsqr79j2' ), ('h', longdescription)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qp8f842ywwr9h5rdxyzggex7q3trvvvaarfssxccju52rj6htfzfshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsd45jh4k2e8jvhzpthqyc2sspr30k3pg67f4a973qumvw48t4gzl4tdr9qh7p04z5afghspsapvp3tahcq7rasw7dtv2vv46sm79479qpm9jdjl" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('n', PUBKEY), ('h', longdescription)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsdcmmg0p9qn003ls0r8dv2f9d7xh7lfx6lsha7h5azxzfdd292r4yuflmhs3dkkghmzaecedwzrvxktsnrhcfmsnx7r64chxw5phrwggq32hqsz" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 514)]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qzszz2jjnvrh9rg877px70r5cfrf0vqwzws0jxmcg3d4pqg0ml337s8qsmxjyedvssge254qjwucnjy3rtzr7gxzfatsy3ad5x4c0ll93acq3rwstg" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 8))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qzg2vwag8ax9f9wkmsz37nvq8qkq53tpznev8jjnkezq7u8qzzytad85zhxgk7hqgqkqrftazq8hfr6lv9q4j4vk2ld6ymrjtsnumetlhjqqe7cl7z" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qzs2p8uyuepjhdpyfsv6xuc0enx57upz4sdz0n38s7mfc07lpm5vvc9zdursykxpz8heddckdg2d6wsjltuf93rrewptuekp5s9w06wucacqgjaara" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 7) + (1 << 11))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrzy2xw96qw57wvauu8skgsvlpnvqluwy4txpm7pw3q9r70lryezhvjrjj9f7tuage9une2hn26quv9fpm9wa4h672288m5ek4jk4gh2cm5sqlkg3yn" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 12))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qryq29tefcvke7zl35fjehvn0t3vnw2uae766g7gxuj23tk4vfsxjzk2n45vnms6ew2pvydlhy7vfxptnwcs0x7qjuct0feg4vryzl3trvlspfvn5v7" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 13))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrgq2yctvzkmsa8w0elyd7gx5wp5lflsx8wsqw5l2ejytgd3q5qrwjkckvqsjamwq93eekje8qwzphsjh9hcy3lmpvrdlks8tmutuad3k9eqquhzhfz" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 14))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrss2dccexrncjpgvjfy93t507h07l63qt7k7fp3zeptdqadv9jmulu6hfa6syjwqqrvjf3a9fu50ap0vtjtrmyrqzjg46are0apf6hq6ztqqfhkr7e" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 10 + (1 << 9) + (1 << 15))]), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqs29trq3u6ctz9qnm4hate5u74f0w6j4tcjj3wr79czn4n48egmze98kwk8wt6e6cj5890jpd8dvwfttua2qullslfyf04save5fnvgp9sqkusukq" ), (LnAddr(date=timestamp, paymenthash=RHASH, amount=24, tags=[('h', longdescription), ('9', 33282)], payment_secret=b"\x11" * 32), "lnmona241ps9zprzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qypqsz29tu4q7pmhu58qywk2mfd5vpuscy308del7mq76pywkqtjz36f240cgcxrpuzzzzprw7gvnwvwn6vxnpm7dtg2t258f0zy0zl7ldnrcpvzyrkz" ), ] # Roundtrip for lnaddr1, invoice_str1 in tests: invoice_str2 = lnencode(lnaddr1, PRIVKEY) self.assertEqual(invoice_str1, invoice_str2) lnaddr2 = lndecode(invoice_str2, net=lnaddr1.net) self.compare(lnaddr1, lnaddr2)