Skip to content

Commit 5852bbe

Browse files
committed
Create normal/phantom invoice with description hash
1 parent c244c78 commit 5852bbe

File tree

1 file changed

+238
-16
lines changed

1 file changed

+238
-16
lines changed

lightning-invoice/src/utils.rs

Lines changed: 238 additions & 16 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,81 @@ 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
53+
amt_msat: Option<u64>, description: String, payment_hash: PaymentHash, payment_secret: PaymentSecret,
54+
phantom_route_hints: Vec<PhantomRouteHints>, keys_manager: K, network: Currency,
5555
) -> 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+
{
56115
if phantom_route_hints.len() == 0 {
57-
return Err(SignOrCreationError::CreationError(CreationError::MissingRouteHints))
116+
return Err(SignOrCreationError::CreationError(
117+
CreationError::MissingRouteHints,
118+
));
58119
}
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
61128
.current_timestamp()
62129
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
63130
.payment_secret(payment_secret)
@@ -126,12 +193,61 @@ where
126193
let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
127194
.expect("for the foreseeable future this shouldn't happen");
128195
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,
208+
K: Deref, F: Deref, L: Deref,
209+
>(
210+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
211+
amt_msat: Option<u64>, description_hash: Sha256,
212+
) -> Result<Invoice, SignOrCreationError<()>>
213+
where
214+
M::Target: chain::Watch<Signer>,
215+
T::Target: BroadcasterInterface,
216+
K::Target: KeysInterface<Signer = Signer>,
217+
F::Target: FeeEstimator,
218+
L::Target: Logger,
219+
{
220+
use std::time::SystemTime;
221+
222+
let duration = SystemTime::now()
223+
.duration_since(SystemTime::UNIX_EPOCH)
224+
.expect("for the foreseeable future this shouldn't happen");
225+
226+
create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
227+
channelmanager, keys_manager, network, amt_msat, description_hash, duration,
228+
)
229+
}
230+
231+
/// See [`create_invoice_from_channelmanager_with_description_hash`]
232+
/// This version can be used in a `no_std` environment, where [`std::time::SystemTime`] is not
233+
/// available and the current time is supplied by the caller.
234+
pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch<
235+
Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref,
236+
>(
237+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
238+
amt_msat: Option<u64>, description_hash: Sha256, duration_since_epoch: Duration,
239+
) -> Result<Invoice, SignOrCreationError<()>>
240+
where
241+
M::Target: chain::Watch<Signer>,
242+
T::Target: BroadcasterInterface,
243+
K::Target: KeysInterface<Signer = Signer>,
244+
F::Target: FeeEstimator,
245+
L::Target: Logger,
246+
{
247+
_create_invoice_from_channelmanager_and_duration_since_epoch(
248+
channelmanager, keys_manager, network, amt_msat,
249+
InvoiceDescription::Hash(&description_hash),
250+
duration_since_epoch,
135251
)
136252
}
137253

@@ -142,6 +258,28 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch<Signer: Sign,
142258
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
143259
amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
144260
) -> Result<Invoice, SignOrCreationError<()>>
261+
where
262+
M::Target: chain::Watch<Signer>,
263+
T::Target: BroadcasterInterface,
264+
K::Target: KeysInterface<Signer = Signer>,
265+
F::Target: FeeEstimator,
266+
L::Target: Logger,
267+
{
268+
_create_invoice_from_channelmanager_and_duration_since_epoch(
269+
channelmanager, keys_manager, network, amt_msat,
270+
InvoiceDescription::Direct(
271+
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
272+
),
273+
duration_since_epoch,
274+
)
275+
}
276+
277+
fn _create_invoice_from_channelmanager_and_duration_since_epoch<
278+
Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref,
279+
>(
280+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
281+
amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
282+
) -> Result<Invoice, SignOrCreationError<()>>
145283
where
146284
M::Target: chain::Watch<Signer>,
147285
T::Target: BroadcasterInterface,
@@ -153,12 +291,19 @@ where
153291

154292
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
155293
// supply.
156-
let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
157-
amt_msat, DEFAULT_EXPIRY_TIME.try_into().unwrap())
294+
let (payment_hash, payment_secret) = channelmanager
295+
.create_inbound_payment(amt_msat, DEFAULT_EXPIRY_TIME.try_into().unwrap())
158296
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
159297
let our_node_pubkey = channelmanager.get_our_node_id();
160-
let mut invoice = InvoiceBuilder::new(network)
161-
.description(description)
298+
299+
let invoice = match description {
300+
InvoiceDescription::Direct(description) => {
301+
InvoiceBuilder::new(network).description(description.0.clone())
302+
}
303+
InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
304+
};
305+
306+
let mut invoice = invoice
162307
.duration_since_epoch(duration_since_epoch)
163308
.payee_pub_key(our_node_pubkey)
164309
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
@@ -407,6 +552,31 @@ mod test {
407552
assert_eq!(events.len(), 2);
408553
}
409554

555+
#[test]
556+
fn test_create_invoice_with_description_hash() {
557+
let chanmon_cfgs = create_chanmon_cfgs(2);
558+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
559+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
560+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
561+
let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes()));
562+
let invoice = ::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
563+
&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000),
564+
description_hash, Duration::from_secs(1234567),
565+
)
566+
.unwrap();
567+
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
568+
assert_eq!(
569+
invoice.min_final_cltv_expiry(),
570+
MIN_FINAL_CLTV_EXPIRY as u64
571+
);
572+
assert_eq!(
573+
invoice.description(),
574+
InvoiceDescription::Hash(&crate::Sha256(Sha256::hash(
575+
"Testing description_hash".as_bytes()
576+
)))
577+
);
578+
}
579+
410580
#[test]
411581
fn test_hints_includes_single_channels_to_nodes() {
412582
let chanmon_cfgs = create_chanmon_cfgs(3);
@@ -688,6 +858,58 @@ mod test {
688858
}
689859
}
690860

861+
#[test]
862+
#[cfg(feature = "std")]
863+
fn create_phantom_invoice_with_description_hash() {
864+
let mut chanmon_cfgs = create_chanmon_cfgs(3);
865+
let seed_1 = [42 as u8; 32];
866+
let seed_2 = [43 as u8; 32];
867+
let cross_node_seed = [44 as u8; 32];
868+
chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
869+
chanmon_cfgs[2].keys_manager.backing =PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
870+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
871+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
872+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
873+
let chan_0_1 = create_announced_chan_between_nodes_with_value(
874+
&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
875+
);
876+
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan_0_1.1);
877+
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_1.0);
878+
let chan_0_2 = create_announced_chan_between_nodes_with_value(
879+
&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
880+
);
881+
nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan_0_2.1);
882+
nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0);
883+
884+
let payment_amt = 20_000;
885+
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
886+
let route_hints = vec![
887+
nodes[1].node.get_phantom_route_hints(),
888+
nodes[2].node.get_phantom_route_hints(),
889+
];
890+
891+
let description_hash = crate::Sha256(Hash::hash("Description hash phantom invoice".as_bytes()));
892+
let invoice = ::utils::create_phantom_invoice_with_description_hash::<
893+
EnforcingSigner,
894+
&test_utils::TestKeysInterface,
895+
>(
896+
Some(payment_amt), description_hash, payment_hash, payment_secret, route_hints,
897+
&nodes[1].keys_manager, Currency::BitcoinTestnet,
898+
).unwrap();
899+
900+
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
901+
assert_eq!(
902+
invoice.min_final_cltv_expiry(),
903+
MIN_FINAL_CLTV_EXPIRY as u64
904+
);
905+
assert_eq!(
906+
invoice.description(),
907+
InvoiceDescription::Hash(&crate::Sha256(Sha256::hash(
908+
"Description hash phantom invoice".as_bytes()
909+
)))
910+
);
911+
}
912+
691913
#[test]
692914
#[cfg(feature = "std")]
693915
fn test_multi_node_hints_includes_single_channels_to_participating_nodes() {

0 commit comments

Comments
 (0)