From 353b45f70aeada8fb92f127d0329200fd9bea9d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 22 Jul 2024 16:14:00 -0500 Subject: [PATCH 1/9] Bolt12Invoice::is_for_refund_without_paths tests --- lightning/src/offers/invoice.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index e1f30138212..7d482bc245e 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1556,6 +1556,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 +1654,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 +1847,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(); From 4a69f58ff69f2cfc36c1cfb24630c7b7f9d8177b Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 16:09:14 -0500 Subject: [PATCH 2/9] Result from Bolt12Invoice::verify_using_payer_data Use a Result return type instead of a bool when verifying a Bolt12Invoice. This way ignoring the result will produce a compiler warning. --- lightning/src/ln/channelmanager.rs | 5 +---- lightning/src/offers/invoice.rs | 11 ++++++----- lightning/src/offers/invoice_request.rs | 16 ++++++++++++---- lightning/src/offers/refund.rs | 16 ++++++++++++---- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 28c40856f50..10c010e8989 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4223,10 +4223,7 @@ where 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(()) + invoice.verify_using_payer_data(*payment_id, *nonce, expanded_key, secp_ctx) }, _ => Err(()), } diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 7d482bc245e..eee71995e32 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -790,12 +790,13 @@ 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, - } + self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, 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 { diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4ad99645002..4b340470226 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1417,7 +1417,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 +1490,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 +1515,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 +1540,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/refund.rs b/lightning/src/offers/refund.rs index 9cfa3147c63..8547cfe9931 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -1049,7 +1049,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 +1113,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 +1129,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 +1146,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] From 8849efe0deeb28ede680b83f0a7f9a6543627bd5 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 13:36:39 -0500 Subject: [PATCH 3/9] Delay adding iv_bytes to MetadataMaterial::hmac In an upcoming commit, the iv_bytes used in MetadataMaterial will vary depending on when whether a blinded path is included in the corresponding message. Delay adding into MetadataMaterial::hmac as otherwise the HmacEngine would need to be re-initialized using an ExpandedKey, which won't be readily available. --- lightning/src/ln/inbound_payment.rs | 9 ++--- lightning/src/offers/invoice_request.rs | 7 ++-- lightning/src/offers/offer.rs | 5 +-- lightning/src/offers/refund.rs | 5 +-- lightning/src/offers/signer.rs | 45 +++++++++++++++---------- 5 files changed, 39 insertions(+), 32 deletions(-) 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/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4b340470226..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 { diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 29220125f66..deacacd1522 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -258,7 +258,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 { @@ -405,7 +405,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), diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 8547cfe9931..76605c77fbb 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -210,7 +210,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 { @@ -316,7 +316,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(); 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); From 293543b7c3f4f7601569ea4362165db08b120810 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 17:54:31 -0500 Subject: [PATCH 4/9] Use different iv_bytes for blinded path metadata Best practice is to use different IV bytes for different contexts. Update Offer and Refund metadata computation to use different IV bytes when the metadata is included in a blinded path. For invoice requests, the metatdata will always be in the blinded path, so it remains the same. --- lightning/src/offers/invoice.rs | 29 ++++++++++++++++------------- lightning/src/offers/offer.rs | 22 ++++++++++++++-------- lightning/src/offers/refund.rs | 12 ++++++++---- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index eee71995e32..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 @@ -792,7 +796,11 @@ impl Bolt12Invoice { &self, payment_id: PaymentId, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result { let metadata = Metadata::payer_data(payment_id, nonce, key); - self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, secp_ctx) + 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(()) @@ -1028,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| { @@ -1041,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, ) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index deacacd1522..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)] @@ -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); @@ -406,7 +410,7 @@ macro_rules! offer_builder_methods { ( // for verification. In the former case, the blinded paths must include // `OffersContext::InvoiceRequest` instead. let (derived_metadata, keys) = - metadata.derive_from(IV_BYTES, tlv_stream, $self.secp_ctx); + 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), @@ -919,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) => { @@ -946,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 76605c77fbb..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. /// @@ -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; @@ -317,7 +321,7 @@ macro_rules! refund_builder_methods { ( } let (derived_metadata, keys) = - metadata.derive_from(IV_BYTES, tlv_stream, $self.secp_ctx); + 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(); From 3e832cbb734f7388c496a65cfa5eff6d287773f8 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 15:40:32 -0500 Subject: [PATCH 5/9] Use BlindedPath::new_for_payment in fuzz tests When creating a Bolt12Invoice in fuzz tests, use BlindedPath::new_for_payment instead of BlindedPath::new_for_message. This way PaymentContext is used instead of MessageContext, as is more realistic though should not affect the test. This allows us to remove OffersContext::Unknown. --- fuzz/src/invoice_request_deser.rs | 102 ++++++++++++++++-------------- fuzz/src/refund_deser.rs | 89 ++++++++++++-------------- 2 files changed, 94 insertions(+), 97 deletions(-) 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/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) { From 2fc0c1b85cb031fb2d2af93e34ad618b15275463 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 3 Jul 2024 11:02:08 -0500 Subject: [PATCH 6/9] Include payment hash when logging InvoiceError By including the payment hash from the invoice in an onion message's reply path, it can be used when logging errors as additional context. --- lightning/src/blinded_path/message.rs | 17 ++++++++++++++++- lightning/src/ln/channelmanager.rs | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 15bf1a94940..2ff799d0ebd 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}; @@ -152,6 +152,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, @@ -168,6 +180,9 @@ impl_writeable_tlv_based_enum!(OffersContext, (0, payment_id, required), (1, nonce, required), }, + (3, InboundPayment) => { + (0, payment_hash, required), + }, ); /// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 10c010e8989..16c06e8deb1 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10879,8 +10879,14 @@ where } }, OffersMessage::InvoiceError(invoice_error) => { + let payment_hash = match context { + 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 }, } From a5382ddcd5240f72f39febfccc99ffcd47e2fa8d Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 3 Jul 2024 11:12:47 -0500 Subject: [PATCH 7/9] Include payment hash when logging invoice handling --- lightning/src/ln/channelmanager.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 16c06e8deb1..b57dee30256 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10828,6 +10828,10 @@ where 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) { @@ -10841,7 +10845,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)) }) } @@ -10857,7 +10861,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 From d2c22d58cee0c0d0fcfe657f82cb4bb647d56288 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 13:49:37 -0500 Subject: [PATCH 8/9] Include payment hash in Bolt12Invoice reply path Instead of using OffersContext::Unknown for the Bolt12Invoice reply path use OffersContext::InboundPayment to include the payment hash. OffersContext::Unknown will be removed in another commit. --- lightning/src/ln/channelmanager.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b57dee30256..c316dc2b484 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9117,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(); From 88343366ca04085904ee0ac41d5a10a86d933a35 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 18:24:43 -0500 Subject: [PATCH 9/9] Replace use of OffersContext::Unknown with None Now that ChannelManager uses a known OffersContext when creating blinded paths, OffersContext::Unknown is no longer needed. Remove it and update OffersMessageHandler to us an Option, which is more idiomatic for signifying whether a message was delivered with or without an OffersContext. --- fuzz/src/onion_message.rs | 3 ++- lightning/src/blinded_path/message.rs | 12 +++------ lightning/src/events/mod.rs | 8 +++--- lightning/src/ln/channelmanager.rs | 26 +++++++++++-------- lightning/src/ln/offers_tests.rs | 8 +++--- lightning/src/ln/peer_handler.rs | 2 +- .../src/onion_message/functional_tests.rs | 2 +- lightning/src/onion_message/messenger.rs | 6 ++--- lightning/src/onion_message/offers.rs | 4 ++- 9 files changed, 36 insertions(+), 35 deletions(-) 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/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 2ff799d0ebd..47444eb900d 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -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`]. @@ -172,15 +167,14 @@ 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), }, - (3, InboundPayment) => { + (2, InboundPayment) => { (0, payment_hash, required), }, ); 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 c316dc2b484..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,17 +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) + Some(&OffersContext::OutboundPayment { payment_id, nonce }) => { + invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) }, _ => Err(()), } @@ -10712,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) + }, _ => {}, } }; @@ -10731,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, }; @@ -10827,7 +10831,7 @@ 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, }; @@ -10888,7 +10892,7 @@ where }, OffersMessage::InvoiceError(invoice_error) => { let payment_hash = match context { - OffersContext::InboundPayment { payment_hash } => Some(payment_hash), + Some(OffersContext::InboundPayment { payment_hash }) => Some(payment_hash), _ => None, }; let logger = WithContext::from(&self.logger, None, None, payment_hash); 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/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. ///