Skip to content

Commit 2ce3832

Browse files
committed
Create normal/phantom invoice with description hash
1 parent 699ac83 commit 2ce3832

File tree

1 file changed

+282
-17
lines changed

1 file changed

+282
-17
lines changed

lightning-invoice/src/utils.rs

Lines changed: 282 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
use {CreationError, Currency, DEFAULT_EXPIRY_TIME, Invoice, InvoiceBuilder, SignOrCreationError};
44
use payment::{Payer, Router};
55

6+
use crate::{prelude::*, Description, InvoiceDescription, Sha256};
67
use bech32::ToBase32;
78
use bitcoin_hashes::{Hash, sha256};
8-
use crate::prelude::*;
99
use lightning::chain;
1010
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1111
use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign};
@@ -50,14 +50,97 @@ 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
5252
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
55-
) -> Result<Invoice, SignOrCreationError<()>> where K::Target: KeysInterface {
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_hash` is a SHA-256 hash of the description text
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_hash: Sha256,
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+
111+
_create_phantom_invoice::<Signer, K>(
112+
amt_msat,
113+
InvoiceDescription::Hash(&description_hash),
114+
payment_hash, payment_secret, phantom_route_hints, keys_manager, network,
115+
)
116+
}
117+
118+
#[cfg(feature = "std")]
119+
fn _create_phantom_invoice<Signer: Sign, K: Deref>(
120+
amt_msat: Option<u64>,
121+
description: InvoiceDescription,
122+
payment_hash: PaymentHash,
123+
payment_secret: PaymentSecret,
124+
phantom_route_hints: Vec<PhantomRouteHints>,
125+
keys_manager: K,
126+
network: Currency,
127+
) -> Result<Invoice, SignOrCreationError<()>>
128+
where
129+
K::Target: KeysInterface,
130+
{
56131
if phantom_route_hints.len() == 0 {
57-
return Err(SignOrCreationError::CreationError(CreationError::MissingRouteHints))
132+
return Err(SignOrCreationError::CreationError(
133+
CreationError::MissingRouteHints,
134+
));
58135
}
59-
let mut invoice = InvoiceBuilder::new(network)
60-
.description(description)
136+
let invoice = match description {
137+
InvoiceDescription::Direct(description) => {
138+
InvoiceBuilder::new(network).description(description.0.clone())
139+
}
140+
InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
141+
};
142+
143+
let mut invoice = invoice
61144
.current_timestamp()
62145
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
63146
.payment_secret(payment_secret)
@@ -126,12 +209,78 @@ where
126209
let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
127210
.expect("for the foreseeable future this shouldn't happen");
128211
create_invoice_from_channelmanager_and_duration_since_epoch(
129-
channelmanager,
130-
keys_manager,
131-
network,
132-
amt_msat,
133-
description,
134-
duration
212+
channelmanager, keys_manager, network, amt_msat, description, duration
213+
)
214+
}
215+
216+
#[cfg(feature = "std")]
217+
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
218+
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
219+
/// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
220+
/// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
221+
/// that the payment secret is valid when the invoice is paid.
222+
/// Use this variant if you want to pass the `description_hash` to the invoice.
223+
pub fn create_invoice_from_channelmanager_with_description_hash<
224+
Signer: Sign,
225+
M: Deref,
226+
T: Deref,
227+
K: Deref,
228+
F: Deref,
229+
L: Deref,
230+
>(
231+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>,
232+
keys_manager: K,
233+
network: Currency,
234+
amt_msat: Option<u64>,
235+
description_hash: Sha256,
236+
) -> Result<Invoice, SignOrCreationError<()>>
237+
where
238+
M::Target: chain::Watch<Signer>,
239+
T::Target: BroadcasterInterface,
240+
K::Target: KeysInterface<Signer = Signer>,
241+
F::Target: FeeEstimator,
242+
L::Target: Logger,
243+
{
244+
use std::time::SystemTime;
245+
246+
let duration = SystemTime::now()
247+
.duration_since(SystemTime::UNIX_EPOCH)
248+
.expect("for the foreseeable future this shouldn't happen");
249+
250+
create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
251+
channelmanager, keys_manager, network, amt_msat, description_hash, duration,
252+
)
253+
}
254+
255+
/// See [`create_invoice_from_channelmanager_with_description_hash`]
256+
/// This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not
257+
/// available and the current time is supplied by the caller.
258+
pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch<
259+
Signer: Sign,
260+
M: Deref,
261+
T: Deref,
262+
K: Deref,
263+
F: Deref,
264+
L: Deref,
265+
>(
266+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>,
267+
keys_manager: K,
268+
network: Currency,
269+
amt_msat: Option<u64>,
270+
description_hash: Sha256,
271+
duration_since_epoch: Duration,
272+
) -> Result<Invoice, SignOrCreationError<()>>
273+
where
274+
M::Target: chain::Watch<Signer>,
275+
T::Target: BroadcasterInterface,
276+
K::Target: KeysInterface<Signer = Signer>,
277+
F::Target: FeeEstimator,
278+
L::Target: Logger,
279+
{
280+
_create_invoice_from_channelmanager_and_duration_since_epoch(
281+
channelmanager, keys_manager, network, amt_msat,
282+
InvoiceDescription::Hash(&description_hash),
283+
duration_since_epoch,
135284
)
136285
}
137286

@@ -142,6 +291,37 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch<Signer: Sign,
142291
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
143292
amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
144293
) -> Result<Invoice, SignOrCreationError<()>>
294+
where
295+
M::Target: chain::Watch<Signer>,
296+
T::Target: BroadcasterInterface,
297+
K::Target: KeysInterface<Signer = Signer>,
298+
F::Target: FeeEstimator,
299+
L::Target: Logger,
300+
{
301+
_create_invoice_from_channelmanager_and_duration_since_epoch(
302+
channelmanager, keys_manager, network, amt_msat,
303+
InvoiceDescription::Direct(
304+
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
305+
),
306+
duration_since_epoch,
307+
)
308+
}
309+
310+
fn _create_invoice_from_channelmanager_and_duration_since_epoch<
311+
Signer: Sign,
312+
M: Deref,
313+
T: Deref,
314+
K: Deref,
315+
F: Deref,
316+
L: Deref,
317+
>(
318+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>,
319+
keys_manager: K,
320+
network: Currency,
321+
amt_msat: Option<u64>,
322+
description: InvoiceDescription,
323+
duration_since_epoch: Duration,
324+
) -> Result<Invoice, SignOrCreationError<()>>
145325
where
146326
M::Target: chain::Watch<Signer>,
147327
T::Target: BroadcasterInterface,
@@ -153,12 +333,19 @@ where
153333

154334
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
155335
// supply.
156-
let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
157-
amt_msat, DEFAULT_EXPIRY_TIME.try_into().unwrap())
336+
let (payment_hash, payment_secret) = channelmanager
337+
.create_inbound_payment(amt_msat, DEFAULT_EXPIRY_TIME.try_into().unwrap())
158338
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
159339
let our_node_pubkey = channelmanager.get_our_node_id();
160-
let mut invoice = InvoiceBuilder::new(network)
161-
.description(description)
340+
341+
let invoice = match description {
342+
InvoiceDescription::Direct(description) => {
343+
InvoiceBuilder::new(network).description(description.0.clone())
344+
}
345+
InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
346+
};
347+
348+
let mut invoice = invoice
162349
.duration_since_epoch(duration_since_epoch)
163350
.payee_pub_key(our_node_pubkey)
164351
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
@@ -407,6 +594,31 @@ mod test {
407594
assert_eq!(events.len(), 2);
408595
}
409596

597+
#[test]
598+
fn test_create_invoice_with_description_hash() {
599+
let chanmon_cfgs = create_chanmon_cfgs(2);
600+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
601+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
602+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
603+
let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes()));
604+
let invoice = ::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
605+
&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000),
606+
description_hash, Duration::from_secs(1234567),
607+
)
608+
.unwrap();
609+
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
610+
assert_eq!(
611+
invoice.min_final_cltv_expiry(),
612+
MIN_FINAL_CLTV_EXPIRY as u64
613+
);
614+
assert_eq!(
615+
invoice.description(),
616+
InvoiceDescription::Hash(&crate::Sha256(Sha256::hash(
617+
"Testing description_hash".as_bytes()
618+
)))
619+
);
620+
}
621+
410622
#[test]
411623
fn test_hints_includes_single_channels_to_nodes() {
412624
let chanmon_cfgs = create_chanmon_cfgs(3);
@@ -688,6 +900,59 @@ mod test {
688900
}
689901
}
690902

903+
#[test]
904+
#[cfg(feature = "std")]
905+
fn create_phantom_invoice_with_description_hash() {
906+
let mut chanmon_cfgs = create_chanmon_cfgs(3);
907+
let seed_1 = [42 as u8; 32];
908+
let seed_2 = [43 as u8; 32];
909+
let cross_node_seed = [44 as u8; 32];
910+
chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
911+
chanmon_cfgs[2].keys_manager.backing =PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
912+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
913+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
914+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
915+
let chan_0_1 = create_announced_chan_between_nodes_with_value(
916+
&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
917+
);
918+
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan_0_1.1);
919+
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_1.0);
920+
let chan_0_2 = create_announced_chan_between_nodes_with_value(
921+
&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
922+
);
923+
nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan_0_2.1);
924+
nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0);
925+
926+
let payment_amt = 20_000;
927+
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
928+
let route_hints = vec![
929+
nodes[1].node.get_phantom_route_hints(),
930+
nodes[2].node.get_phantom_route_hints(),
931+
];
932+
933+
let description_hash = crate::Sha256(Hash::hash("Description hash phantom invoice".as_bytes()));
934+
let invoice = ::utils::create_phantom_invoice_with_description_hash::<
935+
EnforcingSigner,
936+
&test_utils::TestKeysInterface,
937+
>(
938+
Some(payment_amt), description_hash, payment_hash, payment_secret, route_hints,
939+
&nodes[1].keys_manager, Currency::BitcoinTestnet,
940+
)
941+
.unwrap();
942+
943+
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
944+
assert_eq!(
945+
invoice.min_final_cltv_expiry(),
946+
MIN_FINAL_CLTV_EXPIRY as u64
947+
);
948+
assert_eq!(
949+
invoice.description(),
950+
InvoiceDescription::Hash(&crate::Sha256(Sha256::hash(
951+
"Description hash phantom invoice".as_bytes()
952+
)))
953+
);
954+
}
955+
691956
#[test]
692957
#[cfg(feature = "std")]
693958
fn test_multi_node_hints_includes_single_channels_to_participating_nodes() {

0 commit comments

Comments
 (0)