|
5 | 5 | import dataclasses |
6 | 6 | import logging |
7 | 7 | from functools import partial |
8 | | -from unittest.mock import Mock |
9 | | -from types import MappingProxyType |
| 8 | +from unittest.mock import patch |
10 | 9 | from aiorpcx import NetAddress |
11 | 10 |
|
12 | 11 | import electrum_ecc as ecc |
|
17 | 16 | from electrum.lnonion import ( |
18 | 17 | OnionHopsDataSingle, OnionPacket, process_onion_packet, get_bolt04_onion_key, encrypt_onionmsg_data_tlv, |
19 | 18 | get_shared_secrets_along_route, new_onion_packet, ONION_MESSAGE_LARGE_SIZE, HOPS_DATA_SIZE, InvalidPayloadSize, |
20 | | - encrypt_hops_recipient_data, blinding_privkey) |
| 19 | + encrypt_hops_recipient_data, blinding_privkey, decrypt_onionmsg_data_tlv) |
21 | 20 | from electrum.crypto import get_ecdh, privkey_to_pubkey |
22 | 21 | from electrum.lntransport import LNPeerAddr |
23 | 22 | from electrum.lnutil import LnFeatures, Keypair, MIN_FINAL_CLTV_DELTA_ACCEPTED, REMOTE |
24 | 23 | from electrum.onion_message import ( |
25 | | - create_blinded_path, OnionMessageManager, NoRouteFound, Timeout, get_blinded_paths_to_me, |
| 24 | + create_blinded_path, OnionMessageManager, NoRouteFound, Timeout, |
| 25 | + create_route_to_introduction_point, get_blinded_paths_to_me |
26 | 26 | ) |
27 | 27 | from electrum.util import bfh, read_json_file, OldTaskGroup, get_asyncio_loop |
28 | 28 | from electrum.logging import console_stderr_handler |
29 | 29 |
|
30 | 30 | from . import ElectrumTestCase |
31 | | -from .test_lnpeer import TestPeer |
| 31 | +from .test_lnpeer import TestPeer, inject_chan_into_gossipdb |
32 | 32 |
|
33 | 33 |
|
34 | 34 | TIME_STEP = 0.01 # run tests 100 x faster |
@@ -531,3 +531,67 @@ async def test_get_blinded_paths_to_me_payment(self): |
531 | 531 | self.assertEqual(blinded_path['num_hops'], len(blinded_path['path']).to_bytes(length=1)) |
532 | 532 | self.assertIn('blinded_node_id', blinded_path['path'][0]) |
533 | 533 | self.assertIn('encrypted_recipient_data', blinded_path['path'][0]) |
| 534 | + |
| 535 | + async def test_create_route_to_introduction_point(self): |
| 536 | + # A -- B -- C -- D -- E |
| 537 | + # Alice constructs route to Edward as introduction point to some blinded path |
| 538 | + line_graph = self.GRAPH_DEFINITIONS['line_graph'] |
| 539 | + graph = self.prepare_chans_and_peers_in_graph(line_graph) |
| 540 | + alice, bob, carol, dave, edward = graph.workers.values() |
| 541 | + |
| 542 | + session_key = os.urandom(32) |
| 543 | + introduction_point = edward.node_keypair.pubkey |
| 544 | + first_path_key = ecc.ECPrivkey.generate_random_key().get_public_key_bytes() |
| 545 | + blinded_path = { |
| 546 | + 'first_path_key': first_path_key, |
| 547 | + } |
| 548 | + with self.assertRaises(NoRouteFound): |
| 549 | + create_route_to_introduction_point(alice, blinded_path, introduction_point, session_key) |
| 550 | + |
| 551 | + for name, definition in line_graph.items(): |
| 552 | + for channel_partner in definition.get('channels', {}): |
| 553 | + inject_chan_into_gossipdb( |
| 554 | + channel_db=alice.channel_db, |
| 555 | + graph=graph, |
| 556 | + node1name=name, |
| 557 | + node2name=channel_partner, |
| 558 | + ) |
| 559 | + |
| 560 | + # patch is_onion_message_node so we don't have to inject node announcements |
| 561 | + with patch('electrum.onion_message.is_onion_message_node', return_value=True): |
| 562 | + r = create_route_to_introduction_point(alice, blinded_path, introduction_point, session_key) |
| 563 | + peer, path_key, hops_data, blinded_node_ids = r |
| 564 | + # alice hands the onion over to bob |
| 565 | + self.assertEqual(peer.pubkey, bob.lnpeermgr.node_keypair.pubkey) |
| 566 | + |
| 567 | + self.assertEqual(path_key, ecc.ECPrivkey(session_key).get_public_key_bytes()) |
| 568 | + self.assertEqual(len(hops_data), 3) |
| 569 | + self.assertEqual(len(hops_data), len(blinded_node_ids)) |
| 570 | + |
| 571 | + # bob unwraps the first layer, sees the next peer, next peer should be carol |
| 572 | + self.assertEqual(hops_data[0].blind_fields['next_node_id']['node_id'], carol.lnpeermgr.node_keypair.pubkey) |
| 573 | + self.assertEqual(hops_data[1].blind_fields['next_node_id']['node_id'], dave.lnpeermgr.node_keypair.pubkey) |
| 574 | + self.assertEqual(hops_data[2].blind_fields['next_node_id']['node_id'], edward.lnpeermgr.node_keypair.pubkey) |
| 575 | + self.assertEqual(hops_data[2].blind_fields['next_path_key_override']['path_key'], first_path_key) |
| 576 | + |
| 577 | + # verify that the recipient data is encrypted to the correct node |
| 578 | + hop_shared_secrets, blinded_node_ids = get_shared_secrets_along_route( |
| 579 | + (bob.node_keypair.pubkey, carol.node_keypair.pubkey, dave.node_keypair.pubkey), |
| 580 | + session_key) |
| 581 | + for hop, ss in zip(hops_data, hop_shared_secrets): |
| 582 | + encrypted_recipient_data = hop.payload['encrypted_recipient_data']['encrypted_recipient_data'] |
| 583 | + decrypt_onionmsg_data_tlv( |
| 584 | + shared_secret=ss, |
| 585 | + encrypted_recipient_data=encrypted_recipient_data, |
| 586 | + ) |
| 587 | + |
| 588 | + # now Bob is IP, Alice is directly connected to IP |
| 589 | + introduction_point = bob.node_keypair.pubkey |
| 590 | + r = create_route_to_introduction_point(alice, blinded_path, introduction_point, session_key) |
| 591 | + peer, path_key, hops_data, blinded_node_ids = r |
| 592 | + self.assertEqual(path_key, first_path_key) |
| 593 | + self.assertEqual(len(hops_data), 0) |
| 594 | + self.assertEqual(len(blinded_node_ids), 0) |
| 595 | + alice_bob_peer = alice.lnpeermgr.get_peer_by_pubkey(bob.node_keypair.pubkey) |
| 596 | + self.assertIsNotNone(alice_bob_peer) |
| 597 | + self.assertEqual(peer, alice_bob_peer) |
0 commit comments