Skip to content

Commit b049a7d

Browse files
authored
Merge pull request #3139 from jkczyz/2024-06-blinded-path-auth
Authenticate use of offer blinded paths
2 parents 2b1d6aa + 825bda0 commit b049a7d

14 files changed

+938
-303
lines changed

lightning/src/blinded_path/message.rs

+57-18
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::io;
2222
use crate::io::Cursor;
2323
use crate::ln::channelmanager::PaymentId;
2424
use crate::ln::onion_utils;
25+
use crate::offers::nonce::Nonce;
2526
use crate::onion_message::packet::ControlTlvs;
2627
use crate::sign::{NodeSigner, Recipient};
2728
use crate::crypto::streams::ChaChaPolyReadAdapter;
@@ -85,37 +86,71 @@ impl Writeable for ReceiveTlvs {
8586
}
8687
}
8788

88-
/// Represents additional data included by the recipient in a [`BlindedPath`].
89+
/// Additional data included by the recipient in a [`BlindedPath`].
8990
///
90-
/// This data is encrypted by the recipient and remains invisible to anyone else.
91-
/// It is included in the [`BlindedPath`], making it accessible again to the recipient
92-
/// whenever the [`BlindedPath`] is used.
93-
/// The recipient can authenticate the message and utilize it for further processing
94-
/// if needed.
91+
/// This data is encrypted by the recipient and will be given to the corresponding message handler
92+
/// when handling a message sent over the [`BlindedPath`]. The recipient can use this data to
93+
/// authenticate the message or for further processing if needed.
9594
#[derive(Clone, Debug)]
9695
pub enum MessageContext {
97-
/// Represents the data specific to [`OffersMessage`]
96+
/// Context specific to an [`OffersMessage`].
9897
///
9998
/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
10099
Offers(OffersContext),
101-
/// Represents custom data received in a Custom Onion Message.
100+
/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
101+
///
102+
/// [`CustomOnionMessageHandler::CustomMessage`]: crate::onion_message::messenger::CustomOnionMessageHandler::CustomMessage
102103
Custom(Vec<u8>),
103104
}
104105

105-
/// Contains the data specific to [`OffersMessage`]
106+
/// Contains data specific to an [`OffersMessage`].
106107
///
107108
/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
108-
#[derive(Clone, Debug)]
109+
#[derive(Clone, Debug, Eq, PartialEq)]
109110
pub enum OffersContext {
110-
/// Represents an unknown BOLT12 payment context.
111-
/// This variant is used when a message is sent without
112-
/// using a [`BlindedPath`] or over one created prior to
113-
/// LDK version 0.0.124.
111+
/// Represents an unknown BOLT12 message context.
112+
///
113+
/// This variant is used when a message is sent without using a [`BlindedPath`] or over one
114+
/// created prior to LDK version 0.0.124.
114115
Unknown {},
115-
/// Represents an outbound BOLT12 payment context.
116+
/// Context used by a [`BlindedPath`] within an [`Offer`].
117+
///
118+
/// This variant is intended to be received when handling an [`InvoiceRequest`].
119+
///
120+
/// [`Offer`]: crate::offers::offer::Offer
121+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
122+
InvoiceRequest {
123+
/// A nonce used for authenticating that an [`InvoiceRequest`] is for a valid [`Offer`] and
124+
/// for deriving the offer's signing keys.
125+
///
126+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
127+
/// [`Offer`]: crate::offers::offer::Offer
128+
nonce: Nonce,
129+
},
130+
/// Context used by a [`BlindedPath`] within a [`Refund`] or as a reply path for an
131+
/// [`InvoiceRequest`].
132+
///
133+
/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
134+
/// [`InvoiceError`].
135+
///
136+
/// [`Refund`]: crate::offers::refund::Refund
137+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
138+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
139+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
116140
OutboundPayment {
117-
/// Payment ID of the outbound BOLT12 payment.
118-
payment_id: PaymentId
141+
/// Payment ID used when creating a [`Refund`] or [`InvoiceRequest`].
142+
///
143+
/// [`Refund`]: crate::offers::refund::Refund
144+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
145+
payment_id: PaymentId,
146+
147+
/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or
148+
/// [`InvoiceRequest`] and for deriving their signing keys.
149+
///
150+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
151+
/// [`Refund`]: crate::offers::refund::Refund
152+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
153+
nonce: Nonce,
119154
},
120155
}
121156

@@ -126,8 +161,12 @@ impl_writeable_tlv_based_enum!(MessageContext,
126161

127162
impl_writeable_tlv_based_enum!(OffersContext,
128163
(0, Unknown) => {},
129-
(1, OutboundPayment) => {
164+
(1, InvoiceRequest) => {
165+
(0, nonce, required),
166+
},
167+
(2, OutboundPayment) => {
130168
(0, payment_id, required),
169+
(1, nonce, required),
131170
},
132171
);
133172

lightning/src/events/mod.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod bump_transaction;
1818

1919
pub use bump_transaction::BumpTransactionEvent;
2020

21+
use crate::blinded_path::message::OffersContext;
2122
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef};
2223
use crate::chain::transaction;
2324
use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
@@ -806,6 +807,10 @@ pub enum Event {
806807
payment_id: PaymentId,
807808
/// The invoice to pay.
808809
invoice: Bolt12Invoice,
810+
/// The context of the [`BlindedPath`] used to send the invoice.
811+
///
812+
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
813+
context: OffersContext,
809814
/// A responder for replying with an [`InvoiceError`] if needed.
810815
///
811816
/// `None` if the invoice wasn't sent with a reply path.
@@ -1648,12 +1653,13 @@ impl Writeable for Event {
16481653
(0, peer_node_id, required),
16491654
});
16501655
},
1651-
&Event::InvoiceReceived { ref payment_id, ref invoice, ref responder } => {
1656+
&Event::InvoiceReceived { ref payment_id, ref invoice, ref context, ref responder } => {
16521657
41u8.write(writer)?;
16531658
write_tlv_fields!(writer, {
16541659
(0, payment_id, required),
16551660
(2, invoice, required),
1656-
(4, responder, option),
1661+
(4, context, required),
1662+
(6, responder, option),
16571663
});
16581664
},
16591665
&Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => {
@@ -2107,11 +2113,13 @@ impl MaybeReadable for Event {
21072113
_init_and_read_len_prefixed_tlv_fields!(reader, {
21082114
(0, payment_id, required),
21092115
(2, invoice, required),
2110-
(4, responder, option),
2116+
(4, context, required),
2117+
(6, responder, option),
21112118
});
21122119
Ok(Some(Event::InvoiceReceived {
21132120
payment_id: payment_id.0.unwrap(),
21142121
invoice: invoice.0.unwrap(),
2122+
context: context.0.unwrap(),
21152123
responder,
21162124
}))
21172125
};

lightning/src/ln/channelmanager.rs

+84-41
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use crate::ln::wire::Encode;
6464
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
6565
use crate::offers::invoice_error::InvoiceError;
6666
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
67+
use crate::offers::nonce::Nonce;
6768
use crate::offers::offer::{Offer, OfferBuilder};
6869
use crate::offers::parse::Bolt12SemanticError;
6970
use crate::offers::refund::{Refund, RefundBuilder};
@@ -2254,7 +2255,10 @@ where
22542255
event_persist_notifier: Notifier,
22552256
needs_persist_flag: AtomicBool,
22562257

2258+
#[cfg(not(any(test, feature = "_test_utils")))]
22572259
pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
2260+
#[cfg(any(test, feature = "_test_utils"))]
2261+
pub(crate) pending_offers_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
22582262

22592263
/// Tracks the message events that are to be broadcasted when we are connected to some peer.
22602264
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
@@ -4199,15 +4203,35 @@ where
41994203
/// whether or not the payment was successful.
42004204
///
42014205
/// [timer tick]: Self::timer_tick_occurred
4202-
pub fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice) -> Result<(), Bolt12PaymentError> {
4203-
let secp_ctx = &self.secp_ctx;
4204-
let expanded_key = &self.inbound_payment_key;
4205-
match invoice.verify(expanded_key, secp_ctx) {
4206+
pub fn send_payment_for_bolt12_invoice(
4207+
&self, invoice: &Bolt12Invoice, context: &OffersContext,
4208+
) -> Result<(), Bolt12PaymentError> {
4209+
match self.verify_bolt12_invoice(invoice, context) {
42064210
Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
42074211
Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
42084212
}
42094213
}
42104214

4215+
fn verify_bolt12_invoice(
4216+
&self, invoice: &Bolt12Invoice, context: &OffersContext,
4217+
) -> Result<PaymentId, ()> {
4218+
let secp_ctx = &self.secp_ctx;
4219+
let expanded_key = &self.inbound_payment_key;
4220+
4221+
match context {
4222+
OffersContext::Unknown {} if invoice.is_for_refund_without_paths() => {
4223+
invoice.verify_using_metadata(expanded_key, secp_ctx)
4224+
},
4225+
OffersContext::OutboundPayment { payment_id, nonce } => {
4226+
invoice
4227+
.verify_using_payer_data(*payment_id, *nonce, expanded_key, secp_ctx)
4228+
.then(|| *payment_id)
4229+
.ok_or(())
4230+
},
4231+
_ => Err(()),
4232+
}
4233+
}
4234+
42114235
fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
42124236
let best_block_height = self.best_block.read().unwrap().height;
42134237
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
@@ -8784,13 +8808,12 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
87848808
let entropy = &*$self.entropy_source;
87858809
let secp_ctx = &$self.secp_ctx;
87868810

8787-
let path = $self.create_blinded_paths_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry)
8811+
let nonce = Nonce::from_entropy_source(entropy);
8812+
let context = OffersContext::InvoiceRequest { nonce };
8813+
let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry)
87888814
.and_then(|paths| paths.into_iter().next().ok_or(()))
87898815
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
8790-
8791-
let builder = OfferBuilder::deriving_signing_pubkey(
8792-
node_id, expanded_key, entropy, secp_ctx
8793-
)
8816+
let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx)
87948817
.chain_hash($self.chain_hash)
87958818
.path(path);
87968819

@@ -8858,13 +8881,14 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
88588881
let entropy = &*$self.entropy_source;
88598882
let secp_ctx = &$self.secp_ctx;
88608883

8861-
let context = OffersContext::OutboundPayment { payment_id };
8884+
let nonce = Nonce::from_entropy_source(entropy);
8885+
let context = OffersContext::OutboundPayment { payment_id, nonce };
88628886
let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry))
88638887
.and_then(|paths| paths.into_iter().next().ok_or(()))
88648888
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
88658889

88668890
let builder = RefundBuilder::deriving_payer_id(
8867-
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
8891+
node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id
88688892
)?
88698893
.chain_hash($self.chain_hash)
88708894
.absolute_expiry(absolute_expiry)
@@ -8973,8 +8997,9 @@ where
89738997
let entropy = &*self.entropy_source;
89748998
let secp_ctx = &self.secp_ctx;
89758999

9000+
let nonce = Nonce::from_entropy_source(entropy);
89769001
let builder: InvoiceRequestBuilder<DerivedPayerId, secp256k1::All> = offer
8977-
.request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
9002+
.request_invoice_deriving_payer_id(expanded_key, nonce, secp_ctx, payment_id)?
89789003
.into();
89799004
let builder = builder.chain_hash(self.chain_hash)?;
89809005

@@ -8992,8 +9017,9 @@ where
89929017
};
89939018
let invoice_request = builder.build_and_sign()?;
89949019

8995-
let context = OffersContext::OutboundPayment { payment_id };
8996-
let reply_paths = self.create_blinded_paths(context).map_err(|_| Bolt12SemanticError::MissingPaths)?;
9020+
let context = OffersContext::OutboundPayment { payment_id, nonce };
9021+
let reply_paths = self.create_blinded_paths(context)
9022+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
89979023

89989024
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
89999025

@@ -10692,7 +10718,7 @@ where
1069210718

1069310719
let abandon_if_payment = |context| {
1069410720
match context {
10695-
OffersContext::OutboundPayment { payment_id } => self.abandon_payment(payment_id),
10721+
OffersContext::OutboundPayment { payment_id, .. } => self.abandon_payment(payment_id),
1069610722
_ => {},
1069710723
}
1069810724
};
@@ -10703,19 +10729,32 @@ where
1070310729
Some(responder) => responder,
1070410730
None => return ResponseInstruction::NoResponse,
1070510731
};
10732+
10733+
let nonce = match context {
10734+
OffersContext::Unknown {} if invoice_request.metadata().is_some() => None,
10735+
OffersContext::InvoiceRequest { nonce } => Some(nonce),
10736+
_ => return ResponseInstruction::NoResponse,
10737+
};
10738+
10739+
let invoice_request = match nonce {
10740+
Some(nonce) => match invoice_request.verify_using_recipient_data(
10741+
nonce, expanded_key, secp_ctx,
10742+
) {
10743+
Ok(invoice_request) => invoice_request,
10744+
Err(()) => return ResponseInstruction::NoResponse,
10745+
},
10746+
None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) {
10747+
Ok(invoice_request) => invoice_request,
10748+
Err(()) => return ResponseInstruction::NoResponse,
10749+
},
10750+
};
10751+
1070610752
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
10707-
&invoice_request
10753+
&invoice_request.inner
1070810754
) {
1070910755
Ok(amount_msats) => amount_msats,
1071010756
Err(error) => return responder.respond(OffersMessage::InvoiceError(error.into())),
1071110757
};
10712-
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
10713-
Ok(invoice_request) => invoice_request,
10714-
Err(()) => {
10715-
let error = Bolt12SemanticError::InvalidMetadata;
10716-
return responder.respond(OffersMessage::InvoiceError(error.into()));
10717-
},
10718-
};
1071910758

1072010759
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
1072110760
let (payment_hash, payment_secret) = match self.create_inbound_payment(
@@ -10788,24 +10827,28 @@ where
1078810827
}
1078910828
},
1079010829
OffersMessage::Invoice(invoice) => {
10791-
let result = match invoice.verify(expanded_key, secp_ctx) {
10792-
Ok(payment_id) => {
10793-
let features = self.bolt12_invoice_features();
10794-
if invoice.invoice_features().requires_unknown_bits_from(&features) {
10795-
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
10796-
} else if self.default_configuration.manually_handle_bolt12_invoices {
10797-
let event = Event::InvoiceReceived { payment_id, invoice, responder };
10798-
self.pending_events.lock().unwrap().push_back((event, None));
10799-
return ResponseInstruction::NoResponse;
10800-
} else {
10801-
self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
10802-
.map_err(|e| {
10803-
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
10804-
InvoiceError::from_string(format!("{:?}", e))
10805-
})
10806-
}
10807-
},
10808-
Err(()) => Err(InvoiceError::from_string("Unrecognized invoice".to_owned())),
10830+
let payment_id = match self.verify_bolt12_invoice(&invoice, &context) {
10831+
Ok(payment_id) => payment_id,
10832+
Err(()) => return ResponseInstruction::NoResponse,
10833+
};
10834+
10835+
let result = {
10836+
let features = self.bolt12_invoice_features();
10837+
if invoice.invoice_features().requires_unknown_bits_from(&features) {
10838+
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
10839+
} else if self.default_configuration.manually_handle_bolt12_invoices {
10840+
let event = Event::InvoiceReceived {
10841+
payment_id, invoice, context, responder,
10842+
};
10843+
self.pending_events.lock().unwrap().push_back((event, None));
10844+
return ResponseInstruction::NoResponse;
10845+
} else {
10846+
self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
10847+
.map_err(|e| {
10848+
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
10849+
InvoiceError::from_string(format!("{:?}", e))
10850+
})
10851+
}
1080910852
};
1081010853

1081110854
match result {

0 commit comments

Comments
 (0)