diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 54b842febc9..d418cbe51d8 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -10,15 +10,22 @@ use crate::utils::test_logger; use bitcoin::secp256k1::{self, Keypair, Parity, PublicKey, Secp256k1, SecretKey}; use core::convert::TryFrom; -use lightning::blinded_path::message::{ForwardNode, MessageContext, OffersContext}; +use lightning::blinded_path::payment::{ + Bolt12OfferContext, ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, + ReceiveTlvs, +}; use lightning::blinded_path::BlindedPath; +use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; use lightning::ln::features::BlindedHopFeatures; +use lightning::ln::types::PaymentSecret; use lightning::ln::PaymentHash; -use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; -use lightning::offers::invoice_request::InvoiceRequest; +use lightning::offers::invoice::UnsignedBolt12Invoice; +use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; +use lightning::offers::offer::OfferId; use lightning::offers::parse::Bolt12SemanticError; use lightning::sign::EntropySource; use lightning::util::ser::Writeable; +use lightning::util::string::UntrustedString; #[inline] pub fn do_test(data: &[u8], _out: Out) { @@ -76,57 +83,54 @@ fn build_response( invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1, ) -> Result { let entropy_source = Randomness {}; - let intermediate_nodes = [ - [ - ForwardNode { node_id: pubkey(43), short_channel_id: None }, - ForwardNode { node_id: pubkey(44), short_channel_id: None }, - ], - [ - ForwardNode { node_id: pubkey(45), short_channel_id: None }, - ForwardNode { node_id: pubkey(46), short_channel_id: None }, - ], - ]; - let paths = vec![ - BlindedPath::new_for_message( - &intermediate_nodes[0], - pubkey(42), - MessageContext::Offers(OffersContext::Unknown {}), - &entropy_source, - secp_ctx, - ) - .unwrap(), - BlindedPath::new_for_message( - &intermediate_nodes[1], - pubkey(42), - MessageContext::Offers(OffersContext::Unknown {}), - &entropy_source, - secp_ctx, - ) - .unwrap(), - ]; - - let payinfo = vec![ - BlindedPayInfo { - fee_base_msat: 1, - fee_proportional_millionths: 1_000, - cltv_expiry_delta: 42, - htlc_minimum_msat: 100, - htlc_maximum_msat: 1_000_000_000_000, - features: BlindedHopFeatures::empty(), + let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { + offer_id: OfferId([42; 32]), + invoice_request: InvoiceRequestFields { + payer_id: invoice_request.payer_id(), + quantity: invoice_request.quantity(), + payer_note_truncated: invoice_request + .payer_note() + .map(|s| UntrustedString(s.to_string())), + }, + }); + let payee_tlvs = ReceiveTlvs { + payment_secret: PaymentSecret([42; 32]), + payment_constraints: PaymentConstraints { + max_cltv_expiry: 1_000_000, + htlc_minimum_msat: 1, }, - BlindedPayInfo { - fee_base_msat: 1, - fee_proportional_millionths: 1_000, - cltv_expiry_delta: 42, - htlc_minimum_msat: 100, - htlc_maximum_msat: 1_000_000_000_000, + payment_context, + }; + let intermediate_nodes = [ForwardNode { + tlvs: ForwardTlvs { + short_channel_id: 43, + payment_relay: PaymentRelay { + cltv_expiry_delta: 40, + fee_proportional_millionths: 1_000, + fee_base_msat: 1, + }, + payment_constraints: PaymentConstraints { + max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40, + htlc_minimum_msat: 100, + }, features: BlindedHopFeatures::empty(), }, - ]; + node_id: pubkey(43), + htlc_maximum_msat: 1_000_000_000_000, + }]; + let payment_path = BlindedPath::new_for_payment( + &intermediate_nodes, + pubkey(42), + payee_tlvs, + u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, + &entropy_source, + secp_ctx, + ) + .unwrap(); - let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect(); let payment_hash = PaymentHash([42; 32]); - invoice_request.respond_with(payment_paths, payment_hash)?.build() + invoice_request.respond_with(vec![payment_path], payment_hash)?.build() } pub fn invoice_request_deser_test(data: &[u8], out: Out) { diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 05ee7526faa..490b7d72a17 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -106,7 +106,8 @@ struct TestOffersMessageHandler {} impl OffersMessageHandler for TestOffersMessageHandler { fn handle_message( - &self, _message: OffersMessage, _context: OffersContext, _responder: Option, + &self, _message: OffersMessage, _context: Option, + _responder: Option, ) -> ResponseInstruction { ResponseInstruction::NoResponse } diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 8e9e6442f47..5a692280683 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -10,11 +10,16 @@ use crate::utils::test_logger; use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1, SecretKey}; use core::convert::TryFrom; -use lightning::blinded_path::message::{ForwardNode, MessageContext, OffersContext}; +use lightning::blinded_path::payment::{ + Bolt12RefundContext, ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, + PaymentRelay, ReceiveTlvs, +}; use lightning::blinded_path::BlindedPath; +use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; use lightning::ln::features::BlindedHopFeatures; +use lightning::ln::types::PaymentSecret; use lightning::ln::PaymentHash; -use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; +use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::parse::Bolt12SemanticError; use lightning::offers::refund::Refund; use lightning::sign::EntropySource; @@ -65,57 +70,45 @@ fn build_response( refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1, ) -> Result { let entropy_source = Randomness {}; - let intermediate_nodes = [ - [ - ForwardNode { node_id: pubkey(43), short_channel_id: None }, - ForwardNode { node_id: pubkey(44), short_channel_id: None }, - ], - [ - ForwardNode { node_id: pubkey(45), short_channel_id: None }, - ForwardNode { node_id: pubkey(46), short_channel_id: None }, - ], - ]; - let paths = vec![ - BlindedPath::new_for_message( - &intermediate_nodes[0], - pubkey(42), - MessageContext::Offers(OffersContext::Unknown {}), - &entropy_source, - secp_ctx, - ) - .unwrap(), - BlindedPath::new_for_message( - &intermediate_nodes[1], - pubkey(42), - MessageContext::Offers(OffersContext::Unknown {}), - &entropy_source, - secp_ctx, - ) - .unwrap(), - ]; - - let payinfo = vec![ - BlindedPayInfo { - fee_base_msat: 1, - fee_proportional_millionths: 1_000, - cltv_expiry_delta: 42, - htlc_minimum_msat: 100, - htlc_maximum_msat: 1_000_000_000_000, - features: BlindedHopFeatures::empty(), + let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); + let payee_tlvs = ReceiveTlvs { + payment_secret: PaymentSecret([42; 32]), + payment_constraints: PaymentConstraints { + max_cltv_expiry: 1_000_000, + htlc_minimum_msat: 1, }, - BlindedPayInfo { - fee_base_msat: 1, - fee_proportional_millionths: 1_000, - cltv_expiry_delta: 42, - htlc_minimum_msat: 100, - htlc_maximum_msat: 1_000_000_000_000, + payment_context, + }; + let intermediate_nodes = [ForwardNode { + tlvs: ForwardTlvs { + short_channel_id: 43, + payment_relay: PaymentRelay { + cltv_expiry_delta: 40, + fee_proportional_millionths: 1_000, + fee_base_msat: 1, + }, + payment_constraints: PaymentConstraints { + max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40, + htlc_minimum_msat: 100, + }, features: BlindedHopFeatures::empty(), }, - ]; + node_id: pubkey(43), + htlc_maximum_msat: 1_000_000_000_000, + }]; + let payment_path = BlindedPath::new_for_payment( + &intermediate_nodes, + pubkey(42), + payee_tlvs, + u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, + &entropy_source, + secp_ctx, + ) + .unwrap(); - let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect(); let payment_hash = PaymentHash([42; 32]); - refund.respond_with(payment_paths, payment_hash, signing_pubkey)?.build() + refund.respond_with(vec![payment_path], payment_hash, signing_pubkey)?.build() } pub fn refund_deser_test(data: &[u8], out: Out) { diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 15bf1a94940..47444eb900d 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -21,7 +21,7 @@ use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; use crate::ln::channelmanager::PaymentId; -use crate::ln::onion_utils; +use crate::ln::{PaymentHash, onion_utils}; use crate::offers::nonce::Nonce; use crate::onion_message::packet::ControlTlvs; use crate::sign::{NodeSigner, Recipient}; @@ -108,11 +108,6 @@ pub enum MessageContext { /// [`OffersMessage`]: crate::onion_message::offers::OffersMessage #[derive(Clone, Debug, Eq, PartialEq)] pub enum OffersContext { - /// 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 {}, /// Context used by a [`BlindedPath`] within an [`Offer`]. /// /// This variant is intended to be received when handling an [`InvoiceRequest`]. @@ -152,6 +147,18 @@ pub enum OffersContext { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest nonce: Nonce, }, + /// Context used by a [`BlindedPath`] as a reply path for a [`Bolt12Invoice`]. + /// + /// This variant is intended to be received when handling an [`InvoiceError`]. + /// + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError + InboundPayment { + /// The same payment hash as [`Bolt12Invoice::payment_hash`]. + /// + /// [`Bolt12Invoice::payment_hash`]: crate::offers::invoice::Bolt12Invoice::payment_hash + payment_hash: PaymentHash, + }, } impl_writeable_tlv_based_enum!(MessageContext, @@ -160,14 +167,16 @@ impl_writeable_tlv_based_enum!(MessageContext, ); impl_writeable_tlv_based_enum!(OffersContext, - (0, Unknown) => {}, - (1, InvoiceRequest) => { + (0, InvoiceRequest) => { (0, nonce, required), }, - (2, OutboundPayment) => { + (1, OutboundPayment) => { (0, payment_id, required), (1, nonce, required), }, + (2, InboundPayment) => { + (0, payment_hash, required), + }, ); /// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 9aa449efbaa..570d581a01c 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -810,7 +810,7 @@ pub enum Event { /// The context of the [`BlindedPath`] used to send the invoice. /// /// [`BlindedPath`]: crate::blinded_path::BlindedPath - context: OffersContext, + context: Option, /// A responder for replying with an [`InvoiceError`] if needed. /// /// `None` if the invoice wasn't sent with a reply path. @@ -1658,7 +1658,7 @@ impl Writeable for Event { write_tlv_fields!(writer, { (0, payment_id, required), (2, invoice, required), - (4, context, required), + (4, context, option), (6, responder, option), }); }, @@ -2113,13 +2113,13 @@ impl MaybeReadable for Event { _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_id, required), (2, invoice, required), - (4, context, required), + (4, context, option), (6, responder, option), }); Ok(Some(Event::InvoiceReceived { payment_id: payment_id.0.unwrap(), invoice: invoice.0.unwrap(), - context: context.0.unwrap(), + context, responder, })) }; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 28c40856f50..eabc09bc0ab 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4204,7 +4204,7 @@ where /// /// [timer tick]: Self::timer_tick_occurred pub fn send_payment_for_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: &OffersContext, + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, ) -> Result<(), Bolt12PaymentError> { match self.verify_bolt12_invoice(invoice, context) { Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id), @@ -4213,20 +4213,17 @@ where } fn verify_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: &OffersContext, + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, ) -> Result { let secp_ctx = &self.secp_ctx; let expanded_key = &self.inbound_payment_key; match context { - OffersContext::Unknown {} if invoice.is_for_refund_without_paths() => { + None 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(()) + Some(&OffersContext::OutboundPayment { payment_id, nonce }) => { + invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) }, _ => Err(()), } @@ -9120,7 +9117,11 @@ where )?; let builder: InvoiceBuilder = builder.into(); let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - let reply_paths = self.create_blinded_paths(OffersContext::Unknown {}) + + let context = OffersContext::InboundPayment { + payment_hash: invoice.payment_hash(), + }; + let reply_paths = self.create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); @@ -10711,13 +10712,17 @@ where R::Target: Router, L::Target: Logger, { - fn handle_message(&self, message: OffersMessage, context: OffersContext, responder: Option) -> ResponseInstruction { + fn handle_message( + &self, message: OffersMessage, context: Option, responder: Option, + ) -> ResponseInstruction { let secp_ctx = &self.secp_ctx; let expanded_key = &self.inbound_payment_key; let abandon_if_payment = |context| { match context { - OffersContext::OutboundPayment { payment_id, .. } => self.abandon_payment(payment_id), + Some(OffersContext::OutboundPayment { payment_id, .. }) => { + self.abandon_payment(payment_id) + }, _ => {}, } }; @@ -10730,8 +10735,8 @@ where }; let nonce = match context { - OffersContext::Unknown {} if invoice_request.metadata().is_some() => None, - OffersContext::InvoiceRequest { nonce } => Some(nonce), + None if invoice_request.metadata().is_some() => None, + Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), _ => return ResponseInstruction::NoResponse, }; @@ -10826,11 +10831,15 @@ where } }, OffersMessage::Invoice(invoice) => { - let payment_id = match self.verify_bolt12_invoice(&invoice, &context) { + let payment_id = match self.verify_bolt12_invoice(&invoice, context.as_ref()) { Ok(payment_id) => payment_id, Err(()) => return ResponseInstruction::NoResponse, }; + let logger = WithContext::from( + &self.logger, None, None, Some(invoice.payment_hash()), + ); + let result = { let features = self.bolt12_invoice_features(); if invoice.invoice_features().requires_unknown_bits_from(&features) { @@ -10844,7 +10853,7 @@ where } else { self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id) .map_err(|e| { - log_trace!(self.logger, "Failed paying invoice: {:?}", e); + log_trace!(logger, "Failed paying invoice: {:?}", e); InvoiceError::from_string(format!("{:?}", e)) }) } @@ -10860,7 +10869,7 @@ where None => { abandon_if_payment(context); log_trace!( - self.logger, + logger, "An error response was generated, but there is no reply_path specified \ for sending the response. Error: {}", err @@ -10882,8 +10891,14 @@ where } }, OffersMessage::InvoiceError(invoice_error) => { + let payment_hash = match context { + Some(OffersContext::InboundPayment { payment_hash }) => Some(payment_hash), + _ => None, + }; + let logger = WithContext::from(&self.logger, None, None, payment_hash); + log_trace!(logger, "Received invoice_error: {}", invoice_error); + abandon_if_payment(context); - log_trace!(self.logger, "Received invoice_error: {}", invoice_error); ResponseInstruction::NoResponse }, } diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 2d807d55070..0ae260a5086 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -81,13 +81,8 @@ impl ExpandedKey { /// Returns an [`HmacEngine`] used to construct [`Offer::metadata`]. /// /// [`Offer::metadata`]: crate::offers::offer::Offer::metadata - pub(crate) fn hmac_for_offer( - &self, nonce: Nonce, iv_bytes: &[u8; IV_LEN] - ) -> HmacEngine { - let mut hmac = HmacEngine::::new(&self.offers_base_key); - hmac.input(iv_bytes); - hmac.input(&nonce.0); - hmac + pub(crate) fn hmac_for_offer(&self) -> HmacEngine { + HmacEngine::::new(&self.offers_base_key) } /// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 627fc812646..a7fc92f527f 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1099,9 +1099,9 @@ fn pays_bolt12_invoice_asynchronously() { assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } - assert!(bob.node.send_payment_for_bolt12_invoice(&invoice, &context).is_ok()); + assert!(bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()).is_ok()); assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, &context), + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), Err(Bolt12PaymentError::DuplicateInvoice), ); @@ -1112,7 +1112,7 @@ fn pays_bolt12_invoice_asynchronously() { expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, &context), + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), Err(Bolt12PaymentError::DuplicateInvoice), ); @@ -1121,7 +1121,7 @@ fn pays_bolt12_invoice_asynchronously() { } assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, &context), + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), Err(Bolt12PaymentError::UnexpectedInvoice), ); } diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 803a89b4200..3be4d287aef 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -144,7 +144,7 @@ impl OnionMessageHandler for IgnoringMessageHandler { } impl OffersMessageHandler for IgnoringMessageHandler { - fn handle_message(&self, _message: OffersMessage, _context: OffersContext, _responder: Option) -> ResponseInstruction { + fn handle_message(&self, _message: OffersMessage, _context: Option, _responder: Option) -> ResponseInstruction { ResponseInstruction::NoResponse } } diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index e1f30138212..76a4769cb58 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -114,7 +114,7 @@ use crate::blinded_path::BlindedPath; use crate::ln::types::PaymentHash; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; -use crate::ln::inbound_payment::ExpandedKey; +use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::ln::msgs::DecodeError; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; @@ -123,7 +123,7 @@ use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; -use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents}; +use crate::offers::refund::{IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, Refund, RefundContents}; use crate::offers::signer::{Metadata, self}; use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, Readable, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -778,11 +778,15 @@ impl Bolt12Invoice { pub fn verify_using_metadata( &self, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result { - let metadata = match &self.contents { - InvoiceContents::ForOffer { invoice_request, .. } => &invoice_request.inner.payer.0, - InvoiceContents::ForRefund { refund, .. } => &refund.payer.0, + let (metadata, iv_bytes) = match &self.contents { + InvoiceContents::ForOffer { invoice_request, .. } => { + (&invoice_request.inner.payer.0, INVOICE_REQUEST_IV_BYTES) + }, + InvoiceContents::ForRefund { refund, .. } => { + (&refund.payer.0, REFUND_IV_BYTES_WITH_METADATA) + }, }; - self.contents.verify(TlvStream::new(&self.bytes), metadata, key, secp_ctx) + self.contents.verify(TlvStream::new(&self.bytes), metadata, key, iv_bytes, secp_ctx) } /// Verifies that the invoice was for a request or refund created using the given key by @@ -790,12 +794,17 @@ impl Bolt12Invoice { /// sent through. pub fn verify_using_payer_data( &self, payment_id: PaymentId, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { + ) -> Result { let metadata = Metadata::payer_data(payment_id, nonce, key); - match self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, secp_ctx) { - Ok(extracted_payment_id) => payment_id == extracted_payment_id, - Err(()) => false, - } + let iv_bytes = match &self.contents { + InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES, + InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA, + }; + self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, iv_bytes, secp_ctx) + .and_then(|extracted_payment_id| (payment_id == extracted_payment_id) + .then(|| payment_id) + .ok_or(()) + ) } pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { @@ -1027,7 +1036,7 @@ impl InvoiceContents { fn verify( &self, tlv_stream: TlvStream<'_>, metadata: &Metadata, key: &ExpandedKey, - secp_ctx: &Secp256k1 + iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1 ) -> Result { let offer_records = tlv_stream.clone().range(OFFER_TYPES); let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| { @@ -1040,11 +1049,6 @@ impl InvoiceContents { let tlv_stream = offer_records.chain(invreq_records); let payer_id = self.payer_id(); - let iv_bytes = match self { - InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES, - InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES, - }; - signer::verify_payer_metadata( metadata.as_ref(), key, iv_bytes, payer_id, tlv_stream, secp_ctx, ) @@ -1556,6 +1560,7 @@ mod tests { assert_eq!(invoice.payment_hash(), payment_hash); assert!(invoice.fallbacks().is_empty()); assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty()); + assert!(!invoice.is_for_refund_without_paths()); let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes); assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); @@ -1653,6 +1658,7 @@ mod tests { assert_eq!(invoice.payment_hash(), payment_hash); assert!(invoice.fallbacks().is_empty()); assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty()); + assert!(invoice.is_for_refund_without_paths()); let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes); assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); @@ -1845,6 +1851,37 @@ mod tests { } } + #[test] + fn builds_invoice_from_refund_with_path() { + let node_id = payer_pubkey(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + let blinded_path = BlindedPath { + introduction_node: IntroductionNode::NodeId(pubkey(40)), + blinding_point: pubkey(41), + blinded_hops: vec![ + BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, + BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] }, + ], + }; + + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() + .path(blinded_path) + .build().unwrap(); + + let invoice = refund + .respond_using_derived_keys_no_std( + payment_paths(), payment_hash(), now(), &expanded_key, &entropy + ) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + assert!(!invoice.message_paths().is_empty()); + assert!(!invoice.is_for_refund_without_paths()); + } + #[test] fn builds_invoice_with_relative_expiry() { let now = now(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4ad99645002..de9c9c2db31 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -174,7 +174,7 @@ macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, payment_id: PaymentId, ) -> Self { let payment_id = Some(payment_id); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, payment_id); let metadata = Metadata::Derived(derivation_material); Self { offer, @@ -203,7 +203,7 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ( secp_ctx: &'b Secp256k1<$secp_context>, payment_id: PaymentId ) -> Self { let payment_id = Some(payment_id); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, payment_id); let metadata = Metadata::DerivedSigningPubkey(derivation_material); Self { offer, @@ -346,7 +346,8 @@ macro_rules! invoice_request_builder_methods { ( tlv_stream.2.payer_id = $self.payer_id.as_ref(); } - let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); + let (derived_metadata, derived_keys) = + metadata.derive_from(IV_BYTES, tlv_stream, $self.secp_ctx); metadata = derived_metadata; keys = derived_keys; if let Some(keys) = keys { @@ -1417,7 +1418,9 @@ mod tests { Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), Err(()) => panic!("verification failed"), } - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); // Fails verification with altered fields let ( @@ -1488,7 +1491,9 @@ mod tests { .build().unwrap() .sign(recipient_sign).unwrap(); assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - assert!(invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_ok() + ); // Fails verification with altered fields let ( @@ -1511,7 +1516,9 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); // Fails verification with altered payer id let ( @@ -1534,7 +1541,9 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); } #[test] diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 29220125f66..24f0346885c 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -112,7 +112,8 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Offer v2~~~~"; /// An identifier for an [`Offer`] built using [`DerivedMetadata`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -258,7 +259,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { node_id: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce, secp_ctx: &'a Secp256k1<$secp_context> ) -> Self { - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, None); let metadata = Metadata::DerivedSigningPubkey(derivation_material); Self { offer: OfferContents { @@ -391,9 +392,12 @@ macro_rules! offer_builder_methods { ( // Don't derive keys if no blinded paths were given since this means the signing // pubkey must be the node id of an announced node. - if $self.offer.paths.is_none() { + let iv_bytes = if $self.offer.paths.is_none() { metadata = metadata.without_keys(); - } + IV_BYTES_WITH_METADATA + } else { + IV_BYTES_WITHOUT_METADATA + }; let mut tlv_stream = $self.offer.as_tlv_stream(); debug_assert_eq!(tlv_stream.metadata, None); @@ -405,7 +409,8 @@ macro_rules! offer_builder_methods { ( // Either replace the signing pubkey with the derived pubkey or include the metadata // for verification. In the former case, the blinded paths must include // `OffersContext::InvoiceRequest` instead. - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); + let (derived_metadata, keys) = + metadata.derive_from(iv_bytes, tlv_stream, $self.secp_ctx); match keys { Some(keys) => $self.offer.signing_pubkey = Some(keys.public_key()), None => $self.offer.metadata = Some(derived_metadata), @@ -918,18 +923,20 @@ impl OfferContents { pub(super) fn verify_using_metadata( &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { - self.verify(bytes, self.metadata.as_ref(), key, secp_ctx) + self.verify(bytes, self.metadata.as_ref(), key, IV_BYTES_WITH_METADATA, secp_ctx) } pub(super) fn verify_using_recipient_data( &self, bytes: &[u8], nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { - self.verify(bytes, Some(&Metadata::RecipientData(nonce)), key, secp_ctx) + let metadata = Metadata::RecipientData(nonce); + self.verify(bytes, Some(&metadata), key, IV_BYTES_WITHOUT_METADATA, secp_ctx) } /// Verifies that the offer metadata was produced from the offer in the TLV stream. fn verify( - &self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, secp_ctx: &Secp256k1 + &self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, + iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { match metadata { Some(metadata) => { @@ -945,7 +952,7 @@ impl OfferContents { None => return Err(()), }; let keys = signer::verify_recipient_metadata( - metadata.as_ref(), key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx + metadata.as_ref(), key, iv_bytes, signing_pubkey, tlv_stream, secp_ctx )?; let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 9cfa3147c63..242652577bd 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -122,7 +122,8 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; +pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; +pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Refund v2~~~"; /// Builds a [`Refund`] for the "offer for money" flow. /// @@ -210,7 +211,7 @@ macro_rules! refund_builder_methods { ( } let payment_id = Some(payment_id); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, payment_id); let metadata = Metadata::DerivedSigningPubkey(derivation_material); Ok(Self { refund: RefundContents { @@ -306,9 +307,12 @@ macro_rules! refund_builder_methods { ( if $self.refund.payer.0.has_derivation_material() { let mut metadata = core::mem::take(&mut $self.refund.payer.0); - if $self.refund.paths.is_none() { + let iv_bytes = if $self.refund.paths.is_none() { metadata = metadata.without_keys(); - } + IV_BYTES_WITH_METADATA + } else { + IV_BYTES_WITHOUT_METADATA + }; let mut tlv_stream = $self.refund.as_tlv_stream(); tlv_stream.0.metadata = None; @@ -316,7 +320,8 @@ macro_rules! refund_builder_methods { ( tlv_stream.2.payer_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); + let (derived_metadata, keys) = + metadata.derive_from(iv_bytes, tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { $self.refund.payer_id = keys.public_key(); @@ -1049,7 +1054,9 @@ mod tests { Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), Err(()) => panic!("verification failed"), } - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); let mut tlv_stream = refund.as_tlv_stream(); tlv_stream.2.amount = Some(2000); @@ -1111,7 +1118,9 @@ mod tests { .build().unwrap() .sign(recipient_sign).unwrap(); assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - assert!(invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_ok() + ); // Fails verification with altered fields let mut tlv_stream = refund.as_tlv_stream(); @@ -1125,7 +1134,9 @@ mod tests { .unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); // Fails verification with altered payer_id let mut tlv_stream = refund.as_tlv_stream(); @@ -1140,7 +1151,9 @@ mod tests { .unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - assert!(!invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx)); + assert!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() + ); } #[test] diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 2e12a17056e..c15b94d4996 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -140,20 +140,19 @@ impl Metadata { } pub fn derive_from( - self, tlv_stream: W, secp_ctx: Option<&Secp256k1> + self, iv_bytes: &[u8; IV_LEN], tlv_stream: W, secp_ctx: Option<&Secp256k1> ) -> (Self, Option) { match self { Metadata::Bytes(_) => (self, None), Metadata::RecipientData(_) => { debug_assert!(false); (self, None) }, Metadata::PayerData(_) => { debug_assert!(false); (self, None) }, - Metadata::Derived(mut metadata_material) => { - tlv_stream.write(&mut metadata_material.hmac).unwrap(); - (Metadata::Bytes(metadata_material.derive_metadata()), None) + Metadata::Derived(metadata_material) => { + (Metadata::Bytes(metadata_material.derive_metadata(iv_bytes, tlv_stream)), None) }, - Metadata::DerivedSigningPubkey(mut metadata_material) => { - tlv_stream.write(&mut metadata_material.hmac).unwrap(); + Metadata::DerivedSigningPubkey(metadata_material) => { let secp_ctx = secp_ctx.unwrap(); - let (metadata, keys) = metadata_material.derive_metadata_and_keys(secp_ctx); + let (metadata, keys) = + metadata_material.derive_metadata_and_keys(iv_bytes, tlv_stream, secp_ctx); (Metadata::Bytes(metadata), Some(keys)) }, } @@ -217,10 +216,7 @@ pub(super) struct MetadataMaterial { } impl MetadataMaterial { - pub fn new( - nonce: Nonce, expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], - payment_id: Option - ) -> Self { + pub fn new(nonce: Nonce, expanded_key: &ExpandedKey, payment_id: Option) -> Self { // Encrypt payment_id let encrypted_payment_id = payment_id.map(|payment_id| { expanded_key.crypt_for_offer(payment_id.0, nonce) @@ -228,12 +224,16 @@ impl MetadataMaterial { Self { nonce, - hmac: expanded_key.hmac_for_offer(nonce, iv_bytes), + hmac: expanded_key.hmac_for_offer(), encrypted_payment_id, } } - fn derive_metadata(mut self) -> Vec { + fn derive_metadata(mut self, iv_bytes: &[u8; IV_LEN], tlv_stream: W) -> Vec { + self.hmac.input(iv_bytes); + self.hmac.input(&self.nonce.0); + tlv_stream.write(&mut self.hmac).unwrap(); + self.hmac.input(DERIVED_METADATA_HMAC_INPUT); self.maybe_include_encrypted_payment_id(); @@ -243,9 +243,13 @@ impl MetadataMaterial { bytes } - fn derive_metadata_and_keys( - mut self, secp_ctx: &Secp256k1 + fn derive_metadata_and_keys( + mut self, iv_bytes: &[u8; IV_LEN], tlv_stream: W, secp_ctx: &Secp256k1 ) -> (Vec, Keypair) { + self.hmac.input(iv_bytes); + self.hmac.input(&self.nonce.0); + tlv_stream.write(&mut self.hmac).unwrap(); + self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT); self.maybe_include_encrypted_payment_id(); @@ -271,9 +275,12 @@ impl MetadataMaterial { pub(super) fn derive_keys(nonce: Nonce, expanded_key: &ExpandedKey) -> Keypair { const IV_BYTES: &[u8; IV_LEN] = b"LDK Invoice ~~~~"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + let secp_ctx = Secp256k1::new(); - let hmac = Hmac::from_engine(expanded_key.hmac_for_offer(nonce, IV_BYTES)); - let privkey = SecretKey::from_slice(hmac.as_byte_array()).unwrap(); + let privkey = SecretKey::from_slice(Hmac::from_engine(hmac).as_byte_array()).unwrap(); Keypair::from_secret_key(&secp_ctx, &privkey) } @@ -368,7 +375,9 @@ fn hmac_for_message<'a>( Ok(nonce) => nonce, Err(_) => return Err(()), }; - let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes); + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(iv_bytes); + hmac.input(&nonce.0); for record in tlv_stream { hmac.input(record.record_bytes); diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 16e62bf33f4..ad6fe7d99a3 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -76,7 +76,7 @@ impl Drop for MessengerNode { struct TestOffersMessageHandler {} impl OffersMessageHandler for TestOffersMessageHandler { - fn handle_message(&self, _message: OffersMessage, _context: OffersContext, _responder: Option) -> ResponseInstruction { + fn handle_message(&self, _message: OffersMessage, _context: Option, _responder: Option) -> ResponseInstruction { ResponseInstruction::NoResponse } } diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 7c7cd261089..b14210db4b5 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -16,7 +16,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use crate::blinded_path::{BlindedPath, IntroductionNode, NextMessageHop, NodeIdLookUp}; -use crate::blinded_path::message::{advance_path_by_one, ForwardNode, ForwardTlvs, MessageContext, OffersContext, ReceiveTlvs}; +use crate::blinded_path::message::{advance_path_by_one, ForwardNode, ForwardTlvs, MessageContext, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider, ReplayEvent}; use crate::sign::{EntropySource, NodeSigner, Recipient}; @@ -1514,8 +1514,8 @@ where match message { ParsedOnionMessageContents::Offers(msg) => { let context = match context { - None => OffersContext::Unknown {}, - Some(MessageContext::Offers(context)) => context, + None => None, + Some(MessageContext::Offers(context)) => Some(context), Some(MessageContext::Custom(_)) => { debug_assert!(false, "Shouldn't have triggered this case."); return diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs index a8f43c2d213..6884ca77e06 100644 --- a/lightning/src/onion_message/offers.rs +++ b/lightning/src/onion_message/offers.rs @@ -45,7 +45,9 @@ pub trait OffersMessageHandler { /// The returned [`OffersMessage`], if any, is enqueued to be sent by [`OnionMessenger`]. /// /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger - fn handle_message(&self, message: OffersMessage, context: OffersContext, responder: Option) -> ResponseInstruction; + fn handle_message( + &self, message: OffersMessage, context: Option, responder: Option, + ) -> ResponseInstruction; /// Releases any [`OffersMessage`]s that need to be sent. ///