Skip to content

Commit

Permalink
Merge pull request #3139 from jkczyz/2024-06-blinded-path-auth
Browse files Browse the repository at this point in the history
Authenticate use of offer blinded paths
  • Loading branch information
TheBlueMatt committed Jul 22, 2024
2 parents 2b1d6aa + 825bda0 commit b049a7d
Show file tree
Hide file tree
Showing 14 changed files with 938 additions and 303 deletions.
75 changes: 57 additions & 18 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::io;
use crate::io::Cursor;
use crate::ln::channelmanager::PaymentId;
use crate::ln::onion_utils;
use crate::offers::nonce::Nonce;
use crate::onion_message::packet::ControlTlvs;
use crate::sign::{NodeSigner, Recipient};
use crate::crypto::streams::ChaChaPolyReadAdapter;
Expand Down Expand Up @@ -85,37 +86,71 @@ impl Writeable for ReceiveTlvs {
}
}

/// Represents additional data included by the recipient in a [`BlindedPath`].
/// Additional data included by the recipient in a [`BlindedPath`].
///
/// This data is encrypted by the recipient and remains invisible to anyone else.
/// It is included in the [`BlindedPath`], making it accessible again to the recipient
/// whenever the [`BlindedPath`] is used.
/// The recipient can authenticate the message and utilize it for further processing
/// if needed.
/// This data is encrypted by the recipient and will be given to the corresponding message handler
/// when handling a message sent over the [`BlindedPath`]. The recipient can use this data to
/// authenticate the message or for further processing if needed.
#[derive(Clone, Debug)]
pub enum MessageContext {
/// Represents the data specific to [`OffersMessage`]
/// Context specific to an [`OffersMessage`].
///
/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
Offers(OffersContext),
/// Represents custom data received in a Custom Onion Message.
/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
///
/// [`CustomOnionMessageHandler::CustomMessage`]: crate::onion_message::messenger::CustomOnionMessageHandler::CustomMessage
Custom(Vec<u8>),
}

/// Contains the data specific to [`OffersMessage`]
/// Contains data specific to an [`OffersMessage`].
///
/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OffersContext {
/// Represents an unknown BOLT12 payment context.
/// This variant is used when a message is sent without
/// using a [`BlindedPath`] or over one created prior to
/// LDK version 0.0.124.
/// Represents an unknown BOLT12 message context.
///
/// This variant is used when a message is sent without using a [`BlindedPath`] or over one
/// created prior to LDK version 0.0.124.
Unknown {},
/// Represents an outbound BOLT12 payment context.
/// Context used by a [`BlindedPath`] within an [`Offer`].
///
/// This variant is intended to be received when handling an [`InvoiceRequest`].
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
InvoiceRequest {
/// A nonce used for authenticating that an [`InvoiceRequest`] is for a valid [`Offer`] and
/// for deriving the offer's signing keys.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Offer`]: crate::offers::offer::Offer
nonce: Nonce,
},
/// Context used by a [`BlindedPath`] within a [`Refund`] or as a reply path for an
/// [`InvoiceRequest`].
///
/// This variant is intended to be received when handling a [`Bolt12Invoice`] or an
/// [`InvoiceError`].
///
/// [`Refund`]: crate::offers::refund::Refund
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
OutboundPayment {
/// Payment ID of the outbound BOLT12 payment.
payment_id: PaymentId
/// Payment ID used when creating a [`Refund`] or [`InvoiceRequest`].
///
/// [`Refund`]: crate::offers::refund::Refund
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
payment_id: PaymentId,

/// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] or
/// [`InvoiceRequest`] and for deriving their signing keys.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Refund`]: crate::offers::refund::Refund
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
nonce: Nonce,
},
}

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

impl_writeable_tlv_based_enum!(OffersContext,
(0, Unknown) => {},
(1, OutboundPayment) => {
(1, InvoiceRequest) => {
(0, nonce, required),
},
(2, OutboundPayment) => {
(0, payment_id, required),
(1, nonce, required),
},
);

Expand Down
14 changes: 11 additions & 3 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod bump_transaction;

pub use bump_transaction::BumpTransactionEvent;

use crate::blinded_path::message::OffersContext;
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef};
use crate::chain::transaction;
use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
Expand Down Expand Up @@ -806,6 +807,10 @@ pub enum Event {
payment_id: PaymentId,
/// The invoice to pay.
invoice: Bolt12Invoice,
/// The context of the [`BlindedPath`] used to send the invoice.
///
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
context: OffersContext,
/// A responder for replying with an [`InvoiceError`] if needed.
///
/// `None` if the invoice wasn't sent with a reply path.
Expand Down Expand Up @@ -1648,12 +1653,13 @@ impl Writeable for Event {
(0, peer_node_id, required),
});
},
&Event::InvoiceReceived { ref payment_id, ref invoice, ref responder } => {
&Event::InvoiceReceived { ref payment_id, ref invoice, ref context, ref responder } => {
41u8.write(writer)?;
write_tlv_fields!(writer, {
(0, payment_id, required),
(2, invoice, required),
(4, responder, option),
(4, context, required),
(6, responder, option),
});
},
&Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => {
Expand Down Expand Up @@ -2107,11 +2113,13 @@ impl MaybeReadable for Event {
_init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_id, required),
(2, invoice, required),
(4, responder, option),
(4, context, required),
(6, responder, option),
});
Ok(Some(Event::InvoiceReceived {
payment_id: payment_id.0.unwrap(),
invoice: invoice.0.unwrap(),
context: context.0.unwrap(),
responder,
}))
};
Expand Down
125 changes: 84 additions & 41 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use crate::ln::wire::Encode;
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
Expand Down Expand Up @@ -2254,7 +2255,10 @@ where
event_persist_notifier: Notifier,
needs_persist_flag: AtomicBool,

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

/// Tracks the message events that are to be broadcasted when we are connected to some peer.
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
Expand Down Expand Up @@ -4199,15 +4203,35 @@ where
/// whether or not the payment was successful.
///
/// [timer tick]: Self::timer_tick_occurred
pub fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice) -> Result<(), Bolt12PaymentError> {
let secp_ctx = &self.secp_ctx;
let expanded_key = &self.inbound_payment_key;
match invoice.verify(expanded_key, secp_ctx) {
pub fn send_payment_for_bolt12_invoice(
&self, invoice: &Bolt12Invoice, context: &OffersContext,
) -> Result<(), Bolt12PaymentError> {
match self.verify_bolt12_invoice(invoice, context) {
Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
}
}

fn verify_bolt12_invoice(
&self, invoice: &Bolt12Invoice, context: &OffersContext,
) -> Result<PaymentId, ()> {
let secp_ctx = &self.secp_ctx;
let expanded_key = &self.inbound_payment_key;

match context {
OffersContext::Unknown {} if invoice.is_for_refund_without_paths() => {
invoice.verify_using_metadata(expanded_key, secp_ctx)
},
OffersContext::OutboundPayment { payment_id, nonce } => {
invoice
.verify_using_payer_data(*payment_id, *nonce, expanded_key, secp_ctx)
.then(|| *payment_id)
.ok_or(())
},
_ => Err(()),
}
}

fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
Expand Down Expand Up @@ -8784,13 +8808,12 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
let entropy = &*$self.entropy_source;
let secp_ctx = &$self.secp_ctx;

let path = $self.create_blinded_paths_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry)
let nonce = Nonce::from_entropy_source(entropy);
let context = OffersContext::InvoiceRequest { nonce };
let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry)
.and_then(|paths| paths.into_iter().next().ok_or(()))
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let builder = OfferBuilder::deriving_signing_pubkey(
node_id, expanded_key, entropy, secp_ctx
)
let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx)
.chain_hash($self.chain_hash)
.path(path);

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

let context = OffersContext::OutboundPayment { payment_id };
let nonce = Nonce::from_entropy_source(entropy);
let context = OffersContext::OutboundPayment { payment_id, nonce };
let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry))
.and_then(|paths| paths.into_iter().next().ok_or(()))
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let builder = RefundBuilder::deriving_payer_id(
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id
)?
.chain_hash($self.chain_hash)
.absolute_expiry(absolute_expiry)
Expand Down Expand Up @@ -8973,8 +8997,9 @@ where
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;

let nonce = Nonce::from_entropy_source(entropy);
let builder: InvoiceRequestBuilder<DerivedPayerId, secp256k1::All> = offer
.request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
.request_invoice_deriving_payer_id(expanded_key, nonce, secp_ctx, payment_id)?
.into();
let builder = builder.chain_hash(self.chain_hash)?;

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

let context = OffersContext::OutboundPayment { payment_id };
let reply_paths = self.create_blinded_paths(context).map_err(|_| Bolt12SemanticError::MissingPaths)?;
let context = OffersContext::OutboundPayment { payment_id, nonce };
let reply_paths = self.create_blinded_paths(context)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);

Expand Down Expand Up @@ -10692,7 +10718,7 @@ where

let abandon_if_payment = |context| {
match context {
OffersContext::OutboundPayment { payment_id } => self.abandon_payment(payment_id),
OffersContext::OutboundPayment { payment_id, .. } => self.abandon_payment(payment_id),
_ => {},
}
};
Expand All @@ -10703,19 +10729,32 @@ where
Some(responder) => responder,
None => return ResponseInstruction::NoResponse,
};

let nonce = match context {
OffersContext::Unknown {} if invoice_request.metadata().is_some() => None,
OffersContext::InvoiceRequest { nonce } => Some(nonce),
_ => return ResponseInstruction::NoResponse,
};

let invoice_request = match nonce {
Some(nonce) => match invoice_request.verify_using_recipient_data(
nonce, expanded_key, secp_ctx,
) {
Ok(invoice_request) => invoice_request,
Err(()) => return ResponseInstruction::NoResponse,
},
None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) {
Ok(invoice_request) => invoice_request,
Err(()) => return ResponseInstruction::NoResponse,
},
};

let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
&invoice_request
&invoice_request.inner
) {
Ok(amount_msats) => amount_msats,
Err(error) => return responder.respond(OffersMessage::InvoiceError(error.into())),
};
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
Ok(invoice_request) => invoice_request,
Err(()) => {
let error = Bolt12SemanticError::InvalidMetadata;
return responder.respond(OffersMessage::InvoiceError(error.into()));
},
};

let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
let (payment_hash, payment_secret) = match self.create_inbound_payment(
Expand Down Expand Up @@ -10788,24 +10827,28 @@ where
}
},
OffersMessage::Invoice(invoice) => {
let result = match invoice.verify(expanded_key, secp_ctx) {
Ok(payment_id) => {
let features = self.bolt12_invoice_features();
if invoice.invoice_features().requires_unknown_bits_from(&features) {
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
} else if self.default_configuration.manually_handle_bolt12_invoices {
let event = Event::InvoiceReceived { payment_id, invoice, responder };
self.pending_events.lock().unwrap().push_back((event, None));
return ResponseInstruction::NoResponse;
} else {
self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
.map_err(|e| {
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
InvoiceError::from_string(format!("{:?}", e))
})
}
},
Err(()) => Err(InvoiceError::from_string("Unrecognized invoice".to_owned())),
let payment_id = match self.verify_bolt12_invoice(&invoice, &context) {
Ok(payment_id) => payment_id,
Err(()) => return ResponseInstruction::NoResponse,
};

let result = {
let features = self.bolt12_invoice_features();
if invoice.invoice_features().requires_unknown_bits_from(&features) {
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
} else if self.default_configuration.manually_handle_bolt12_invoices {
let event = Event::InvoiceReceived {
payment_id, invoice, context, responder,
};
self.pending_events.lock().unwrap().push_back((event, None));
return ResponseInstruction::NoResponse;
} else {
self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
.map_err(|e| {
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
InvoiceError::from_string(format!("{:?}", e))
})
}
};

match result {
Expand Down
Loading

0 comments on commit b049a7d

Please sign in to comment.