33use { CreationError , Currency , DEFAULT_EXPIRY_TIME , Invoice , InvoiceBuilder , SignOrCreationError } ;
44use payment:: { Payer , Router } ;
55
6+ use crate :: { prelude:: * , Description , InvoiceDescription } ;
67use bech32:: ToBase32 ;
78use bitcoin_hashes:: { Hash , sha256} ;
8- use crate :: prelude:: * ;
99use lightning:: chain;
1010use lightning:: chain:: chaininterface:: { BroadcasterInterface , FeeEstimator } ;
1111use lightning:: chain:: keysinterface:: { Recipient , KeysInterface , Sign } ;
@@ -50,18 +50,100 @@ use sync::Mutex;
5050/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
5151/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
5252pub 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
55- ) -> Result < Invoice , SignOrCreationError < ( ) > > where K :: Target : KeysInterface {
56- if phantom_route_hints. len ( ) == 0 {
57- return Err ( SignOrCreationError :: CreationError ( CreationError :: MissingRouteHints ) )
58- }
59- let mut invoice = InvoiceBuilder :: new ( network)
60- . description ( description)
61- . current_timestamp ( )
62- . payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
63- . payment_secret ( payment_secret)
64- . min_final_cltv_expiry ( MIN_FINAL_CLTV_EXPIRY . into ( ) ) ;
53+ amt_msat : Option < u64 > ,
54+ description : String ,
55+ payment_hash : PaymentHash ,
56+ payment_secret : PaymentSecret ,
57+ phantom_route_hints : Vec < PhantomRouteHints > ,
58+ keys_manager : K ,
59+ network : Currency ,
60+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
61+ where
62+ K :: Target : KeysInterface ,
63+ {
64+ let description = Description :: new ( description) . map_err ( SignOrCreationError :: CreationError ) ?;
65+ let description = InvoiceDescription :: Direct ( & description, ) ;
66+ _create_phantom_invoice :: < Signer , K > (
67+ amt_msat, description, payment_hash, payment_secret, phantom_route_hints, keys_manager, network,
68+ )
69+ }
70+
71+ #[ cfg( feature = "std" ) ]
72+ /// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
73+ /// See [`PhantomKeysManager`] for more information on phantom node payments.
74+ ///
75+ /// `phantom_route_hints` parameter:
76+ /// * Contains channel info for all nodes participating in the phantom invoice
77+ /// * Entries are retrieved from a call to [`ChannelManager::get_phantom_route_hints`] on each
78+ /// participating node
79+ /// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
80+ /// updated when a channel becomes disabled or closes
81+ /// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
82+ /// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
83+ /// down
84+ ///
85+ /// `description` will be SHA-256 hashed and transformed in `description_hash` on the invoice
86+ ///
87+ /// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
88+ /// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any
89+ /// participating node.
90+ ///
91+ /// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom
92+ /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
93+ /// requirement).
94+ ///
95+ /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
96+ /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
97+ /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
98+ pub fn create_phantom_invoice_with_description_hash < Signer : Sign , K : Deref > (
99+ amt_msat : Option < u64 > ,
100+ description : String ,
101+ payment_hash : PaymentHash ,
102+ payment_secret : PaymentSecret ,
103+ phantom_route_hints : Vec < PhantomRouteHints > ,
104+ keys_manager : K ,
105+ network : Currency ,
106+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
107+ where
108+ K :: Target : KeysInterface ,
109+ {
110+ _create_phantom_invoice :: < Signer , K > (
111+ amt_msat,
112+ InvoiceDescription :: Hash ( & crate :: Sha256 ( Hash :: hash ( description. as_bytes ( ) ) ) ) ,
113+ payment_hash, payment_secret, phantom_route_hints, keys_manager, network,
114+ )
115+ }
116+
117+ #[ cfg( feature = "std" ) ]
118+ fn _create_phantom_invoice < Signer : Sign , K : Deref > (
119+ amt_msat : Option < u64 > ,
120+ description : InvoiceDescription ,
121+ payment_hash : PaymentHash ,
122+ payment_secret : PaymentSecret ,
123+ phantom_route_hints : Vec < PhantomRouteHints > ,
124+ keys_manager : K ,
125+ network : Currency ,
126+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
127+ where
128+ K :: Target : KeysInterface ,
129+ {
130+ if phantom_route_hints. len ( ) == 0 {
131+ return Err ( SignOrCreationError :: CreationError (
132+ CreationError :: MissingRouteHints ,
133+ ) ) ;
134+ }
135+ let invoice = match description {
136+ InvoiceDescription :: Direct ( description) => {
137+ InvoiceBuilder :: new ( network) . description ( description. 0 . clone ( ) )
138+ }
139+ InvoiceDescription :: Hash ( hash) => InvoiceBuilder :: new ( network) . description_hash ( hash. 0 ) ,
140+ } ;
141+
142+ let mut invoice = invoice
143+ . current_timestamp ( )
144+ . payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
145+ . payment_secret ( payment_secret)
146+ . min_final_cltv_expiry ( MIN_FINAL_CLTV_EXPIRY . into ( ) ) ;
65147 if let Some ( amt) = amt_msat {
66148 invoice = invoice. amount_milli_satoshis ( amt) ;
67149 }
@@ -126,15 +208,81 @@ where
126208 let duration = SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH )
127209 . expect ( "for the foreseeable future this shouldn't happen" ) ;
128210 create_invoice_from_channelmanager_and_duration_since_epoch (
129- channelmanager,
130- keys_manager,
131- network,
132- amt_msat,
133- description,
134- duration
211+ channelmanager, keys_manager, network, amt_msat, description, duration
135212 )
136213}
137214
215+ #[ cfg( feature = "std" ) ]
216+ /// Utility to construct an invoice. Generally, unless you want to do something like a custom
217+ /// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
218+ /// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
219+ /// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
220+ /// that the payment secret is valid when the invoice is paid.
221+ /// Use this variant if you want to hash the description and pass a `payment_hash` instead
222+ pub fn create_invoice_from_channelmanager_with_description_hash <
223+ Signer : Sign ,
224+ M : Deref ,
225+ T : Deref ,
226+ K : Deref ,
227+ F : Deref ,
228+ L : Deref ,
229+ > (
230+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > ,
231+ keys_manager : K ,
232+ network : Currency ,
233+ amt_msat : Option < u64 > ,
234+ description : String ,
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+ use std:: time:: SystemTime ;
244+
245+ let duration = SystemTime :: now ( )
246+ . duration_since ( SystemTime :: UNIX_EPOCH )
247+ . expect ( "for the foreseeable future this shouldn't happen" ) ;
248+
249+ create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch (
250+ channelmanager, keys_manager, network, amt_msat, description, duration,
251+ )
252+ }
253+
254+ /// See [`create_invoice_from_channelmanager_with_description_hash`]
255+ /// This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not
256+ /// available and the current time is supplied by the caller.
257+ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch <
258+ Signer : Sign ,
259+ M : Deref ,
260+ T : Deref ,
261+ K : Deref ,
262+ F : Deref ,
263+ L : Deref ,
264+ > (
265+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > ,
266+ keys_manager : K ,
267+ network : Currency ,
268+ amt_msat : Option < u64 > ,
269+ description : String ,
270+ duration_since_epoch : Duration ,
271+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
272+ where
273+ M :: Target : chain:: Watch < Signer > ,
274+ T :: Target : BroadcasterInterface ,
275+ K :: Target : KeysInterface < Signer = Signer > ,
276+ F :: Target : FeeEstimator ,
277+ L :: Target : Logger ,
278+ {
279+ _create_invoice_from_channelmanager_and_duration_since_epoch (
280+ channelmanager, keys_manager, network, amt_msat,
281+ InvoiceDescription :: Hash ( & crate :: Sha256 ( Hash :: hash ( description. as_bytes ( ) ) ) ) ,
282+ duration_since_epoch,
283+ )
284+ }
285+
138286/// See [`create_invoice_from_channelmanager`]
139287/// This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not
140288/// available and the current time is supplied by the caller.
@@ -148,26 +296,64 @@ where
148296 K :: Target : KeysInterface < Signer = Signer > ,
149297 F :: Target : FeeEstimator ,
150298 L :: Target : Logger ,
299+ {
300+ _create_invoice_from_channelmanager_and_duration_since_epoch (
301+ channelmanager, keys_manager, network, amt_msat,
302+ InvoiceDescription :: Direct (
303+ & Description :: new ( description) . map_err ( SignOrCreationError :: CreationError ) ?,
304+ ) ,
305+ duration_since_epoch,
306+ )
307+ }
308+
309+ fn _create_invoice_from_channelmanager_and_duration_since_epoch <
310+ Signer : Sign ,
311+ M : Deref ,
312+ T : Deref ,
313+ K : Deref ,
314+ F : Deref ,
315+ L : Deref ,
316+ > (
317+ channelmanager : & ChannelManager < Signer , M , T , K , F , L > ,
318+ keys_manager : K ,
319+ network : Currency ,
320+ amt_msat : Option < u64 > ,
321+ description : InvoiceDescription ,
322+ duration_since_epoch : Duration ,
323+ ) -> Result < Invoice , SignOrCreationError < ( ) > >
324+ where
325+ M :: Target : chain:: Watch < Signer > ,
326+ T :: Target : BroadcasterInterface ,
327+ K :: Target : KeysInterface < Signer = Signer > ,
328+ F :: Target : FeeEstimator ,
329+ L :: Target : Logger ,
151330{
152331 let route_hints = filter_channels ( channelmanager. list_usable_channels ( ) , amt_msat) ;
153332
154- // `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
155- // supply.
156- let ( payment_hash, payment_secret) = channelmanager. create_inbound_payment (
157- amt_msat, DEFAULT_EXPIRY_TIME . try_into ( ) . unwrap ( ) )
158- . map_err ( |( ) | SignOrCreationError :: CreationError ( CreationError :: InvalidAmount ) ) ?;
159- let our_node_pubkey = channelmanager. get_our_node_id ( ) ;
160- let mut invoice = InvoiceBuilder :: new ( network)
161- . description ( description)
162- . duration_since_epoch ( duration_since_epoch)
163- . payee_pub_key ( our_node_pubkey)
164- . payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
165- . payment_secret ( payment_secret)
166- . basic_mpp ( )
167- . min_final_cltv_expiry ( MIN_FINAL_CLTV_EXPIRY . into ( ) ) ;
168- if let Some ( amt) = amt_msat {
169- invoice = invoice. amount_milli_satoshis ( amt) ;
170- }
333+ // `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
334+ // supply.
335+ let ( payment_hash, payment_secret) = channelmanager
336+ . create_inbound_payment ( amt_msat, DEFAULT_EXPIRY_TIME . try_into ( ) . unwrap ( ) )
337+ . map_err ( |( ) | SignOrCreationError :: CreationError ( CreationError :: InvalidAmount ) ) ?;
338+ let our_node_pubkey = channelmanager. get_our_node_id ( ) ;
339+
340+ let invoice = match description {
341+ InvoiceDescription :: Direct ( description) => {
342+ InvoiceBuilder :: new ( network) . description ( description. 0 . clone ( ) )
343+ }
344+ InvoiceDescription :: Hash ( hash) => InvoiceBuilder :: new ( network) . description_hash ( hash. 0 ) ,
345+ } ;
346+
347+ let mut invoice = invoice
348+ . duration_since_epoch ( duration_since_epoch)
349+ . payee_pub_key ( our_node_pubkey)
350+ . payment_hash ( Hash :: from_slice ( & payment_hash. 0 ) . unwrap ( ) )
351+ . payment_secret ( payment_secret)
352+ . basic_mpp ( )
353+ . min_final_cltv_expiry ( MIN_FINAL_CLTV_EXPIRY . into ( ) ) ;
354+ if let Some ( amt) = amt_msat {
355+ invoice = invoice. amount_milli_satoshis ( amt) ;
356+ }
171357 for hint in route_hints {
172358 invoice = invoice. private_route ( hint) ;
173359 }
@@ -1015,4 +1201,81 @@ mod test {
10151201 }
10161202 assert ! ( chan_ids_to_match. is_empty( ) , "Unmatched short channel ids: {:?}" , chan_ids_to_match) ;
10171203 }
1204+
1205+ #[ test]
1206+ fn test_create_invoice_with_description_hash ( ) {
1207+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
1208+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
1209+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
1210+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
1211+
1212+ let invoice = :: utils:: create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch (
1213+ & nodes[ 1 ] . node , nodes[ 1 ] . keys_manager , Currency :: BitcoinTestnet , Some ( 10_000 ) ,
1214+ "Testing description_hash" . to_string ( ) , Duration :: from_secs ( 1234567 ) ,
1215+ )
1216+ . unwrap ( ) ;
1217+ assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 100_000 ) ) ;
1218+ assert_eq ! (
1219+ invoice. min_final_cltv_expiry( ) ,
1220+ MIN_FINAL_CLTV_EXPIRY as u64
1221+ ) ;
1222+ assert_eq ! (
1223+ invoice. description( ) ,
1224+ InvoiceDescription :: Hash ( & crate :: Sha256 ( Sha256 :: hash(
1225+ "Testing description_hash" . as_bytes( )
1226+ ) ) )
1227+ ) ;
1228+ }
1229+
1230+
1231+ #[ test]
1232+ #[ cfg( feature = "std" ) ]
1233+ fn create_phantom_invoice_with_description_hash ( ) {
1234+ let mut chanmon_cfgs = create_chanmon_cfgs ( 3 ) ;
1235+ let seed_1 = [ 42 as u8 ; 32 ] ;
1236+ let seed_2 = [ 43 as u8 ; 32 ] ;
1237+ let cross_node_seed = [ 44 as u8 ; 32 ] ;
1238+ chanmon_cfgs[ 1 ] . keys_manager . backing = PhantomKeysManager :: new ( & seed_1, 43 , 44 , & cross_node_seed) ;
1239+ chanmon_cfgs[ 2 ] . keys_manager . backing =PhantomKeysManager :: new ( & seed_2, 43 , 44 , & cross_node_seed) ;
1240+ let node_cfgs = create_node_cfgs ( 3 , & chanmon_cfgs) ;
1241+ let node_chanmgrs = create_node_chanmgrs ( 3 , & node_cfgs, & [ None , None , None ] ) ;
1242+ let nodes = create_network ( 3 , & node_cfgs, & node_chanmgrs) ;
1243+ let chan_0_1 = create_announced_chan_between_nodes_with_value (
1244+ & nodes, 0 , 1 , 100000 , 10001 , InitFeatures :: known ( ) , InitFeatures :: known ( ) ,
1245+ ) ;
1246+ nodes[ 0 ] . node . handle_channel_update ( & nodes[ 1 ] . node . get_our_node_id ( ) , & chan_0_1. 1 ) ;
1247+ nodes[ 1 ] . node . handle_channel_update ( & nodes[ 0 ] . node . get_our_node_id ( ) , & chan_0_1. 0 ) ;
1248+ let chan_0_2 = create_announced_chan_between_nodes_with_value (
1249+ & nodes, 0 , 2 , 100000 , 10001 , InitFeatures :: known ( ) , InitFeatures :: known ( ) ,
1250+ ) ;
1251+ nodes[ 0 ] . node . handle_channel_update ( & nodes[ 2 ] . node . get_our_node_id ( ) , & chan_0_2. 1 ) ;
1252+ nodes[ 2 ] . node . handle_channel_update ( & nodes[ 0 ] . node . get_our_node_id ( ) , & chan_0_2. 0 ) ;
1253+
1254+ let payment_amt = 20_000 ;
1255+ let ( payment_hash, payment_secret) = nodes[ 1 ] . node . create_inbound_payment ( Some ( payment_amt) , 3600 ) . unwrap ( ) ;
1256+ let route_hints = vec ! [
1257+ nodes[ 1 ] . node. get_phantom_route_hints( ) ,
1258+ nodes[ 2 ] . node. get_phantom_route_hints( ) ,
1259+ ] ;
1260+ let invoice = :: utils:: create_phantom_invoice_with_description_hash :: <
1261+ EnforcingSigner ,
1262+ & test_utils:: TestKeysInterface ,
1263+ > (
1264+ Some ( payment_amt) , "Description hash phantom invoice" . to_string ( ) , payment_hash,
1265+ payment_secret, route_hints, & nodes[ 1 ] . keys_manager , Currency :: BitcoinTestnet ,
1266+ )
1267+ . unwrap ( ) ;
1268+
1269+ assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 200_000 ) ) ;
1270+ assert_eq ! (
1271+ invoice. min_final_cltv_expiry( ) ,
1272+ MIN_FINAL_CLTV_EXPIRY as u64
1273+ ) ;
1274+ assert_eq ! (
1275+ invoice. description( ) ,
1276+ InvoiceDescription :: Hash ( & crate :: Sha256 ( Sha256 :: hash(
1277+ "Description hash phantom invoice" . as_bytes( )
1278+ ) ) )
1279+ ) ;
1280+ }
10181281}
0 commit comments