Skip to content

Commit 1476143

Browse files
committed
OffersMessageHandler impl for ChannelManager
Define the BOLT 12 message flow in ChannelManager's OffersMessageHandler implementation. - An invoice_request message results in responding with an invoice message if it can be verified that the request is for a valid offer. - An invoice is paid if it can be verified to have originated from a sent invoice_request or a refund. - An invoice_error is sent in some failure cases.
1 parent 3f485c8 commit 1476143

File tree

3 files changed

+146
-4
lines changed

3 files changed

+146
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5555
use crate::ln::outbound_payment;
5656
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment};
5757
use crate::ln::wire::Encode;
58+
use crate::offers::invoice::{DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
59+
use crate::offers::invoice_error::InvoiceError;
60+
use crate::offers::merkle::SignError;
61+
use crate::offers::parse::SemanticError;
62+
use crate::onion_message::{OffersMessage, OffersMessageHandler};
5863
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, ChannelSigner, WriteableEcdsaChannelSigner};
5964
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
6065
use crate::util::wakers::{Future, Notifier};
@@ -7214,6 +7219,133 @@ where
72147219
}
72157220
}
72167221

7222+
const BOLT_12_INVOICE_RETRY_STRATEGY: Retry = Retry::Attempts(3);
7223+
7224+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
7225+
OffersMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
7226+
where
7227+
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
7228+
T::Target: BroadcasterInterface,
7229+
ES::Target: EntropySource,
7230+
NS::Target: NodeSigner,
7231+
SP::Target: SignerProvider,
7232+
F::Target: FeeEstimator,
7233+
R::Target: Router,
7234+
L::Target: Logger,
7235+
{
7236+
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
7237+
let secp_ctx = &self.secp_ctx;
7238+
let expanded_key = &self.inbound_payment_key;
7239+
7240+
match message {
7241+
OffersMessage::InvoiceRequest(invoice_request) => {
7242+
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
7243+
&invoice_request
7244+
) {
7245+
Ok(amount_msats) => Some(amount_msats),
7246+
Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
7247+
};
7248+
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
7249+
Ok(invoice_request) => invoice_request,
7250+
Err(()) => {
7251+
let error = SemanticError::InvalidMetadata;
7252+
return Some(OffersMessage::InvoiceError(error.into()));
7253+
},
7254+
};
7255+
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
7256+
7257+
match self.create_inbound_payment(amount_msats, relative_expiry, None) {
7258+
Ok((payment_hash, _payment_secret)) if invoice_request.keys.is_some() => {
7259+
// TODO: Include payment_secret in payment_paths.
7260+
let payment_paths = vec![];
7261+
#[cfg(not(feature = "no-std"))]
7262+
let builder = invoice_request.respond_using_derived_keys(
7263+
payment_paths, payment_hash
7264+
);
7265+
#[cfg(feature = "no-std")]
7266+
let created_at = Duration::from_secs(
7267+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
7268+
);
7269+
#[cfg(feature = "no-std")]
7270+
let builder = invoice_request.respond_using_derived_keys_no_std(
7271+
payment_paths, payment_hash, created_at
7272+
);
7273+
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
7274+
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
7275+
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
7276+
}
7277+
},
7278+
Ok((payment_hash, _payment_secret)) => {
7279+
// TODO: Include payment_secret in payment_paths.
7280+
let payment_paths = vec![];
7281+
#[cfg(not(feature = "no-std"))]
7282+
let builder = invoice_request.respond_with(payment_paths, payment_hash);
7283+
#[cfg(feature = "no-std")]
7284+
let created_at = Duration::from_secs(
7285+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
7286+
);
7287+
#[cfg(feature = "no-std")]
7288+
let builder = invoice_request.respond_with_no_std(
7289+
payment_paths, payment_hash, created_at
7290+
);
7291+
let response = builder.and_then(|builder| builder.allow_mpp().build())
7292+
.map_err(|e| OffersMessage::InvoiceError(e.into()))
7293+
.and_then(|invoice|
7294+
match invoice.sign(|digest, tag, bytes, metadata|
7295+
self.node_signer.sign_bolt12_message(digest, tag, bytes, metadata)
7296+
) {
7297+
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
7298+
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
7299+
InvoiceError::from_str("Failed signing invoice")
7300+
)),
7301+
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
7302+
InvoiceError::from_str("Failed invoice signature verification")
7303+
)),
7304+
});
7305+
match response {
7306+
Ok(invoice) => Some(invoice),
7307+
Err(error) => Some(error),
7308+
}
7309+
},
7310+
Err(()) => {
7311+
Some(OffersMessage::InvoiceError(SemanticError::InvalidAmount.into()))
7312+
},
7313+
}
7314+
},
7315+
OffersMessage::Invoice(invoice) => {
7316+
if !invoice.verify(expanded_key, secp_ctx) {
7317+
Some(OffersMessage::InvoiceError(InvoiceError::from_str("Unrecognized invoice")))
7318+
} else if invoice.features().requires_unknown_bits() {
7319+
Some(OffersMessage::InvoiceError(SemanticError::UnknownRequiredFeatures.into()))
7320+
} else {
7321+
let recipient_onion = RecipientOnionFields {
7322+
payment_secret: None,
7323+
payment_metadata: None,
7324+
};
7325+
let payment_hash = invoice.payment_hash();
7326+
let payment_id = PaymentId(payment_hash.0);
7327+
let route_params = RouteParameters {
7328+
payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
7329+
final_value_msat: invoice.amount_msats(),
7330+
};
7331+
if let Err(e) = self.send_payment(
7332+
payment_hash, recipient_onion, payment_id, route_params,
7333+
BOLT_12_INVOICE_RETRY_STRATEGY
7334+
) {
7335+
// TODO: Should we send an invoice_error?
7336+
log_error!(self.logger, "Failed paying invoice: {:?}", e);
7337+
}
7338+
None
7339+
}
7340+
},
7341+
OffersMessage::InvoiceError(invoice_error) => {
7342+
log_error!(self.logger, "Received invoice_error: {}", invoice_error);
7343+
None
7344+
},
7345+
}
7346+
}
7347+
}
7348+
72177349
/// Fetches the set of [`NodeFeatures`] flags which are provided by or required by
72187350
/// [`ChannelManager`].
72197351
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {

lightning/src/offers/invoice.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ use crate::prelude::*;
124124
#[cfg(feature = "std")]
125125
use std::time::SystemTime;
126126

127-
const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
127+
pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
128128

129129
pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
130130

@@ -169,7 +169,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
169169
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
170170
created_at: Duration, payment_hash: PaymentHash
171171
) -> Result<Self, SemanticError> {
172-
let amount_msats = Self::check_amount_msats(invoice_request)?;
172+
let amount_msats = Self::amount_msats(invoice_request)?;
173173
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
174174
let contents = InvoiceContents::ForOffer {
175175
invoice_request: invoice_request.contents.clone(),
@@ -202,7 +202,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
202202
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
203203
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
204204
) -> Result<Self, SemanticError> {
205-
let amount_msats = Self::check_amount_msats(invoice_request)?;
205+
let amount_msats = Self::amount_msats(invoice_request)?;
206206
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
207207
let contents = InvoiceContents::ForOffer {
208208
invoice_request: invoice_request.contents.clone(),
@@ -232,7 +232,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
232232
}
233233

234234
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
235-
fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
235+
pub(crate) fn amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
236236
match invoice_request.amount_msats() {
237237
Some(amount_msats) => Ok(amount_msats),
238238
None => match invoice_request.contents.inner.offer.amount() {

lightning/src/offers/invoice_error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ pub struct ErroneousField {
4848
pub suggested_value: Option<Vec<u8>>,
4949
}
5050

51+
impl InvoiceError {
52+
/// Creates an [`InvoiceError`] with the given message.
53+
pub fn from_str(s: &str) -> Self {
54+
Self {
55+
erroneous_field: None,
56+
message: UntrustedString(s.to_string()),
57+
}
58+
}
59+
}
60+
5161
impl core::fmt::Display for InvoiceError {
5262
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
5363
self.message.fmt(f)

0 commit comments

Comments
 (0)