@@ -60,7 +60,7 @@ use crate::offers::invoice_error::InvoiceError;
6060use crate :: offers:: invoice_request:: { InvoiceRequest , InvoiceRequestFields , InvoiceRequestVerifiedFromOffer } ;
6161use crate :: offers:: nonce:: Nonce ;
6262use crate :: offers:: parse:: Bolt12SemanticError ;
63- use crate :: onion_message:: messenger:: { DefaultMessageRouter , Destination , MessageSendInstructions , NodeIdMessageRouter , NullMessageRouter , PeeledOnion , PADDED_PATH_LENGTH } ;
63+ use crate :: onion_message:: messenger:: { DefaultMessageRouter , Destination , MessageSendInstructions , NodeIdMessageRouter , NullMessageRouter , PeeledOnion , DUMMY_HOPS_PATH_LENGTH , QR_CODED_DUMMY_HOPS_PATH_LENGTH } ;
6464use crate :: onion_message:: offers:: OffersMessage ;
6565use crate :: routing:: gossip:: { NodeAlias , NodeId } ;
6666use crate :: routing:: router:: { PaymentParameters , RouteParameters , RouteParametersConfig } ;
@@ -163,6 +163,20 @@ fn check_compact_path_introduction_node<'a, 'b, 'c>(
163163 && matches ! ( path. introduction_node( ) , IntroductionNode :: DirectedShortChannelId ( ..) )
164164}
165165
166+ fn check_dummy_hopd_path_length < ' a , ' b , ' c > (
167+ path : & BlindedMessagePath ,
168+ lookup_node : & Node < ' a , ' b , ' c > ,
169+ expected_introduction_node : PublicKey ,
170+ expected_path_length : usize ,
171+ ) -> bool {
172+ let introduction_node_id = resolve_introduction_node ( lookup_node, path) ;
173+ let first_hop_len = path. blinded_hops ( ) . first ( ) . unwrap ( ) . encrypted_payload . len ( ) ;
174+ let hops = path. blinded_hops ( ) ;
175+ introduction_node_id == expected_introduction_node
176+ && hops. len ( ) == expected_path_length
177+ && hops. iter ( ) . take ( hops. len ( ) - 1 ) . all ( |hop| hop. encrypted_payload . len ( ) == first_hop_len)
178+ }
179+
166180fn route_bolt12_payment < ' a , ' b , ' c > (
167181 node : & Node < ' a , ' b , ' c > , path : & [ & Node < ' a , ' b , ' c > ] , invoice : & Bolt12Invoice
168182) {
@@ -455,7 +469,7 @@ fn check_dummy_hop_pattern_in_offer() {
455469 let bob_id = bob. node . get_our_node_id ( ) ;
456470
457471 // Case 1: DefaultMessageRouter → uses compact blinded paths (via SCIDs)
458- // Expected: No dummy hops; each path contains only the recipient.
472+ // Expected: Padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH for QR code size optimization
459473 let default_router = DefaultMessageRouter :: new ( alice. network_graph , alice. keys_manager ) ;
460474
461475 let compact_offer = alice. node
@@ -467,8 +481,8 @@ fn check_dummy_hop_pattern_in_offer() {
467481
468482 for path in compact_offer. paths ( ) {
469483 assert_eq ! (
470- path. blinded_hops( ) . len( ) , 1 ,
471- "Compact paths must include only the recipient "
484+ path. blinded_hops( ) . len( ) , QR_CODED_DUMMY_HOPS_PATH_LENGTH ,
485+ "Compact offer paths are padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH "
472486 ) ;
473487 }
474488
@@ -480,10 +494,10 @@ fn check_dummy_hop_pattern_in_offer() {
480494
481495 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
482496 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , bob_id) ;
483- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, bob_id) ) ;
497+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
484498
485499 // Case 2: NodeIdMessageRouter → uses node ID-based blinded paths
486- // Expected: 0 to MAX_DUMMY_HOPS_COUNT dummy hops, followed by recipient.
500+ // Expected: Also padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH for QR code size optimization
487501 let node_id_router = NodeIdMessageRouter :: new ( alice. network_graph , alice. keys_manager ) ;
488502
489503 let padded_offer = alice. node
@@ -492,7 +506,7 @@ fn check_dummy_hop_pattern_in_offer() {
492506 . build ( ) . unwrap ( ) ;
493507
494508 assert ! ( !padded_offer. paths( ) . is_empty( ) ) ;
495- assert ! ( padded_offer. paths( ) . iter( ) . all( |path| path. blinded_hops( ) . len( ) == PADDED_PATH_LENGTH ) ) ;
509+ assert ! ( padded_offer. paths( ) . iter( ) . all( |path| path. blinded_hops( ) . len( ) == QR_CODED_DUMMY_HOPS_PATH_LENGTH ) ) ;
496510
497511 let payment_id = PaymentId ( [ 2 ; 32 ] ) ;
498512 bob. node . pay_for_offer ( & padded_offer, None , payment_id, Default :: default ( ) ) . unwrap ( ) ;
@@ -502,7 +516,7 @@ fn check_dummy_hop_pattern_in_offer() {
502516
503517 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
504518 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , bob_id) ;
505- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, bob_id) ) ;
519+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
506520}
507521
508522/// Checks that blinded paths are compact for short-lived offers.
@@ -687,7 +701,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
687701 } ) ;
688702 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
689703 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , david_id) ;
690- assert ! ( check_compact_path_introduction_node ( & reply_path, bob, charlie_id) ) ;
704+ assert ! ( check_dummy_hopd_path_length ( & reply_path, bob, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
691705
692706 let onion_message = alice. onion_messenger . next_onion_message_for_peer ( charlie_id) . unwrap ( ) ;
693707 charlie. onion_messenger . handle_onion_message ( alice_id, & onion_message) ;
@@ -706,8 +720,8 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
706720 // to Alice when she's handling the message. Therefore, either Bob or Charlie could
707721 // serve as the introduction node for the reply path back to Alice.
708722 assert ! (
709- check_compact_path_introduction_node ( & reply_path, david, bob_id) ||
710- check_compact_path_introduction_node ( & reply_path, david, charlie_id)
723+ check_dummy_hopd_path_length ( & reply_path, david, bob_id, DUMMY_HOPS_PATH_LENGTH ) ||
724+ check_dummy_hopd_path_length ( & reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH )
711725 ) ;
712726
713727 route_bolt12_payment ( david, & [ charlie, bob, alice] , & invoice) ;
@@ -790,7 +804,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
790804 for path in invoice. payment_paths ( ) {
791805 assert_eq ! ( path. introduction_node( ) , & IntroductionNode :: NodeId ( bob_id) ) ;
792806 }
793- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, bob_id) ) ;
807+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
794808
795809 route_bolt12_payment ( david, & [ charlie, bob, alice] , & invoice) ;
796810 expect_recent_payment ! ( david, RecentPaymentDetails :: Pending , payment_id) ;
@@ -845,7 +859,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
845859 } ) ;
846860 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
847861 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , bob_id) ;
848- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, bob_id) ) ;
862+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
849863
850864 let onion_message = alice. onion_messenger . next_onion_message_for_peer ( bob_id) . unwrap ( ) ;
851865 bob. onion_messenger . handle_onion_message ( alice_id, & onion_message) ;
@@ -857,7 +871,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
857871 for path in invoice. payment_paths ( ) {
858872 assert_eq ! ( path. introduction_node( ) , & IntroductionNode :: NodeId ( alice_id) ) ;
859873 }
860- assert ! ( check_compact_path_introduction_node ( & reply_path, bob, alice_id) ) ;
874+ assert ! ( check_dummy_hopd_path_length ( & reply_path, bob, alice_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
861875
862876 route_bolt12_payment ( bob, & [ alice] , & invoice) ;
863877 expect_recent_payment ! ( bob, RecentPaymentDetails :: Pending , payment_id) ;
@@ -913,7 +927,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
913927 for path in invoice. payment_paths ( ) {
914928 assert_eq ! ( path. introduction_node( ) , & IntroductionNode :: NodeId ( alice_id) ) ;
915929 }
916- assert ! ( check_compact_path_introduction_node ( & reply_path, bob, alice_id) ) ;
930+ assert ! ( check_dummy_hopd_path_length ( & reply_path, bob, alice_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
917931
918932 route_bolt12_payment ( bob, & [ alice] , & invoice) ;
919933 expect_recent_payment ! ( bob, RecentPaymentDetails :: Pending , payment_id) ;
@@ -1059,6 +1073,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10591073 let bob_id = bob. node . get_our_node_id ( ) ;
10601074 let charlie_id = charlie. node . get_our_node_id ( ) ;
10611075 let david_id = david. node . get_our_node_id ( ) ;
1076+ let frank_id = nodes[ 6 ] . node . get_our_node_id ( ) ;
10621077
10631078 disconnect_peers ( alice, & [ charlie, david, & nodes[ 4 ] , & nodes[ 5 ] , & nodes[ 6 ] ] ) ;
10641079 disconnect_peers ( david, & [ bob, & nodes[ 4 ] , & nodes[ 5 ] ] ) ;
@@ -1089,7 +1104,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10891104 alice. onion_messenger . handle_onion_message ( bob_id, & onion_message) ;
10901105
10911106 let ( _, reply_path) = extract_invoice_request ( alice, & onion_message) ;
1092- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, charlie_id) ) ;
1107+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
10931108
10941109 // Send, extract and verify the second Invoice Request message
10951110 let onion_message = david. onion_messenger . next_onion_message_for_peer ( bob_id) . unwrap ( ) ;
@@ -1099,7 +1114,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10991114 alice. onion_messenger . handle_onion_message ( bob_id, & onion_message) ;
11001115
11011116 let ( _, reply_path) = extract_invoice_request ( alice, & onion_message) ;
1102- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, nodes [ 6 ] . node . get_our_node_id ( ) ) ) ;
1117+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, frank_id , DUMMY_HOPS_PATH_LENGTH ) ) ;
11031118}
11041119
11051120/// This test checks that when multiple potential introduction nodes are available for the payee,
@@ -1170,7 +1185,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
11701185 let onion_message = bob. onion_messenger . next_onion_message_for_peer ( alice_id) . unwrap ( ) ;
11711186
11721187 let ( _, reply_path) = extract_invoice ( alice, & onion_message) ;
1173- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, charlie_id) ) ;
1188+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
11741189
11751190 // Send, extract and verify the second Invoice Request message
11761191 let onion_message = david. onion_messenger . next_onion_message_for_peer ( bob_id) . unwrap ( ) ;
@@ -1179,7 +1194,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
11791194 let onion_message = bob. onion_messenger . next_onion_message_for_peer ( alice_id) . unwrap ( ) ;
11801195
11811196 let ( _, reply_path) = extract_invoice ( alice, & onion_message) ;
1182- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, nodes[ 6 ] . node. get_our_node_id( ) ) ) ;
1197+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, nodes[ 6 ] . node. get_our_node_id( ) , DUMMY_HOPS_PATH_LENGTH ) ) ;
11831198}
11841199
11851200/// Verifies that the invoice request message can be retried if it fails to reach the
@@ -1233,7 +1248,7 @@ fn creates_and_pays_for_offer_with_retry() {
12331248 } ) ;
12341249 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
12351250 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , bob_id) ;
1236- assert ! ( check_compact_path_introduction_node ( & reply_path, alice, bob_id) ) ;
1251+ assert ! ( check_dummy_hopd_path_length ( & reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
12371252 let onion_message = alice. onion_messenger . next_onion_message_for_peer ( bob_id) . unwrap ( ) ;
12381253 bob. onion_messenger . handle_onion_message ( alice_id, & onion_message) ;
12391254
@@ -1534,7 +1549,7 @@ fn fails_authentication_when_handling_invoice_request() {
15341549 let ( invoice_request, reply_path) = extract_invoice_request ( alice, & onion_message) ;
15351550 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
15361551 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , david_id) ;
1537- assert ! ( check_compact_path_introduction_node ( & reply_path, david, charlie_id) ) ;
1552+ assert ! ( check_dummy_hopd_path_length ( & reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
15381553
15391554 assert_eq ! ( alice. onion_messenger. next_onion_message_for_peer( charlie_id) , None ) ;
15401555
@@ -1563,7 +1578,7 @@ fn fails_authentication_when_handling_invoice_request() {
15631578 let ( invoice_request, reply_path) = extract_invoice_request ( alice, & onion_message) ;
15641579 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
15651580 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , david_id) ;
1566- assert ! ( check_compact_path_introduction_node ( & reply_path, david, charlie_id) ) ;
1581+ assert ! ( check_dummy_hopd_path_length ( & reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
15671582
15681583 assert_eq ! ( alice. onion_messenger. next_onion_message_for_peer( charlie_id) , None ) ;
15691584}
@@ -1663,7 +1678,7 @@ fn fails_authentication_when_handling_invoice_for_offer() {
16631678 let ( invoice_request, reply_path) = extract_invoice_request ( alice, & onion_message) ;
16641679 assert_eq ! ( invoice_request. amount_msats( ) , Some ( 10_000_000 ) ) ;
16651680 assert_ne ! ( invoice_request. payer_signing_pubkey( ) , david_id) ;
1666- assert ! ( check_compact_path_introduction_node ( & reply_path, david, charlie_id) ) ;
1681+ assert ! ( check_dummy_hopd_path_length ( & reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH ) ) ;
16671682
16681683 let onion_message = alice. onion_messenger . next_onion_message_for_peer ( charlie_id) . unwrap ( ) ;
16691684 charlie. onion_messenger . handle_onion_message ( alice_id, & onion_message) ;
0 commit comments