3
3
use { CreationError , Currency , DEFAULT_EXPIRY_TIME , Invoice , InvoiceBuilder , SignOrCreationError } ;
4
4
use payment:: { Payer , Router } ;
5
5
6
+ use crate :: { prelude:: * , Description , InvoiceDescription , Sha256 } ;
6
7
use bech32:: ToBase32 ;
7
8
use bitcoin_hashes:: { Hash , sha256} ;
8
- use crate :: prelude:: * ;
9
9
use lightning:: chain;
10
10
use lightning:: chain:: chaininterface:: { BroadcasterInterface , FeeEstimator } ;
11
11
use lightning:: chain:: keysinterface:: { Recipient , KeysInterface , Sign } ;
@@ -50,14 +50,81 @@ use sync::Mutex;
50
50
/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
51
51
/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
52
52
pub fn create_phantom_invoice < Signer : Sign , K : Deref > (
53
- amt_msat : Option < u64 > , description : String , payment_hash : PaymentHash , payment_secret :
54
- PaymentSecret , phantom_route_hints : Vec < PhantomRouteHints > , keys_manager : K , network : Currency
53
+ amt_msat : Option < u64 > , description : String , payment_hash : PaymentHash , payment_secret : PaymentSecret ,
54
+ phantom_route_hints : Vec < PhantomRouteHints > , keys_manager : K , network : Currency ,
55
55
) -> Result < Invoice , SignOrCreationError < ( ) > > where K :: Target : KeysInterface {
56
+ let description = Description :: new ( description) . map_err ( SignOrCreationError :: CreationError ) ?;
57
+ let description = InvoiceDescription :: Direct ( & description, ) ;
58
+ _create_phantom_invoice :: < Signer , K > (
59
+ amt_msat, description, payment_hash, payment_secret, phantom_route_hints, keys_manager, network,
60
+ )
61
+ }
62
+
63
+ #[ cfg( feature = "std" ) ]
64
+ /// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
65
+ /// See [`PhantomKeysManager`] for more information on phantom node payments.
66
+ ///
67
+ /// `phantom_route_hints` parameter:
68
+ /// * Contains channel info for all nodes participating in the phantom invoice
69
+ /// * Entries are retrieved from a call to [`ChannelManager::get_phantom_route_hints`] on each
70
+ /// participating node
71
+ /// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
72
+ /// updated when a channel becomes disabled or closes
73
+ /// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
74
+ /// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
75
+ /// down
76
+ ///
77
+ /// `description_hash` is a SHA-256 hash of the description text
78
+ ///
79
+ /// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
80
+ /// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any
81
+ /// participating node.
82
+ ///
83
+ /// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom
84
+ /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
85
+ /// requirement).
86
+ ///
87
+ /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
88
+ /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
89
+ /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
90
+ pub fn create_phantom_invoice_with_description_hash < Signer : Sign , K : Deref > (
91
+ amt_msat : Option < u64 > , description_hash : Sha256 , payment_hash : PaymentHash ,
92
+ payment_secret : PaymentSecret , phantom_route_hints : Vec < PhantomRouteHints > ,
93
+ keys_manager : K , network : Currency ,
94
+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
95
+ where
96
+ K :: Target : KeysInterface ,
97
+ {
98
+
99
+ _create_phantom_invoice :: < Signer , K > (
100
+ amt_msat,
101
+ InvoiceDescription :: Hash ( & description_hash) ,
102
+ payment_hash, payment_secret, phantom_route_hints, keys_manager, network,
103
+ )
104
+ }
105
+
106
+ #[ cfg( feature = "std" ) ]
107
+ fn _create_phantom_invoice < Signer : Sign , K : Deref > (
108
+ amt_msat : Option < u64 > , description : InvoiceDescription , payment_hash : PaymentHash ,
109
+ payment_secret : PaymentSecret , phantom_route_hints : Vec < PhantomRouteHints > ,
110
+ keys_manager : K , network : Currency ,
111
+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
112
+ where
113
+ K :: Target : KeysInterface ,
114
+ {
56
115
if phantom_route_hints. len ( ) == 0 {
57
- return Err ( SignOrCreationError :: CreationError ( CreationError :: MissingRouteHints ) )
116
+ return Err ( SignOrCreationError :: CreationError (
117
+ CreationError :: MissingRouteHints ,
118
+ ) ) ;
58
119
}
59
- let mut invoice = InvoiceBuilder :: new ( network)
60
- . description ( description)
120
+ let invoice = match description {
121
+ InvoiceDescription :: Direct ( description) => {
122
+ InvoiceBuilder :: new ( network) . description ( description. 0 . clone ( ) )
123
+ }
124
+ InvoiceDescription :: Hash ( hash) => InvoiceBuilder :: new ( network) . description_hash ( hash. 0 ) ,
125
+ } ;
126
+
127
+ let mut invoice = invoice
61
128
. current_timestamp ( )
62
129
. payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
63
130
. payment_secret ( payment_secret)
@@ -126,12 +193,57 @@ where
126
193
let duration = SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH )
127
194
. expect ( "for the foreseeable future this shouldn't happen" ) ;
128
195
create_invoice_from_channelmanager_and_duration_since_epoch (
129
- channelmanager,
130
- keys_manager,
131
- network,
132
- amt_msat,
133
- description,
134
- duration
196
+ channelmanager, keys_manager, network, amt_msat, description, duration
197
+ )
198
+ }
199
+
200
+ #[ cfg( feature = "std" ) ]
201
+ /// Utility to construct an invoice. Generally, unless you want to do something like a custom
202
+ /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
203
+ /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
204
+ /// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
205
+ /// that the payment secret is valid when the invoice is paid.
206
+ /// Use this variant if you want to pass the `description_hash` to the invoice.
207
+ pub fn create_invoice_from_channelmanager_with_description_hash < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > (
208
+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > , keys_manager : K , network : Currency ,
209
+ amt_msat : Option < u64 > , description_hash : Sha256 ,
210
+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
211
+ where
212
+ M :: Target : chain:: Watch < Signer > ,
213
+ T :: Target : BroadcasterInterface ,
214
+ K :: Target : KeysInterface < Signer = Signer > ,
215
+ F :: Target : FeeEstimator ,
216
+ L :: Target : Logger ,
217
+ {
218
+ use std:: time:: SystemTime ;
219
+
220
+ let duration = SystemTime :: now ( )
221
+ . duration_since ( SystemTime :: UNIX_EPOCH )
222
+ . expect ( "for the foreseeable future this shouldn't happen" ) ;
223
+
224
+ create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch (
225
+ channelmanager, keys_manager, network, amt_msat, description_hash, duration,
226
+ )
227
+ }
228
+
229
+ /// See [`create_invoice_from_channelmanager_with_description_hash`]
230
+ /// This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not
231
+ /// available and the current time is supplied by the caller.
232
+ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > (
233
+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > , keys_manager : K , network : Currency ,
234
+ amt_msat : Option < u64 > , description_hash : Sha256 , duration_since_epoch : Duration ,
235
+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
236
+ where
237
+ M :: Target : chain:: Watch < Signer > ,
238
+ T :: Target : BroadcasterInterface ,
239
+ K :: Target : KeysInterface < Signer = Signer > ,
240
+ F :: Target : FeeEstimator ,
241
+ L :: Target : Logger ,
242
+ {
243
+ _create_invoice_from_channelmanager_and_duration_since_epoch (
244
+ channelmanager, keys_manager, network, amt_msat,
245
+ InvoiceDescription :: Hash ( & description_hash) ,
246
+ duration_since_epoch,
135
247
)
136
248
}
137
249
@@ -142,6 +254,26 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch<Signer: Sign,
142
254
channelmanager : & ChannelManager < Signer , M , T , K , F , L > , keys_manager : K , network : Currency ,
143
255
amt_msat : Option < u64 > , description : String , duration_since_epoch : Duration ,
144
256
) -> Result < Invoice , SignOrCreationError < ( ) > >
257
+ where
258
+ M :: Target : chain:: Watch < Signer > ,
259
+ T :: Target : BroadcasterInterface ,
260
+ K :: Target : KeysInterface < Signer = Signer > ,
261
+ F :: Target : FeeEstimator ,
262
+ L :: Target : Logger ,
263
+ {
264
+ _create_invoice_from_channelmanager_and_duration_since_epoch (
265
+ channelmanager, keys_manager, network, amt_msat,
266
+ InvoiceDescription :: Direct (
267
+ & Description :: new ( description) . map_err ( SignOrCreationError :: CreationError ) ?,
268
+ ) ,
269
+ duration_since_epoch,
270
+ )
271
+ }
272
+
273
+ fn _create_invoice_from_channelmanager_and_duration_since_epoch < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > (
274
+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > , keys_manager : K , network : Currency ,
275
+ amt_msat : Option < u64 > , description : InvoiceDescription , duration_since_epoch : Duration ,
276
+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
145
277
where
146
278
M :: Target : chain:: Watch < Signer > ,
147
279
T :: Target : BroadcasterInterface ,
@@ -153,12 +285,19 @@ where
153
285
154
286
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
155
287
// supply.
156
- let ( payment_hash, payment_secret) = channelmanager. create_inbound_payment (
157
- amt_msat, DEFAULT_EXPIRY_TIME . try_into ( ) . unwrap ( ) )
288
+ let ( payment_hash, payment_secret) = channelmanager
289
+ . create_inbound_payment ( amt_msat, DEFAULT_EXPIRY_TIME . try_into ( ) . unwrap ( ) )
158
290
. map_err ( |( ) | SignOrCreationError :: CreationError ( CreationError :: InvalidAmount ) ) ?;
159
291
let our_node_pubkey = channelmanager. get_our_node_id ( ) ;
160
- let mut invoice = InvoiceBuilder :: new ( network)
161
- . description ( description)
292
+
293
+ let invoice = match description {
294
+ InvoiceDescription :: Direct ( description) => {
295
+ InvoiceBuilder :: new ( network) . description ( description. 0 . clone ( ) )
296
+ }
297
+ InvoiceDescription :: Hash ( hash) => InvoiceBuilder :: new ( network) . description_hash ( hash. 0 ) ,
298
+ } ;
299
+
300
+ let mut invoice = invoice
162
301
. duration_since_epoch ( duration_since_epoch)
163
302
. payee_pub_key ( our_node_pubkey)
164
303
. payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
@@ -407,6 +546,22 @@ mod test {
407
546
assert_eq ! ( events. len( ) , 2 ) ;
408
547
}
409
548
549
+ #[ test]
550
+ fn test_create_invoice_with_description_hash ( ) {
551
+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
552
+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
553
+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
554
+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
555
+ let description_hash = crate :: Sha256 ( Hash :: hash ( "Testing description_hash" . as_bytes ( ) ) ) ;
556
+ let invoice = :: utils:: create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch (
557
+ & nodes[ 1 ] . node , nodes[ 1 ] . keys_manager , Currency :: BitcoinTestnet , Some ( 10_000 ) ,
558
+ description_hash, Duration :: from_secs ( 1234567 ) ,
559
+ ) . unwrap ( ) ;
560
+ assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 100_000 ) ) ;
561
+ assert_eq ! ( invoice. min_final_cltv_expiry( ) , MIN_FINAL_CLTV_EXPIRY as u64 ) ;
562
+ assert_eq ! ( invoice. description( ) , InvoiceDescription :: Hash ( & crate :: Sha256 ( Sha256 :: hash( "Testing description_hash" . as_bytes( ) ) ) ) ) ;
563
+ }
564
+
410
565
#[ test]
411
566
fn test_hints_includes_single_channels_to_nodes ( ) {
412
567
let chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
@@ -688,6 +843,34 @@ mod test {
688
843
}
689
844
}
690
845
846
+ #[ test]
847
+ #[ cfg( feature = "std" ) ]
848
+ fn create_phantom_invoice_with_description_hash ( ) {
849
+ let mut chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
850
+ let seed_1 = [ 42 as u8 ; 32 ] ;
851
+ let seed_2 = [ 43 as u8 ; 32 ] ;
852
+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
853
+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
854
+ chanmon_cfgs[ 2 ] . keys_manager . backing =PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
855
+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
856
+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
857
+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
858
+
859
+ let payment_amt = 20_000 ;
860
+ let ( payment_hash, payment_secret) = nodes[ 1 ] . node . create_inbound_payment ( Some ( payment_amt) , 3600 ) . unwrap ( ) ;
861
+ let route_hints = vec ! [
862
+ nodes[ 1 ] . node. get_phantom_route_hints( ) ,
863
+ nodes[ 2 ] . node. get_phantom_route_hints( ) ,
864
+ ] ;
865
+
866
+ let description_hash = crate :: Sha256 ( Hash :: hash ( "Description hash phantom invoice" . as_bytes ( ) ) ) ;
867
+ let invoice = :: utils:: create_phantom_invoice_with_description_hash :: < EnforcingSigner , & test_utils:: TestKeysInterface > ( Some ( payment_amt) , description_hash, payment_hash, payment_secret, route_hints, & nodes[ 1 ] . keys_manager , Currency :: BitcoinTestnet ) . unwrap ( ) ;
868
+
869
+ assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 200_000 ) ) ;
870
+ assert_eq ! ( invoice. min_final_cltv_expiry( ) , MIN_FINAL_CLTV_EXPIRY as u64 ) ;
871
+ assert_eq ! ( invoice. description( ) , InvoiceDescription :: Hash ( & crate :: Sha256 ( Sha256 :: hash( "Description hash phantom invoice" . as_bytes( ) ) ) ) ) ;
872
+ }
873
+
691
874
#[ test]
692
875
#[ cfg( feature = "std" ) ]
693
876
fn test_multi_node_hints_includes_single_channels_to_participating_nodes ( ) {
0 commit comments