Skip to content

Commit a614b9b

Browse files
Add utility to create an invoice using the ChannelManager
This also allows the ChannelManager to track information for inbound payments to check the PaymentSecret on receive.
1 parent b440fa0 commit a614b9b

File tree

3 files changed

+178
-2
lines changed

3 files changed

+178
-2
lines changed

lightning-invoice/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ secp256k1 = { version = "0.20", features = ["recovery"] }
1515
num-traits = "0.2.8"
1616
bitcoin_hashes = "0.9.4"
1717

18+
[dev-dependencies]
19+
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }
20+
1821
[lib]
1922
crate-type = ["cdylib", "rlib"]
2023

lightning-invoice/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ use lightning::routing::router::RouteHintHop;
3333
use secp256k1::key::PublicKey;
3434
use secp256k1::{Message, Secp256k1};
3535
use secp256k1::recovery::RecoverableSignature;
36-
use std::ops::Deref;
3736

37+
use std::fmt::{Display, Formatter, self};
3838
use std::iter::FilterMap;
39+
use std::ops::Deref;
3940
use std::slice::Iter;
4041
use std::time::{SystemTime, Duration, UNIX_EPOCH};
41-
use std::fmt::{Display, Formatter, self};
4242

4343
mod de;
4444
mod ser;

lightning-invoice/src/utils.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/// Convenient utilities to create an invoice.
2+
use {Currency, Invoice, InvoiceBuilder, SignOrCreationError, SignedRawInvoice};
3+
use bech32::{FromBase32, ToBase32, u5};
4+
use bitcoin_hashes::Hash;
5+
use lightning::chain;
6+
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
7+
use lightning::chain::keysinterface::{Sign, KeysInterface};
8+
use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY};
9+
use lightning::ln::features::InvoiceFeatures;
10+
use lightning::ln::msgs::DecodeError;
11+
use lightning::routing::network_graph::RoutingFees;
12+
use lightning::routing::router::RouteHintHop;
13+
use lightning::util::logger::Logger;
14+
use std::ops::Deref;
15+
16+
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
17+
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
18+
/// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
19+
/// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
20+
/// that the payment secret is valid when the invoice is paid.
21+
pub fn create_invoice_from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
22+
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, amt_msat: Option<u64>,
23+
description: String, network: Currency
24+
) -> Result<Invoice, SignOrCreationError<DecodeError>>
25+
where
26+
M::Target: chain::Watch<Signer>,
27+
T::Target: BroadcasterInterface,
28+
K::Target: KeysInterface<Signer = Signer>,
29+
F::Target: FeeEstimator,
30+
L::Target: Logger,
31+
{
32+
// Marshall route hints.
33+
let our_channels = channelmanager.list_usable_channels();
34+
let mut route_hints = vec![];
35+
for channel in our_channels {
36+
let short_channel_id = match channel.short_channel_id {
37+
Some(id) => id,
38+
None => continue,
39+
};
40+
let forwarding_info = match channel.counterparty_forwarding_info {
41+
Some(info) => info,
42+
None => continue,
43+
};
44+
route_hints.push(vec![RouteHintHop {
45+
src_node_id: channel.remote_network_id,
46+
short_channel_id,
47+
fees: RoutingFees {
48+
base_msat: forwarding_info.fee_base_msat,
49+
proportional_millionths: forwarding_info.fee_proportional_millionths,
50+
},
51+
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
52+
htlc_minimum_msat: None,
53+
htlc_maximum_msat: None,
54+
}]);
55+
}
56+
57+
let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
58+
amt_msat,
59+
7200, // default invoice expiry is 2 hours
60+
0,
61+
);
62+
let our_node_pubkey = channelmanager.get_our_node_id();
63+
let mut invoice = InvoiceBuilder::new(network)
64+
.description(description)
65+
.current_timestamp()
66+
.payee_pub_key(our_node_pubkey)
67+
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
68+
.payment_secret(payment_secret)
69+
.features(InvoiceFeatures::known())
70+
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
71+
if let Some(amt) = amt_msat {
72+
invoice = invoice.amount_pico_btc(amt * 10);
73+
}
74+
for hint in route_hints.drain(..) {
75+
invoice = invoice.route(hint);
76+
}
77+
78+
let raw_invoice = match invoice.build_raw() {
79+
Ok(inv) => inv,
80+
Err(e) => return Err(SignOrCreationError::CreationError(e))
81+
};
82+
let hrp_str = raw_invoice.hrp.to_string();
83+
let hrp_bytes = hrp_str.as_bytes();
84+
let data_without_signature = raw_invoice.data.to_base32();
85+
let mut invoice_preimage = Vec::<u8>::from(hrp_bytes);
86+
87+
let mut data_part = Vec::from(data_without_signature);
88+
let overhang = (data_part.len() * 5) % 8;
89+
if overhang > 0 {
90+
// add padding if data does not end at a byte boundary
91+
data_part.push(u5::try_from_u8(0).unwrap());
92+
93+
// if overhang is in (1..3) we need to add u5(0) padding two times
94+
if overhang < 3 {
95+
data_part.push(u5::try_from_u8(0).unwrap());
96+
}
97+
}
98+
99+
invoice_preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
100+
.expect("No padding error may occur due to appended zero above."));
101+
let signed_raw_invoice: Result<SignedRawInvoice, DecodeError> = raw_invoice.sign(|_| keys_manager.sign_invoice(invoice_preimage));
102+
match signed_raw_invoice {
103+
Ok(inv) => return Ok(Invoice::from_signed(inv).unwrap()),
104+
Err(e) => return Err(SignOrCreationError::SignError(e))
105+
};
106+
}
107+
108+
#[cfg(test)]
109+
mod test {
110+
use {Currency, Description, InvoiceDescription};
111+
use lightning::ln::PaymentHash;
112+
use lightning::ln::functional_test_utils::*;
113+
use lightning::ln::features::InitFeatures;
114+
use lightning::ln::msgs::ChannelMessageHandler;
115+
use lightning::routing::router;
116+
use lightning::util::events::MessageSendEventsProvider;
117+
use lightning::util::test_utils;
118+
#[test]
119+
fn test_from_channelmanager() {
120+
let chanmon_cfgs = create_chanmon_cfgs(2);
121+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
122+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
123+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
124+
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
125+
let invoice = ::utils::create_invoice_from_channelmanager(&nodes[1].node, Some(10_000), "test".to_string(), Currency::BitcoinTestnet, nodes[1].keys_manager).unwrap();
126+
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
127+
assert_eq!(invoice.min_final_cltv_expiry(), Some(&9));
128+
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
129+
130+
let mut route_hints = invoice.routes().clone();
131+
let mut last_hops = Vec::new();
132+
for hint in route_hints.drain(..) {
133+
last_hops.push(hint[hint.len() - 1].clone());
134+
}
135+
let amt_msat = invoice.amount_pico_btc().unwrap() / 10;
136+
137+
let first_hops = nodes[0].node.list_usable_channels();
138+
let network_graph = nodes[0].net_graph_msg_handler.network_graph.read().unwrap();
139+
let logger = test_utils::TestLogger::new();
140+
let route = router::get_route(
141+
&nodes[0].node.get_our_node_id(),
142+
&network_graph,
143+
&invoice.recover_payee_pub_key(),
144+
Some(invoice.features().unwrap().clone()),
145+
Some(&first_hops.iter().collect::<Vec<_>>()),
146+
&last_hops.iter().collect::<Vec<_>>(),
147+
amt_msat,
148+
*invoice.min_final_cltv_expiry().unwrap() as u32,
149+
&logger,
150+
).unwrap();
151+
152+
let payment_event = {
153+
let mut payment_hash = PaymentHash([0; 32]);
154+
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
155+
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap();
156+
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
157+
assert_eq!(added_monitors.len(), 1);
158+
added_monitors.clear();
159+
160+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
161+
assert_eq!(events.len(), 1);
162+
SendEvent::from_event(events.remove(0))
163+
164+
};
165+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
166+
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg);
167+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
168+
assert_eq!(added_monitors.len(), 1);
169+
added_monitors.clear();
170+
let events = nodes[1].node.get_and_clear_pending_msg_events();
171+
assert_eq!(events.len(), 2);
172+
}
173+
}

0 commit comments

Comments
 (0)