From 61e4fcea8e19eba1f6b1299c8415031c3a031a94 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Apr 2024 18:30:59 -0500 Subject: [PATCH] Add InvoiceRequestTlvStream::paths Instead of reusing OfferTlvStream::paths, add a dedicated paths TLV to InvoiceRequestTlvStream such that it can be used in Refund. This allows for an Offer without a signing_pubkey and still be able to differentiate whether an invoice is for an offer or a refund. --- lightning/src/offers/invoice.rs | 2 ++ lightning/src/offers/invoice_request.rs | 14 +++++++++- lightning/src/offers/parse.rs | 2 ++ lightning/src/offers/refund.rs | 35 ++++++++++++++++--------- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index ab9e7bf8e..7dd573653 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1632,6 +1632,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, InvoiceTlvStreamRef { paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))), @@ -1724,6 +1725,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, InvoiceTlvStreamRef { paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))), diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index a858e5afb..be4696be9 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -971,6 +971,7 @@ impl InvoiceRequestContentsWithoutPayerId { quantity: self.quantity, payer_id: None, payer_note: self.payer_note.as_ref(), + paths: None, }; (payer, offer, invoice_request) @@ -1003,6 +1004,8 @@ pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range = 80..160; /// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88; +// This TLV stream is used for both InvoiceRequest and Refund, but not all TLV records are valid for +// InvoiceRequest as noted below. tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, { (80, chain: ChainHash), (82, amount: (u64, HighZeroBytesDroppedBigSize)), @@ -1010,6 +1013,8 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST (86, quantity: (u64, HighZeroBytesDroppedBigSize)), (INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey), (89, payer_note: (String, WithoutLength)), + // Only used for Refund since the onion message of an InvoiceRequest has a reply path. + (90, paths: (Vec, WithoutLength)), }); type FullInvoiceRequestTlvStream = @@ -1092,7 +1097,9 @@ impl TryFrom for InvoiceRequestContents { let ( PayerTlvStream { metadata }, offer_tlv_stream, - InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note }, + InvoiceRequestTlvStream { + chain, amount, features, quantity, payer_id, payer_note, paths, + }, ) = tlv_stream; let payer = match metadata { @@ -1119,6 +1126,10 @@ impl TryFrom for InvoiceRequestContents { Some(payer_id) => payer_id, }; + if paths.is_some() { + return Err(Bolt12SemanticError::UnexpectedPaths); + } + Ok(InvoiceRequestContents { inner: InvoiceRequestContentsWithoutPayerId { payer, offer, chain, amount_msats: amount, features, quantity, payer_note, @@ -1310,6 +1321,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, ), diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index 72c4c380d..3b9b04a5c 100644 --- a/lightning/src/offers/parse.rs +++ b/lightning/src/offers/parse.rs @@ -183,6 +183,8 @@ pub enum Bolt12SemanticError { DuplicatePaymentId, /// Blinded paths were expected but were missing. MissingPaths, + /// Blinded paths were provided but were not expected. + UnexpectedPaths, /// The blinded payinfo given does not match the number of blinded path hops. InvalidPayInfo, /// An invoice creation time was expected but was missing. diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 03253fb64..f9aa90ba0 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -170,8 +170,8 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { Ok(Self { refund: RefundContents { payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None, - paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), - quantity: None, payer_id, payer_note: None, + chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), + quantity: None, payer_id, payer_note: None, paths: None, }, secp_ctx: None, }) @@ -209,8 +209,8 @@ macro_rules! refund_builder_methods { ( Ok(Self { refund: RefundContents { payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None, - paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), - quantity: None, payer_id: node_id, payer_note: None, + chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), + quantity: None, payer_id: node_id, payer_note: None, paths: None, }, secp_ctx: Some(secp_ctx), }) @@ -410,7 +410,6 @@ pub(super) struct RefundContents { description: String, absolute_expiry: Option, issuer: Option, - paths: Option>, // invoice_request fields chain: Option, amount_msats: u64, @@ -418,6 +417,7 @@ pub(super) struct RefundContents { quantity: Option, payer_id: PublicKey, payer_note: Option, + paths: Option>, } impl Refund { @@ -734,7 +734,7 @@ impl RefundContents { description: Some(&self.description), features: None, absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()), - paths: self.paths.as_ref(), + paths: None, issuer: self.issuer.as_ref(), quantity_max: None, node_id: None, @@ -752,6 +752,7 @@ impl RefundContents { quantity: self.quantity, payer_id: Some(&self.payer_id), payer_note: self.payer_note.as_ref(), + paths: self.paths.as_ref(), }; (payer, offer, invoice_request) @@ -820,9 +821,12 @@ impl TryFrom for RefundContents { PayerTlvStream { metadata: payer_metadata }, OfferTlvStream { chains, metadata, currency, amount: offer_amount, description, - features: offer_features, absolute_expiry, paths, issuer, quantity_max, node_id, + features: offer_features, absolute_expiry, paths: offer_paths, issuer, quantity_max, + node_id, + }, + InvoiceRequestTlvStream { + chain, amount, features, quantity, payer_id, payer_note, paths }, - InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note }, ) = tlv_stream; let payer = match payer_metadata { @@ -853,6 +857,10 @@ impl TryFrom for RefundContents { let absolute_expiry = absolute_expiry.map(Duration::from_secs); + if offer_paths.is_some() { + return Err(Bolt12SemanticError::UnexpectedPaths); + } + if quantity_max.is_some() { return Err(Bolt12SemanticError::UnexpectedQuantity); } @@ -877,8 +885,8 @@ impl TryFrom for RefundContents { }; Ok(RefundContents { - payer, description, absolute_expiry, issuer, paths, chain, amount_msats, features, - quantity, payer_id, payer_note, + payer, description, absolute_expiry, issuer, chain, amount_msats, features, quantity, + payer_id, payer_note, paths, }) } } @@ -980,6 +988,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, ), ); @@ -1173,12 +1182,12 @@ mod tests { .path(paths[1].clone()) .build() .unwrap(); - let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream(); - assert_eq!(refund.paths(), paths.as_slice()); + let (_, _, invoice_request_tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.payer_id(), pubkey(42)); + assert_eq!(refund.paths(), paths.as_slice()); assert_ne!(pubkey(42), pubkey(44)); - assert_eq!(offer_tlv_stream.paths, Some(&paths)); assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42))); + assert_eq!(invoice_request_tlv_stream.paths, Some(&paths)); } #[test] -- 2.39.5