1212use bitcoin:: secp256k1:: ecdh:: SharedSecret ;
1313use bitcoin:: secp256k1:: { self , PublicKey , Secp256k1 , SecretKey } ;
1414
15+ use crate :: blinded_path:: message:: MAX_DUMMY_HOPS_COUNT ;
1516use crate :: blinded_path:: utils:: { self , BlindedPathWithPadding } ;
1617use crate :: blinded_path:: { BlindedHop , BlindedPath , IntroductionNode , NodeIdLookUp } ;
1718use crate :: crypto:: streams:: ChaChaDualPolyReadAdapter ;
@@ -121,6 +122,32 @@ impl BlindedPaymentPath {
121122 local_node_receive_key : ReceiveAuthKey , payee_tlvs : ReceiveTlvs , htlc_maximum_msat : u64 ,
122123 min_final_cltv_expiry_delta : u16 , entropy_source : ES , secp_ctx : & Secp256k1 < T > ,
123124 ) -> Result < Self , ( ) >
125+ where
126+ ES :: Target : EntropySource ,
127+ {
128+ BlindedPaymentPath :: new_with_dummy_hops (
129+ intermediate_nodes,
130+ payee_node_id,
131+ 0 ,
132+ local_node_receive_key,
133+ payee_tlvs,
134+ htlc_maximum_msat,
135+ min_final_cltv_expiry_delta,
136+ entropy_source,
137+ secp_ctx,
138+ )
139+ }
140+
141+ /// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops.
142+ ///
143+ /// Note:
144+ /// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path.
145+ pub fn new_with_dummy_hops < ES : Deref , T : secp256k1:: Signing + secp256k1:: Verification > (
146+ intermediate_nodes : & [ PaymentForwardNode ] , payee_node_id : PublicKey ,
147+ dummy_hop_count : usize , local_node_receive_key : ReceiveAuthKey , payee_tlvs : ReceiveTlvs ,
148+ htlc_maximum_msat : u64 , min_final_cltv_expiry_delta : u16 , entropy_source : ES ,
149+ secp_ctx : & Secp256k1 < T > ,
150+ ) -> Result < Self , ( ) >
124151 where
125152 ES :: Target : EntropySource ,
126153 {
@@ -145,6 +172,7 @@ impl BlindedPaymentPath {
145172 secp_ctx,
146173 intermediate_nodes,
147174 payee_node_id,
175+ dummy_hop_count,
148176 payee_tlvs,
149177 & blinding_secret,
150178 local_node_receive_key,
@@ -363,6 +391,7 @@ pub(crate) enum BlindedTrampolineTlvs {
363391}
364392
365393// Used to include forward and receive TLVs in the same iterator for encoding.
394+ #[ derive( Clone ) ]
366395enum BlindedPaymentTlvsRef < ' a > {
367396 Forward ( & ' a ForwardTlvs ) ,
368397 Dummy ,
@@ -644,22 +673,48 @@ pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30;
644673/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
645674pub ( super ) fn blinded_hops < T : secp256k1:: Signing + secp256k1:: Verification > (
646675 secp_ctx : & Secp256k1 < T > , intermediate_nodes : & [ PaymentForwardNode ] , payee_node_id : PublicKey ,
647- payee_tlvs : ReceiveTlvs , session_priv : & SecretKey , local_node_receive_key : ReceiveAuthKey ,
676+ dummy_hop_count : usize , payee_tlvs : ReceiveTlvs , session_priv : & SecretKey ,
677+ local_node_receive_key : ReceiveAuthKey ,
648678) -> Vec < BlindedHop > {
679+ let dummy_count = core:: cmp:: min ( dummy_hop_count, MAX_DUMMY_HOPS_COUNT ) ;
649680 let pks = intermediate_nodes
650681 . iter ( )
651682 . map ( |node| ( node. node_id , None ) )
683+ . chain ( core:: iter:: repeat ( ( payee_node_id, Some ( local_node_receive_key) ) ) . take ( dummy_count) )
652684 . chain ( core:: iter:: once ( ( payee_node_id, Some ( local_node_receive_key) ) ) ) ;
653685 let tlvs = intermediate_nodes
654686 . iter ( )
655687 . map ( |node| BlindedPaymentTlvsRef :: Forward ( & node. tlvs ) )
688+ . chain ( core:: iter:: repeat ( BlindedPaymentTlvsRef :: Dummy ) . take ( dummy_count) )
656689 . chain ( core:: iter:: once ( BlindedPaymentTlvsRef :: Receive ( & payee_tlvs) ) ) ;
657690
658691 let path = pks. zip (
659692 tlvs. map ( |tlv| BlindedPathWithPadding { tlvs : tlv, round_off : PAYMENT_PADDING_ROUND_OFF } ) ,
660693 ) ;
661694
662- utils:: construct_blinded_hops ( secp_ctx, path, session_priv)
695+ // Debug invariant: all non-final hops must have identical serialized size.
696+ #[ cfg( debug_assertions) ]
697+ {
698+ let mut iter = path. clone ( ) ;
699+ if let Some ( ( _, first) ) = iter. next ( ) {
700+ let remaining = iter. clone ( ) . count ( ) ; // includes intermediate + final
701+
702+ // At least one intermediate hop
703+ if remaining > 1 {
704+ let expected = first. serialized_length ( ) ;
705+
706+ // skip final hop: take(remaining - 1)
707+ for ( _, hop) in iter. take ( remaining - 1 ) {
708+ debug_assert ! (
709+ hop. serialized_length( ) == expected,
710+ "All intermediate blinded hops must have identical serialized size"
711+ ) ;
712+ }
713+ }
714+ }
715+ }
716+
717+ utils:: construct_blinded_hops ( secp_ctx, path. into_iter ( ) , session_priv)
663718}
664719
665720/// `None` if underflow occurs.
0 commit comments