Skip to content

Invoice chanman utility #895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ use lightning::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdateErr,
use lightning::chain::transaction::OutPoint;
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::keysinterface::{KeysInterface, InMemorySigner};
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret, PaymentSendFailure, ChannelManagerReadArgs};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentSendFailure, ChannelManagerReadArgs};
use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use lightning::ln::msgs::{CommitmentUpdate, ChannelMessageHandler, DecodeError, ErrorAction, UpdateAddHTLC, Init};
use lightning::util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER};
Expand All @@ -55,6 +56,7 @@ use utils::test_logger;
use utils::test_persister::TestPersister;

use bitcoin::secp256k1::key::{PublicKey,SecretKey};
use bitcoin::secp256k1::recovery::RecoverableSignature;
use bitcoin::secp256k1::Secp256k1;

use std::mem;
Expand Down Expand Up @@ -205,6 +207,10 @@ impl KeysInterface for KeyProvider {
disable_revocation_policy_check: false,
})
}

fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}

impl KeyProvider {
Expand Down
8 changes: 7 additions & 1 deletion fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget,
use lightning::chain::chainmonitor;
use lightning::chain::transaction::OutPoint;
use lightning::chain::keysinterface::{InMemorySigner, KeysInterface};
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager};
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor};
use lightning::ln::msgs::DecodeError;
use lightning::routing::router::get_route;
Expand All @@ -47,6 +48,7 @@ use utils::test_logger;
use utils::test_persister::TestPersister;

use bitcoin::secp256k1::key::{PublicKey,SecretKey};
use bitcoin::secp256k1::recovery::RecoverableSignature;
use bitcoin::secp256k1::Secp256k1;

use std::cell::RefCell;
Expand Down Expand Up @@ -312,6 +314,10 @@ impl KeysInterface for KeyProvider {
fn read_chan_signer(&self, data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
EnforcingSigner::read(&mut std::io::Cursor::new(data))
}

fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}

#[inline]
Expand Down
3 changes: 0 additions & 3 deletions lightning-background-processor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,3 @@ lightning-persister = { version = "0.0.13", path = "../lightning-persister" }
[dev-dependencies]
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }

[dev-dependencies.bitcoin]
version = "0.26"
features = ["bitcoinconsensus"]
3 changes: 3 additions & 0 deletions lightning-invoice/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ secp256k1 = { version = "0.20", features = ["recovery"] }
num-traits = "0.2.8"
bitcoin_hashes = "0.9.4"

[dev-dependencies]
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }

[lib]
crate-type = ["cdylib", "rlib"]

16 changes: 1 addition & 15 deletions lightning-invoice/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bech32::{u5, FromBase32};

use bitcoin_hashes::Hash;
use bitcoin_hashes::sha256;
use lightning::ln::PaymentSecret;
use lightning::routing::network_graph::RoutingFees;
use lightning::routing::router::RouteHintHop;

Expand Down Expand Up @@ -484,21 +485,6 @@ impl FromBase32 for PayeePubKey {
}
}

impl FromBase32 for PaymentSecret {
type Err = ParseError;

fn from_base32(field_data: &[u5]) -> Result<PaymentSecret, ParseError> {
if field_data.len() != 52 {
Err(ParseError::Skip)
} else {
let data_bytes = Vec::<u8>::from_base32(field_data)?;
let mut payment_secret = [0; 32];
payment_secret.copy_from_slice(&data_bytes);
Ok(PaymentSecret(payment_secret))
}
}
}

impl FromBase32 for ExpiryTime {
type Err = ParseError;

Expand Down
19 changes: 11 additions & 8 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//! * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
//! * For constructing invoices use the `InvoiceBuilder`
//! * For serializing invoices use the `Display`/`ToString` traits
pub mod utils;

extern crate bech32;
extern crate bitcoin_hashes;
Expand All @@ -24,6 +25,7 @@ extern crate secp256k1;
use bech32::u5;
use bitcoin_hashes::Hash;
use bitcoin_hashes::sha256;
use lightning::ln::PaymentSecret;
use lightning::ln::features::InvoiceFeatures;
#[cfg(any(doc, test))]
use lightning::routing::network_graph::RoutingFees;
Expand All @@ -32,12 +34,12 @@ use lightning::routing::router::RouteHintHop;
use secp256k1::key::PublicKey;
use secp256k1::{Message, Secp256k1};
use secp256k1::recovery::RecoverableSignature;
use std::ops::Deref;

use std::fmt::{Display, Formatter, self};
use std::iter::FilterMap;
use std::ops::Deref;
use std::slice::Iter;
use std::time::{SystemTime, Duration, UNIX_EPOCH};
use std::fmt::{Display, Formatter, self};

mod de;
mod ser;
Expand Down Expand Up @@ -358,10 +360,6 @@ pub struct Description(String);
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct PayeePubKey(pub PublicKey);

/// 256-bit payment secret
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct PaymentSecret(pub [u8; 32]);

/// Positive duration that defines when (relatively to the timestamp) in the future the invoice
/// expires
///
Expand Down Expand Up @@ -731,8 +729,8 @@ macro_rules! find_extract {

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

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

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

/// Hash the HRP as bytes and signatureless data part.
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
let preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, data_without_signature);
let mut hash: [u8; 32] = Default::default();
hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]);
hash
Expand Down
12 changes: 0 additions & 12 deletions lightning-invoice/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,18 +297,6 @@ impl Base32Len for PayeePubKey {
}
}

impl ToBase32 for PaymentSecret {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
(&self.0[..]).write_base32(writer)
}
}

impl Base32Len for PaymentSecret {
fn base32_len(&self) -> usize {
bytes_size_to_base32_size(32)
}
}

impl ToBase32 for ExpiryTime {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
writer.write(&encode_int_be_base32(self.as_seconds()))
Expand Down
157 changes: 157 additions & 0 deletions lightning-invoice/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! Convenient utilities to create an invoice.
use {Currency, Invoice, InvoiceBuilder, SignOrCreationError, RawInvoice};
use bech32::ToBase32;
use bitcoin_hashes::Hash;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::chain::keysinterface::{Sign, KeysInterface};
use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY};
use lightning::ln::features::InvoiceFeatures;
use lightning::routing::network_graph::RoutingFees;
use lightning::routing::router::RouteHintHop;
use lightning::util::logger::Logger;
use std::ops::Deref;

/// Utility to construct an invoice. Generally, unless you want to do something like a custom
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
/// method stores the invoice's payment secret and preimage in `ChannelManager`, so (a) the user
/// doesn't have to store preimage/payment secret information and (b) `ChannelManager` can verify
/// that the payment secret is valid when the invoice is paid.
pub fn create_invoice_from_channelmanager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
channelmanager: &ChannelManager<Signer, M, T, K, F, L>, keys_manager: K, network: Currency,
amt_msat: Option<u64>, description: String
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<Signer>,
T::Target: BroadcasterInterface,
K::Target: KeysInterface<Signer = Signer>,
F::Target: FeeEstimator,
L::Target: Logger,
{
// Marshall route hints.
let our_channels = channelmanager.list_usable_channels();
let mut route_hints = vec![];
for channel in our_channels {
let short_channel_id = match channel.short_channel_id {
Some(id) => id,
None => continue,
};
let forwarding_info = match channel.counterparty_forwarding_info {
Some(info) => info,
None => continue,
};
route_hints.push(vec![RouteHintHop {
src_node_id: channel.remote_network_id,
short_channel_id,
fees: RoutingFees {
base_msat: forwarding_info.fee_base_msat,
proportional_millionths: forwarding_info.fee_proportional_millionths,
},
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]);
}

let (payment_hash, payment_secret) = channelmanager.create_inbound_payment(
amt_msat,
7200, // default invoice expiry is 2 hours
0,
);
let our_node_pubkey = channelmanager.get_our_node_id();
let mut invoice = InvoiceBuilder::new(network)
.description(description)
.current_timestamp()
.payee_pub_key(our_node_pubkey)
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
.features(InvoiceFeatures::known())
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
if let Some(amt) = amt_msat {
invoice = invoice.amount_pico_btc(amt * 10);
}
for hint in route_hints.drain(..) {
invoice = invoice.route(hint);
}

let raw_invoice = match invoice.build_raw() {
Ok(inv) => inv,
Err(e) => return Err(SignOrCreationError::CreationError(e))
};
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
let invoice_preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, &data_without_signature);
let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(invoice_preimage));
match signed_raw_invoice {
Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}

#[cfg(test)]
mod test {
use {Currency, Description, InvoiceDescription};
use lightning::ln::PaymentHash;
use lightning::ln::functional_test_utils::*;
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::routing::router;
use lightning::util::events::MessageSendEventsProvider;
use lightning::util::test_utils;
#[test]
fn test_from_channelmanager() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let _chan = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let invoice = ::utils::create_invoice_from_channelmanager(&nodes[1].node, nodes[1].keys_manager, Currency::BitcoinTestnet, Some(10_000), "test".to_string()).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry(), Some(9));
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));

let mut route_hints = invoice.routes().clone();
let mut last_hops = Vec::new();
for hint in route_hints.drain(..) {
last_hops.push(hint[hint.len() - 1].clone());
}
let amt_msat = invoice.amount_pico_btc().unwrap() / 10;

let first_hops = nodes[0].node.list_usable_channels();
let network_graph = nodes[0].net_graph_msg_handler.network_graph.read().unwrap();
let logger = test_utils::TestLogger::new();
let route = router::get_route(
&nodes[0].node.get_our_node_id(),
&network_graph,
&invoice.recover_payee_pub_key(),
Some(invoice.features().unwrap().clone()),
Some(&first_hops.iter().collect::<Vec<_>>()),
&last_hops.iter().collect::<Vec<_>>(),
amt_msat,
invoice.min_final_cltv_expiry().unwrap() as u32,
&logger,
).unwrap();

let payment_event = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap();
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
assert_eq!(added_monitors.len(), 1);
added_monitors.clear();

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
SendEvent::from_event(events.remove(0))

};
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg);
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
assert_eq!(added_monitors.len(), 1);
added_monitors.clear();
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
}
}
2 changes: 2 additions & 0 deletions lightning-invoice/tests/ser_de.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
extern crate bitcoin_hashes;
extern crate lightning;
extern crate lightning_invoice;
extern crate secp256k1;

use bitcoin_hashes::hex::FromHex;
use bitcoin_hashes::sha256;
use lightning::ln::PaymentSecret;
use lightning_invoice::*;
use secp256k1::Secp256k1;
use secp256k1::key::SecretKey;
Expand Down
4 changes: 0 additions & 4 deletions lightning-persister/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,5 @@ libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winbase"] }

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

[dev-dependencies]
lightning = { version = "0.0.13", path = "../lightning", features = ["_test_utils"] }
10 changes: 5 additions & 5 deletions lightning/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Still missing tons of error-handling. See GitHub issues for suggested projects i
allow_wallclock_use = []
fuzztarget = ["bitcoin/fuzztarget", "regex"]
# Internal test utilities exposed to other repo crates
_test_utils = ["hex", "regex"]
_test_utils = ["hex", "regex", "bitcoin/bitcoinconsensus"]
# Unlog messages superior at targeted level.
max_level_off = []
max_level_error = []
Expand All @@ -32,13 +32,13 @@ bitcoin = "0.26"
hex = { version = "0.3", optional = true }
regex = { version = "0.1.80", optional = true }

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

[dev-dependencies]
hex = "0.3"
regex = "0.1.80"

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

[package.metadata.docs.rs]
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.
Loading