Skip to content

Commit 2b1c12f

Browse files
Support phantom payment receive in ChannelManager, with invoice util
See get_phantom_scid and invoice util's create_phantom_invoice for more info
1 parent a7ec4f6 commit 2b1c12f

File tree

5 files changed

+355
-23
lines changed

5 files changed

+355
-23
lines changed

lightning-invoice/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,20 @@ pub enum CreationError {
14941494

14951495
/// The supplied millisatoshi amount was greater than the total bitcoin supply.
14961496
InvalidAmount,
1497+
1498+
/// Route hints were required for this invoice and were missing. Applies to
1499+
/// [phantom invoices].
1500+
///
1501+
/// [phantom invoices]: crate::utils::create_phantom_invoice
1502+
MissingRouteHints,
1503+
1504+
/// For [phantom invoices], short channel ids for phantom route hints are supplied and used to
1505+
/// retrieve the phantom's private key. This can fail if an scid was not previously retrieved from
1506+
/// [`ChannelManager::get_phantom_scid`].
1507+
///
1508+
/// [phantom invoices]: crate::utils::create_phantom_invoice
1509+
/// [`ChannelManager::get_phantom_scid`]: lightning::ln::channelmanager::ChannelManager::get_phantom_scid
1510+
InvalidPhantomScid,
14971511
}
14981512

14991513
impl Display for CreationError {
@@ -1504,6 +1518,8 @@ impl Display for CreationError {
15041518
CreationError::TimestampOutOfBounds => f.write_str("The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`"),
15051519
CreationError::ExpiryTimeOutOfBounds => f.write_str("The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`"),
15061520
CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"),
1521+
CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"),
1522+
CreationError::InvalidPhantomScid => f.write_str("Failed to retrieve the phantom secret with the supplied phantom scid"),
15071523
}
15081524
}
15091525
}

lightning-invoice/src/utils.rs

Lines changed: 247 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,124 @@ use payment::{Payer, Router};
55

66
use bech32::ToBase32;
77
use bitcoin_hashes::Hash;
8+
use bitcoin_hashes::sha256::Hash as Sha256;
89
use crate::prelude::*;
910
use lightning::chain;
1011
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1112
use lightning::chain::keysinterface::{Sign, KeysInterface};
1213
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
13-
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
14+
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
1415
use lightning::ln::msgs::LightningError;
1516
use lightning::routing::scoring::Score;
1617
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
1718
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route};
1819
use lightning::util::logger::Logger;
20+
use secp256k1::Secp256k1;
1921
use secp256k1::key::PublicKey;
2022
use core::convert::TryInto;
2123
use core::ops::Deref;
2224
use core::time::Duration;
2325

26+
#[cfg(feature = "std")]
27+
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
28+
/// This works because the invoice officially pays to a fake node (the "phantom"). Useful for
29+
/// load-balancing payments between multiple nodes.
30+
///
31+
/// `channels` parameter:
32+
/// * Contains a list of tuples of `(phantom_node_scid, real_node_pubkey,
33+
/// real_channel_details)`, where `phantom_node_scid` is unique to each `channels` entry
34+
/// * `phantom_node_scid`s are retrieved from [`ChannelManager::get_phantom_scid`]
35+
/// * `real_channel_details` are retrieved from [`ChannelManager::list_channels`]
36+
/// * Contains channel info for all nodes participating in the phantom invoice
37+
/// * For increased privacy, it is ideal but not required to generate new `phantom_node_scid`s for
38+
/// each new invoice
39+
/// * It is fine to cache this information and reuse it across invoices, as long as you periodically
40+
/// check that no provided real channels have become unavailable
41+
/// * Note that including too many channels here could result in making the invoice too long for QR
42+
/// code scanning
43+
///
44+
/// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
45+
/// [`ChannelManager::create_inbound_payment_for_hash`].
46+
///
47+
/// See [`KeysInterface::get_phantom_secret`] for more requirements on supporting phantom node
48+
/// payments.
49+
///
50+
/// [`KeysInterface::get_phantom_secret`]: lightning::chain::keysinterface::KeysInterface::get_phantom_secret
51+
pub fn create_phantom_invoice<Signer: Sign, K: Deref>(
52+
amt_msat: Option<u64>, description: String, payment_hash: PaymentHash, payment_secret:
53+
PaymentSecret, channels: &[(u64, PublicKey, &ChannelDetails)], keys_manager: K, network: Currency
54+
) -> Result<Invoice, SignOrCreationError<()>> where K::Target: KeysInterface {
55+
if channels.len() == 0 {
56+
return Err(SignOrCreationError::CreationError(CreationError::MissingRouteHints))
57+
}
58+
let phantom_secret = match keys_manager.get_phantom_secret(channels[0].0) {
59+
Ok(s) => s,
60+
Err(()) => return Err(SignOrCreationError::CreationError(CreationError::InvalidPhantomScid))
61+
};
62+
let phantom_pubkey = PublicKey::from_secret_key(&Secp256k1::new(), &phantom_secret);
63+
let mut invoice = InvoiceBuilder::new(network)
64+
.description(description)
65+
.current_timestamp()
66+
.payee_pub_key(phantom_pubkey)
67+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
68+
.payment_secret(payment_secret)
69+
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
70+
if let Some(amt) = amt_msat {
71+
invoice = invoice.amount_milli_satoshis(amt);
72+
}
73+
74+
for (phantom_scid, real_node_pubkey, channel) in channels {
75+
let short_channel_id = match channel.short_channel_id {
76+
Some(id) => id,
77+
None => continue,
78+
};
79+
let forwarding_info = match &channel.counterparty.forwarding_info {
80+
Some(info) => info.clone(),
81+
None => continue,
82+
};
83+
invoice = invoice.private_route(RouteHint(vec![
84+
RouteHintHop {
85+
src_node_id: channel.counterparty.node_id,
86+
short_channel_id,
87+
fees: RoutingFees {
88+
base_msat: forwarding_info.fee_base_msat,
89+
proportional_millionths: forwarding_info.fee_proportional_millionths,
90+
},
91+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
92+
htlc_minimum_msat: None,
93+
htlc_maximum_msat: None,
94+
},
95+
RouteHintHop {
96+
src_node_id: *real_node_pubkey,
97+
short_channel_id: *phantom_scid,
98+
fees: RoutingFees {
99+
base_msat: 0,
100+
proportional_millionths: 0,
101+
},
102+
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
103+
htlc_minimum_msat: None,
104+
htlc_maximum_msat: None,
105+
}])
106+
);
107+
}
108+
109+
let raw_invoice = match invoice.build_raw() {
110+
Ok(inv) => inv,
111+
Err(e) => return Err(SignOrCreationError::CreationError(e))
112+
};
113+
let hrp_str = raw_invoice.hrp.to_string();
114+
let hrp_bytes = hrp_str.as_bytes();
115+
let data_without_signature = raw_invoice.data.to_base32();
116+
let invoice_preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, &data_without_signature);
117+
let secp_ctx = Secp256k1::new();
118+
let invoice_preimage_msg = secp256k1::Message::from_slice(&Sha256::hash(&invoice_preimage)).unwrap();
119+
let signed_raw_invoice = raw_invoice.sign(|_| Ok(secp_ctx.sign_recoverable(&invoice_preimage_msg, &phantom_secret)));
120+
match signed_raw_invoice {
121+
Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
122+
Err(e) => Err(SignOrCreationError::SignError(e))
123+
}
124+
}
125+
24126
#[cfg(feature = "std")]
25127
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
26128
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
@@ -193,15 +295,20 @@ where
193295
mod test {
194296
use core::time::Duration;
195297
use {Currency, Description, InvoiceDescription};
196-
use lightning::ln::PaymentHash;
197-
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY;
298+
use bitcoin_hashes::Hash;
299+
use bitcoin_hashes::sha256::Hash as Sha256;
300+
use lightning::chain::keysinterface::KeysManager;
301+
use lightning::ln::{PaymentPreimage, PaymentHash};
302+
use lightning::ln::channelmanager::{ChannelDetails, MIN_FINAL_CLTV_EXPIRY};
198303
use lightning::ln::functional_test_utils::*;
199304
use lightning::ln::features::InitFeatures;
200305
use lightning::ln::msgs::ChannelMessageHandler;
201306
use lightning::routing::router::{Payee, RouteParameters, find_route};
202-
use lightning::util::events::MessageSendEventsProvider;
307+
use lightning::util::enforcing_trait_impls::EnforcingSigner;
308+
use lightning::util::events::{MessageSendEvent, MessageSendEventsProvider, Event, PaymentPurpose};
203309
use lightning::util::test_utils;
204310
use utils::create_invoice_from_channelmanager_and_duration_since_epoch;
311+
use secp256k1::PublicKey;
205312

206313
#[test]
207314
fn test_from_channelmanager() {
@@ -255,4 +362,140 @@ mod test {
255362
let events = nodes[1].node.get_and_clear_pending_msg_events();
256363
assert_eq!(events.len(), 2);
257364
}
365+
366+
#[test]
367+
fn test_multi_node_receive() {
368+
#[cfg(feature = "std")]
369+
do_test_multi_node_receive(true);
370+
#[cfg(feature = "std")]
371+
do_test_multi_node_receive(false);
372+
}
373+
374+
#[cfg(feature = "std")]
375+
fn do_test_multi_node_receive(user_generated_pmt_hash: bool) {
376+
let mut chanmon_cfgs = create_chanmon_cfgs(3);
377+
let seed_1 = [42 as u8; 32];
378+
let seed_2 = [43 as u8; 32];
379+
let cross_node_seed = [44 as u8; 32];
380+
chanmon_cfgs[1].keys_manager.backing = KeysManager::new_multi_receive(&seed_1, 43, 44, &cross_node_seed);
381+
chanmon_cfgs[2].keys_manager.backing = KeysManager::new_multi_receive(&seed_2, 43, 44, &cross_node_seed);
382+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
383+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
384+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
385+
let chan_0_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
386+
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan_0_1.1);
387+
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_1.0);
388+
let chan_0_2 = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known());
389+
nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan_0_2.1);
390+
nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0);
391+
392+
let payment_amt = 10_000;
393+
let (payment_preimage, payment_hash, payment_secret) = {
394+
if user_generated_pmt_hash {
395+
let payment_preimage = PaymentPreimage([1; 32]);
396+
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
397+
let payment_secret = nodes[1].node.create_inbound_payment_for_hash(payment_hash, Some(payment_amt), 3600).unwrap();
398+
(payment_preimage, payment_hash, payment_secret)
399+
} else {
400+
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
401+
let payment_preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
402+
(payment_preimage, payment_hash, payment_secret)
403+
}
404+
};
405+
let channels_1 = nodes[1].node.list_channels();
406+
let channels_2 = nodes[2].node.list_channels();
407+
let mut route_hints = channels_1.iter().map(|e| (nodes[1].node.get_phantom_scid(), nodes[1].node.get_our_node_id(), e)).collect::<Vec<(u64, PublicKey, &ChannelDetails)>>();
408+
for channel in channels_2.iter() {
409+
route_hints.push((nodes[2].node.get_phantom_scid(), nodes[2].node.get_our_node_id(), &channel));
410+
}
411+
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();
412+
413+
assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
414+
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
415+
assert_eq!(invoice.route_hints().len(), 2);
416+
assert!(!invoice.features().unwrap().supports_basic_mpp());
417+
418+
let payee = Payee::from_node_id(invoice.recover_payee_pub_key())
419+
.with_features(invoice.features().unwrap().clone())
420+
.with_route_hints(invoice.route_hints());
421+
let params = RouteParameters {
422+
payee,
423+
final_value_msat: invoice.amount_milli_satoshis().unwrap(),
424+
final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
425+
};
426+
let first_hops = nodes[0].node.list_usable_channels();
427+
let network_graph = node_cfgs[0].network_graph;
428+
let logger = test_utils::TestLogger::new();
429+
let scorer = test_utils::TestScorer::with_fixed_penalty(0);
430+
let route = find_route(
431+
&nodes[0].node.get_our_node_id(), &params, network_graph,
432+
Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,
433+
).unwrap();
434+
let (payment_event, fwd_idx) = {
435+
let mut payment_hash = PaymentHash([0; 32]);
436+
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
437+
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().clone())).unwrap();
438+
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
439+
assert_eq!(added_monitors.len(), 1);
440+
added_monitors.clear();
441+
442+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
443+
assert_eq!(events.len(), 1);
444+
let fwd_idx = match events[0] {
445+
MessageSendEvent::UpdateHTLCs { node_id, .. } => {
446+
if node_id == nodes[1].node.get_our_node_id() {
447+
1
448+
} else { 2 }
449+
},
450+
_ => panic!("Unexpected event")
451+
};
452+
(SendEvent::from_event(events.remove(0)), fwd_idx)
453+
};
454+
nodes[fwd_idx].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
455+
commitment_signed_dance!(nodes[fwd_idx], nodes[0], &payment_event.commitment_msg, false, true);
456+
// expect_pending_htlcs_forwardable!(nodes[fwd_idx]);
457+
458+
// Note that we have to "forward pending HTLCs" twice before we see the PaymentReceived as
459+
// this "emulates" the payment taking two hops, providing some privacy to make phantom node
460+
// payments "look real" by taking more time.
461+
expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
462+
nodes[fwd_idx].node.process_pending_htlc_forwards();
463+
expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
464+
nodes[fwd_idx].node.process_pending_htlc_forwards();
465+
466+
let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
467+
let events = nodes[fwd_idx].node.get_and_clear_pending_events();
468+
assert_eq!(events.len(), 1);
469+
match events[0] {
470+
Event::PaymentReceived { payment_hash: ref hash, ref purpose, amt } => {
471+
assert_eq!(*hash, payment_hash);
472+
assert_eq!(amt, payment_amt);
473+
match purpose {
474+
PaymentPurpose::InvoicePayment { payment_preimage, payment_secret: secret, .. } => {
475+
assert_eq!(*payment_preimage, payment_preimage_opt);
476+
assert_eq!(*secret, payment_secret);
477+
},
478+
_ => panic!("Unexpected payment purpose"),
479+
}
480+
},
481+
_ => panic!("Unexpected event"),
482+
}
483+
do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[fwd_idx])[..]), false, payment_preimage);
484+
let events = nodes[0].node.get_and_clear_pending_events();
485+
assert_eq!(events.len(), 2);
486+
match events[0] {
487+
Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
488+
assert_eq!(payment_preimage, *ev_preimage);
489+
assert_eq!(payment_hash, *ev_hash);
490+
assert_eq!(fee_paid_msat, &Some(0));
491+
},
492+
_ => panic!("Unexpected event")
493+
}
494+
match events[1] {
495+
Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
496+
assert_eq!(hash, Some(payment_hash));
497+
},
498+
_ => panic!("Unexpected event")
499+
}
500+
}
258501
}

lightning/src/chain/keysinterface.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,31 @@ pub trait KeysInterface {
407407

408408
/// Get secret key material as bytes for use in encrypting and decrypting inbound payment data.
409409
///
410+
/// If the implementor of this trait supports [phantom node payments], then every node that is
411+
/// intended to be included in the phantom invoice(s) route hints must return the same value from
412+
/// this method.
413+
///
410414
/// This method must return the same value each time it is called.
415+
///
416+
///[phantom node payments]: crate::ln::channelmanager::ChannelManager::get_phantom_scid
411417
fn get_inbound_payment_key_material(&self) -> KeyMaterial;
412418

413-
/// Get a secret key for use in receiving phantom node payments.
419+
/// Get the phantom node secret key (aka node_id or network_key) for use in receiving a [phantom
420+
/// node payment]. This secret key is used to sign phantom invoices.
421+
///
422+
/// When a phantom invoice is generated with a set of phantom scids from multiple nodes, this
423+
/// method must return the same key as the invoice was signed with, no matter the node on which it
424+
/// is called. This implies that each set of phantom SCIDs will have one common secret key that
425+
/// must be uniformly returned across nodes, though the secret keys may be reused.
426+
///
427+
/// Note that if you are using [`KeysManager`], every LDK node must use
428+
/// [`KeysManager::new_multi_receive`] to initialize its `KeysManager` or phantom payments will
429+
/// fail to be received.
430+
///
431+
/// See [`KeysInterface::get_inbound_payment_key_material`] for further requirements on nodes
432+
/// supporting phantom node payments.
433+
///
434+
///[phantom node payment]: crate::ln::channelmanager::ChannelManager::get_phantom_scid
414435
fn get_phantom_secret(&self, scid: u64) -> Result<SecretKey, ()>;
415436
}
416437

0 commit comments

Comments
 (0)