@@ -10,7 +10,7 @@ use lightning::chain;
10
10
use lightning:: chain:: chaininterface:: { BroadcasterInterface , FeeEstimator } ;
11
11
use lightning:: chain:: keysinterface:: { Recipient , KeysInterface , Sign } ;
12
12
use 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 } ;
14
14
use lightning:: ln:: msgs:: LightningError ;
15
15
use lightning:: routing:: scoring:: Score ;
16
16
use lightning:: routing:: network_graph:: { NetworkGraph , RoutingFees } ;
@@ -21,6 +21,99 @@ use core::convert::TryInto;
21
21
use core:: ops:: Deref ;
22
22
use core:: time:: Duration ;
23
23
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
+
24
117
#[ cfg( feature = "std" ) ]
25
118
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
26
119
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
@@ -192,13 +285,17 @@ where
192
285
mod test {
193
286
use core:: time:: Duration ;
194
287
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 } ;
196
292
use lightning:: ln:: channelmanager:: MIN_FINAL_CLTV_EXPIRY ;
197
293
use lightning:: ln:: functional_test_utils:: * ;
198
294
use lightning:: ln:: features:: InitFeatures ;
199
295
use lightning:: ln:: msgs:: ChannelMessageHandler ;
200
296
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 } ;
202
299
use lightning:: util:: test_utils;
203
300
use utils:: create_invoice_from_channelmanager_and_duration_since_epoch;
204
301
@@ -254,4 +351,121 @@ mod test {
254
351
let events = nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
255
352
assert_eq ! ( events. len( ) , 2 ) ;
256
353
}
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
+ }
257
471
}
0 commit comments