@@ -10,7 +10,7 @@ use lightning::chain;
1010use lightning:: chain:: chaininterface:: { BroadcasterInterface , FeeEstimator } ;
1111use lightning:: chain:: keysinterface:: { Recipient , KeysInterface , Sign } ;
1212use lightning:: ln:: { PaymentHash , PaymentPreimage , PaymentSecret } ;
13- use lightning:: ln:: channelmanager:: { ChannelDetails , ChannelManager , PaymentId , PaymentSendFailure , MIN_FINAL_CLTV_EXPIRY } ;
13+ use lightning:: ln:: channelmanager:: { ChannelDetails , ChannelManager , PaymentId , PaymentSendFailure , PhantomRouteHints , MIN_FINAL_CLTV_EXPIRY , MIN_CLTV_EXPIRY_DELTA } ;
1414use lightning:: ln:: msgs:: LightningError ;
1515use lightning:: routing:: scoring:: Score ;
1616use lightning:: routing:: network_graph:: { NetworkGraph , RoutingFees } ;
@@ -21,6 +21,99 @@ use core::convert::TryInto;
2121use core:: ops:: Deref ;
2222use core:: time:: Duration ;
2323
24+ #[ cfg( feature = "std" ) ]
25+ /// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
26+ /// See [`PhantomKeysManager`] for more information on phantom node payments.
27+ ///
28+ /// `phantom_route_hints` parameter:
29+ /// * Contains channel info for all nodes participating in the phantom invoice
30+ /// * Entries are retrieved from a call to [`ChannelManager::get_phantom_route_hints`] on each
31+ /// participating node
32+ /// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
33+ /// updated when a channel becomes disabled or closes
34+ /// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
35+ /// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
36+ /// down
37+ ///
38+ /// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
39+ /// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any
40+ /// participating node.
41+ ///
42+ /// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom
43+ /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
44+ /// requirement).
45+ ///
46+ /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
47+ /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
48+ /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
49+ pub fn create_phantom_invoice < Signer : Sign , K : Deref > (
50+ amt_msat : Option < u64 > , description : String , payment_hash : PaymentHash , payment_secret :
51+ PaymentSecret , phantom_route_hints : Vec < PhantomRouteHints > , keys_manager : K , network : Currency
52+ ) -> Result < Invoice , SignOrCreationError < ( ) > > where K :: Target : KeysInterface {
53+ if phantom_route_hints. len ( ) == 0 {
54+ return Err ( SignOrCreationError :: CreationError ( CreationError :: MissingRouteHints ) )
55+ }
56+ let mut invoice = InvoiceBuilder :: new ( network)
57+ . description ( description)
58+ . current_timestamp ( )
59+ . payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
60+ . payment_secret ( payment_secret)
61+ . min_final_cltv_expiry ( MIN_FINAL_CLTV_EXPIRY . into ( ) ) ;
62+ if let Some ( amt) = amt_msat {
63+ invoice = invoice. amount_milli_satoshis ( amt) ;
64+ }
65+
66+ for hint in phantom_route_hints {
67+ for channel in & hint. channels {
68+ let short_channel_id = match channel. short_channel_id {
69+ Some ( id) => id,
70+ None => continue ,
71+ } ;
72+ let forwarding_info = match & channel. counterparty . forwarding_info {
73+ Some ( info) => info. clone ( ) ,
74+ None => continue ,
75+ } ;
76+ invoice = invoice. private_route ( RouteHint ( vec ! [
77+ RouteHintHop {
78+ src_node_id: channel. counterparty. node_id,
79+ short_channel_id,
80+ fees: RoutingFees {
81+ base_msat: forwarding_info. fee_base_msat,
82+ proportional_millionths: forwarding_info. fee_proportional_millionths,
83+ } ,
84+ cltv_expiry_delta: forwarding_info. cltv_expiry_delta,
85+ htlc_minimum_msat: None ,
86+ htlc_maximum_msat: None ,
87+ } ,
88+ RouteHintHop {
89+ src_node_id: hint. real_node_pubkey,
90+ short_channel_id: hint. phantom_scid,
91+ fees: RoutingFees {
92+ base_msat: 0 ,
93+ proportional_millionths: 0 ,
94+ } ,
95+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA ,
96+ htlc_minimum_msat: None ,
97+ htlc_maximum_msat: None ,
98+ } ] )
99+ ) ;
100+ }
101+ }
102+
103+ let raw_invoice = match invoice. build_raw ( ) {
104+ Ok ( inv) => inv,
105+ Err ( e) => return Err ( SignOrCreationError :: CreationError ( e) )
106+ } ;
107+ let hrp_str = raw_invoice. hrp . to_string ( ) ;
108+ let hrp_bytes = hrp_str. as_bytes ( ) ;
109+ let data_without_signature = raw_invoice. data . to_base32 ( ) ;
110+ let signed_raw_invoice = raw_invoice. sign ( |_| keys_manager. sign_invoice ( hrp_bytes, & data_without_signature, Recipient :: PhantomNode ) ) ;
111+ match signed_raw_invoice {
112+ Ok ( inv) => Ok ( Invoice :: from_signed ( inv) . unwrap ( ) ) ,
113+ Err ( e) => Err ( SignOrCreationError :: SignError ( e) )
114+ }
115+ }
116+
24117#[ cfg( feature = "std" ) ]
25118/// Utility to construct an invoice. Generally, unless you want to do something like a custom
26119/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
@@ -192,13 +285,17 @@ where
192285mod test {
193286 use core:: time:: Duration ;
194287 use { Currency , Description , InvoiceDescription } ;
195- use lightning:: ln:: PaymentHash ;
288+ use bitcoin_hashes:: Hash ;
289+ use bitcoin_hashes:: sha256:: Hash as Sha256 ;
290+ use lightning:: chain:: keysinterface:: PhantomKeysManager ;
291+ use lightning:: ln:: { PaymentPreimage , PaymentHash } ;
196292 use lightning:: ln:: channelmanager:: MIN_FINAL_CLTV_EXPIRY ;
197293 use lightning:: ln:: functional_test_utils:: * ;
198294 use lightning:: ln:: features:: InitFeatures ;
199295 use lightning:: ln:: msgs:: ChannelMessageHandler ;
200296 use lightning:: routing:: router:: { PaymentParameters , RouteParameters , find_route} ;
201- use lightning:: util:: events:: MessageSendEventsProvider ;
297+ use lightning:: util:: enforcing_trait_impls:: EnforcingSigner ;
298+ use lightning:: util:: events:: { MessageSendEvent , MessageSendEventsProvider , Event } ;
202299 use lightning:: util:: test_utils;
203300 use utils:: create_invoice_from_channelmanager_and_duration_since_epoch;
204301
@@ -254,4 +351,121 @@ mod test {
254351 let events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
255352 assert_eq ! ( events. len( ) , 2 ) ;
256353 }
354+
355+ #[ test]
356+ #[ cfg( feature = "std" ) ]
357+ fn test_multi_node_receive ( ) {
358+ do_test_multi_node_receive ( true ) ;
359+ do_test_multi_node_receive ( false ) ;
360+ }
361+
362+ #[ cfg( feature = "std" ) ]
363+ fn do_test_multi_node_receive ( user_generated_pmt_hash : bool ) {
364+ let mut chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
365+ let seed_1 = [ 42 as u8 ; 32 ] ;
366+ let seed_2 = [ 43 as u8 ; 32 ] ;
367+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
368+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
369+ chanmon_cfgs[ 2 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
370+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
371+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
372+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
373+ let chan_0_1 = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 100000 , 10001 , InitFeatures :: known ( ) , InitFeatures :: known ( ) ) ;
374+ nodes[ 0 ] . node . handle_channel_update ( & nodes[ 1 ] . node . get_our_node_id ( ) , & chan_0_1. 1 ) ;
375+ nodes[ 1 ] . node . handle_channel_update ( & nodes[ 0 ] . node . get_our_node_id ( ) , & chan_0_1. 0 ) ;
376+ let chan_0_2 = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 2 , 100000 , 10001 , InitFeatures :: known ( ) , InitFeatures :: known ( ) ) ;
377+ nodes[ 0 ] . node . handle_channel_update ( & nodes[ 2 ] . node . get_our_node_id ( ) , & chan_0_2. 1 ) ;
378+ nodes[ 2 ] . node . handle_channel_update ( & nodes[ 0 ] . node . get_our_node_id ( ) , & chan_0_2. 0 ) ;
379+
380+ let payment_amt = 10_000 ;
381+ let ( payment_preimage, payment_hash, payment_secret) = {
382+ if user_generated_pmt_hash {
383+ let payment_preimage = PaymentPreimage ( [ 1 ; 32 ] ) ;
384+ let payment_hash = PaymentHash ( Sha256 :: hash ( & payment_preimage. 0 [ ..] ) . into_inner ( ) ) ;
385+ let payment_secret = nodes[ 1 ] . node . create_inbound_payment_for_hash ( payment_hash, Some ( payment_amt) , 3600 ) . unwrap ( ) ;
386+ ( payment_preimage, payment_hash, payment_secret)
387+ } else {
388+ let ( payment_hash, payment_secret) = nodes[ 1 ] . node . create_inbound_payment ( Some ( payment_amt) , 3600 ) . unwrap ( ) ;
389+ let payment_preimage = nodes[ 1 ] . node . get_payment_preimage ( payment_hash, payment_secret) . unwrap ( ) ;
390+ ( payment_preimage, payment_hash, payment_secret)
391+ }
392+ } ;
393+ let route_hints = vec ! [
394+ nodes[ 1 ] . node. get_phantom_route_hints( ) ,
395+ nodes[ 2 ] . node. get_phantom_route_hints( ) ,
396+ ] ;
397+ let invoice = :: utils:: create_phantom_invoice :: < EnforcingSigner , & test_utils:: TestKeysInterface > ( Some ( payment_amt) , "test" . to_string ( ) , payment_hash, payment_secret, route_hints, & nodes[ 1 ] . keys_manager , Currency :: BitcoinTestnet ) . unwrap ( ) ;
398+
399+ assert_eq ! ( invoice. min_final_cltv_expiry( ) , MIN_FINAL_CLTV_EXPIRY as u64 ) ;
400+ assert_eq ! ( invoice. description( ) , InvoiceDescription :: Direct ( & Description ( "test" . to_string( ) ) ) ) ;
401+ assert_eq ! ( invoice. route_hints( ) . len( ) , 2 ) ;
402+ assert ! ( !invoice. features( ) . unwrap( ) . supports_basic_mpp( ) ) ;
403+
404+ let payment_params = PaymentParameters :: from_node_id ( invoice. recover_payee_pub_key ( ) )
405+ . with_features ( invoice. features ( ) . unwrap ( ) . clone ( ) )
406+ . with_route_hints ( invoice. route_hints ( ) ) ;
407+ let params = RouteParameters {
408+ payment_params,
409+ final_value_msat : invoice. amount_milli_satoshis ( ) . unwrap ( ) ,
410+ final_cltv_expiry_delta : invoice. min_final_cltv_expiry ( ) as u32 ,
411+ } ;
412+ let first_hops = nodes[ 0 ] . node . list_usable_channels ( ) ;
413+ let network_graph = node_cfgs[ 0 ] . network_graph ;
414+ let logger = test_utils:: TestLogger :: new ( ) ;
415+ let scorer = test_utils:: TestScorer :: with_penalty ( 0 ) ;
416+ let route = find_route (
417+ & nodes[ 0 ] . node . get_our_node_id ( ) , & params, network_graph,
418+ Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , & logger, & scorer,
419+ ) . unwrap ( ) ;
420+ let ( payment_event, fwd_idx) = {
421+ let mut payment_hash = PaymentHash ( [ 0 ; 32 ] ) ;
422+ payment_hash. 0 . copy_from_slice ( & invoice. payment_hash ( ) . as_ref ( ) [ 0 ..32 ] ) ;
423+ nodes[ 0 ] . node . send_payment ( & route, payment_hash, & Some ( invoice. payment_secret ( ) . clone ( ) ) ) . unwrap ( ) ;
424+ let mut added_monitors = nodes[ 0 ] . chain_monitor . added_monitors . lock ( ) . unwrap ( ) ;
425+ assert_eq ! ( added_monitors. len( ) , 1 ) ;
426+ added_monitors. clear ( ) ;
427+
428+ let mut events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
429+ assert_eq ! ( events. len( ) , 1 ) ;
430+ let fwd_idx = match events[ 0 ] {
431+ MessageSendEvent :: UpdateHTLCs { node_id, .. } => {
432+ if node_id == nodes[ 1 ] . node . get_our_node_id ( ) {
433+ 1
434+ } else { 2 }
435+ } ,
436+ _ => panic ! ( "Unexpected event" )
437+ } ;
438+ ( SendEvent :: from_event ( events. remove ( 0 ) ) , fwd_idx)
439+ } ;
440+ nodes[ fwd_idx] . node . handle_update_add_htlc ( & nodes[ 0 ] . node . get_our_node_id ( ) , & payment_event. msgs [ 0 ] ) ;
441+ commitment_signed_dance ! ( nodes[ fwd_idx] , nodes[ 0 ] , & payment_event. commitment_msg, false , true ) ;
442+
443+ // Note that we have to "forward pending HTLCs" twice before we see the PaymentReceived as
444+ // this "emulates" the payment taking two hops, providing some privacy to make phantom node
445+ // payments "look real" by taking more time.
446+ expect_pending_htlcs_forwardable_ignore ! ( nodes[ fwd_idx] ) ;
447+ nodes[ fwd_idx] . node . process_pending_htlc_forwards ( ) ;
448+ expect_pending_htlcs_forwardable_ignore ! ( nodes[ fwd_idx] ) ;
449+ nodes[ fwd_idx] . node . process_pending_htlc_forwards ( ) ;
450+
451+ let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some ( payment_preimage) } ;
452+ expect_payment_received ! ( & nodes[ fwd_idx] , payment_hash, payment_secret, payment_amt, payment_preimage_opt) ;
453+ do_claim_payment_along_route ( & nodes[ 0 ] , & vec ! ( & vec!( & nodes[ fwd_idx] ) [ ..] ) , false , payment_preimage) ;
454+ let events = nodes[ 0 ] . node . get_and_clear_pending_events ( ) ;
455+ assert_eq ! ( events. len( ) , 2 ) ;
456+ match events[ 0 ] {
457+ Event :: PaymentSent { payment_preimage : ref ev_preimage, payment_hash : ref ev_hash, ref fee_paid_msat, .. } => {
458+ assert_eq ! ( payment_preimage, * ev_preimage) ;
459+ assert_eq ! ( payment_hash, * ev_hash) ;
460+ assert_eq ! ( fee_paid_msat, & Some ( 0 ) ) ;
461+ } ,
462+ _ => panic ! ( "Unexpected event" )
463+ }
464+ match events[ 1 ] {
465+ Event :: PaymentPathSuccessful { payment_hash : hash, .. } => {
466+ assert_eq ! ( hash, Some ( payment_hash) ) ;
467+ } ,
468+ _ => panic ! ( "Unexpected event" )
469+ }
470+ }
257471}
0 commit comments