Skip to content

Commit 3b67be2

Browse files
authored
Merge pull request #895 from valentinewallace/invoice-chanman-utility
Invoice chanman utility
2 parents 3be185a + 4c7be7e commit 3b67be2

27 files changed

+282
-79
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ use lightning::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdateErr,
3737
use lightning::chain::transaction::OutPoint;
3838
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
3939
use lightning::chain::keysinterface::{KeysInterface, InMemorySigner};
40-
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret, PaymentSendFailure, ChannelManagerReadArgs};
40+
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
41+
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentSendFailure, ChannelManagerReadArgs};
4142
use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
4243
use lightning::ln::msgs::{CommitmentUpdate, ChannelMessageHandler, DecodeError, ErrorAction, UpdateAddHTLC, Init};
4344
use lightning::util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER};
@@ -55,6 +56,7 @@ use utils::test_logger;
5556
use utils::test_persister::TestPersister;
5657

5758
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
59+
use bitcoin::secp256k1::recovery::RecoverableSignature;
5860
use bitcoin::secp256k1::Secp256k1;
5961

6062
use std::mem;
@@ -205,6 +207,10 @@ impl KeysInterface for KeyProvider {
205207
disable_revocation_policy_check: false,
206208
})
207209
}
210+
211+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
212+
unreachable!()
213+
}
208214
}
209215

210216
impl KeyProvider {

fuzz/src/full_stack.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget,
3232
use lightning::chain::chainmonitor;
3333
use lightning::chain::transaction::OutPoint;
3434
use lightning::chain::keysinterface::{InMemorySigner, KeysInterface};
35-
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret};
35+
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
36+
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager};
3637
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor};
3738
use lightning::ln::msgs::DecodeError;
3839
use lightning::routing::router::get_route;
@@ -47,6 +48,7 @@ use utils::test_logger;
4748
use utils::test_persister::TestPersister;
4849

4950
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
51+
use bitcoin::secp256k1::recovery::RecoverableSignature;
5052
use bitcoin::secp256k1::Secp256k1;
5153

5254
use std::cell::RefCell;
@@ -312,6 +314,10 @@ impl KeysInterface for KeyProvider {
312314
fn read_chan_signer(&self, data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
313315
EnforcingSigner::read(&mut std::io::Cursor::new(data))
314316
}
317+
318+
fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
319+
unreachable!()
320+
}
315321
}
316322

317323
#[inline]

lightning-background-processor/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,3 @@ lightning-persister = { version = "0.0.13", path = "../lightning-persister" }
1717
[dev-dependencies]
1818
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }
1919

20-
[dev-dependencies.bitcoin]
21-
version = "0.26"
22-
features = ["bitcoinconsensus"]

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/de.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use bech32::{u5, FromBase32};
1010

1111
use bitcoin_hashes::Hash;
1212
use bitcoin_hashes::sha256;
13+
use lightning::ln::PaymentSecret;
1314
use lightning::routing::network_graph::RoutingFees;
1415
use lightning::routing::router::RouteHintHop;
1516

@@ -484,21 +485,6 @@ impl FromBase32 for PayeePubKey {
484485
}
485486
}
486487

487-
impl FromBase32 for PaymentSecret {
488-
type Err = ParseError;
489-
490-
fn from_base32(field_data: &[u5]) -> Result<PaymentSecret, ParseError> {
491-
if field_data.len() != 52 {
492-
Err(ParseError::Skip)
493-
} else {
494-
let data_bytes = Vec::<u8>::from_base32(field_data)?;
495-
let mut payment_secret = [0; 32];
496-
payment_secret.copy_from_slice(&data_bytes);
497-
Ok(PaymentSecret(payment_secret))
498-
}
499-
}
500-
}
501-
502488
impl FromBase32 for ExpiryTime {
503489
type Err = ParseError;
504490

lightning-invoice/src/lib.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
//! * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
1515
//! * For constructing invoices use the `InvoiceBuilder`
1616
//! * For serializing invoices use the `Display`/`ToString` traits
17+
pub mod utils;
1718

1819
extern crate bech32;
1920
extern crate bitcoin_hashes;
@@ -24,6 +25,7 @@ extern crate secp256k1;
2425
use bech32::u5;
2526
use bitcoin_hashes::Hash;
2627
use bitcoin_hashes::sha256;
28+
use lightning::ln::PaymentSecret;
2729
use lightning::ln::features::InvoiceFeatures;
2830
#[cfg(any(doc, test))]
2931
use lightning::routing::network_graph::RoutingFees;
@@ -32,12 +34,12 @@ use lightning::routing::router::RouteHintHop;
3234
use secp256k1::key::PublicKey;
3335
use secp256k1::{Message, Secp256k1};
3436
use secp256k1::recovery::RecoverableSignature;
35-
use std::ops::Deref;
3637

38+
use std::fmt::{Display, Formatter, self};
3739
use std::iter::FilterMap;
40+
use std::ops::Deref;
3841
use std::slice::Iter;
3942
use std::time::{SystemTime, Duration, UNIX_EPOCH};
40-
use std::fmt::{Display, Formatter, self};
4143

4244
mod de;
4345
mod ser;
@@ -358,10 +360,6 @@ pub struct Description(String);
358360
#[derive(Eq, PartialEq, Debug, Clone)]
359361
pub struct PayeePubKey(pub PublicKey);
360362

361-
/// 256-bit payment secret
362-
#[derive(Eq, PartialEq, Debug, Clone)]
363-
pub struct PaymentSecret(pub [u8; 32]);
364-
365363
/// Positive duration that defines when (relatively to the timestamp) in the future the invoice
366364
/// expires
367365
///
@@ -731,8 +729,8 @@ macro_rules! find_extract {
731729

732730
#[allow(missing_docs)]
733731
impl RawInvoice {
734-
/// Hash the HRP as bytes and signatureless data part.
735-
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
732+
/// Construct the invoice's HRP and signatureless data into a preimage to be hashed.
733+
pub(crate) fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec<u8> {
736734
use bech32::FromBase32;
737735

738736
let mut preimage = Vec::<u8>::from(hrp_bytes);
@@ -751,7 +749,12 @@ impl RawInvoice {
751749

752750
preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
753751
.expect("No padding error may occur due to appended zero above."));
752+
preimage
753+
}
754754

755+
/// Hash the HRP as bytes and signatureless data part.
756+
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
757+
let preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, data_without_signature);
755758
let mut hash: [u8; 32] = Default::default();
756759
hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]);
757760
hash

lightning-invoice/src/ser.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -297,18 +297,6 @@ impl Base32Len for PayeePubKey {
297297
}
298298
}
299299

300-
impl ToBase32 for PaymentSecret {
301-
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
302-
(&self.0[..]).write_base32(writer)
303-
}
304-
}
305-
306-
impl Base32Len for PaymentSecret {
307-
fn base32_len(&self) -> usize {
308-
bytes_size_to_base32_size(32)
309-
}
310-
}
311-
312300
impl ToBase32 for ExpiryTime {
313301
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
314302
writer.write(&encode_int_be_base32(self.as_seconds()))

lightning-invoice/src/utils.rs

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

lightning-invoice/tests/ser_de.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
extern crate bitcoin_hashes;
2+
extern crate lightning;
23
extern crate lightning_invoice;
34
extern crate secp256k1;
45

56
use bitcoin_hashes::hex::FromHex;
67
use bitcoin_hashes::sha256;
8+
use lightning::ln::PaymentSecret;
79
use lightning_invoice::*;
810
use secp256k1::Secp256k1;
911
use secp256k1::key::SecretKey;

lightning-persister/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,5 @@ libc = "0.2"
1919
[target.'cfg(windows)'.dependencies]
2020
winapi = { version = "0.3", features = ["winbase"] }
2121

22-
[dev-dependencies.bitcoin]
23-
version = "0.26"
24-
features = ["bitcoinconsensus"]
25-
2622
[dev-dependencies]
2723
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }

lightning/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Still missing tons of error-handling. See GitHub issues for suggested projects i
1414
allow_wallclock_use = []
1515
fuzztarget = ["bitcoin/fuzztarget", "regex"]
1616
# Internal test utilities exposed to other repo crates
17-
_test_utils = ["hex", "regex"]
17+
_test_utils = ["hex", "regex", "bitcoin/bitcoinconsensus"]
1818
# Unlog messages superior at targeted level.
1919
max_level_off = []
2020
max_level_error = []
@@ -32,13 +32,13 @@ bitcoin = "0.26"
3232
hex = { version = "0.3", optional = true }
3333
regex = { version = "0.1.80", optional = true }
3434

35-
[dev-dependencies.bitcoin]
36-
version = "0.26"
37-
features = ["bitcoinconsensus"]
38-
3935
[dev-dependencies]
4036
hex = "0.3"
4137
regex = "0.1.80"
4238

39+
[dev-dependencies.bitcoin]
40+
version = "0.26"
41+
features = ["bitcoinconsensus"]
42+
4343
[package.metadata.docs.rs]
4444
features = ["allow_wallclock_use"] # When https://github.com/rust-lang/rust/issues/43781 complies with our MSVR, we can add nice banners in the docs for the methods behind this feature-gate.

0 commit comments

Comments
 (0)