Add InvoiceRequestTlvStream::paths
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 23 Apr 2024 23:30:59 +0000 (18:30 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 26 Apr 2024 23:14:09 +0000 (18:14 -0500)
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
lightning/src/offers/invoice_request.rs
lightning/src/offers/parse.rs
lightning/src/offers/refund.rs

index ab9e7bf8e64c16f49c41149a06832b1035bc8b7d..7dd57365321078ef9172d4ba5efaa9add5a4381c 100644 (file)
@@ -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))),
index a858e5afb2c0b688399c74189cebc862c3ac9d7e..be4696be91c7ee4bba7f105c13faa0f5c687b964 100644 (file)
@@ -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<u64> = 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<BlindedPath>, WithoutLength)),
 });
 
 type FullInvoiceRequestTlvStream =
@@ -1092,7 +1097,9 @@ impl TryFrom<PartialInvoiceRequestTlvStream> 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<PartialInvoiceRequestTlvStream> 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()) },
                        ),
index 72c4c380d52592f8c87b59c9f50653a449fec0b9..3b9b04a5c06f8f00d898cec70aec46f6be600e3e 100644 (file)
@@ -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.
index 03253fb6400bfe2c1261fcb2b00f13bc6eff1779..f9aa90ba0500af0b23da09b30289931877aa9949 100644 (file)
@@ -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<Duration>,
        issuer: Option<String>,
-       paths: Option<Vec<BlindedPath>>,
        // invoice_request fields
        chain: Option<ChainHash>,
        amount_msats: u64,
@@ -418,6 +417,7 @@ pub(super) struct RefundContents {
        quantity: Option<u64>,
        payer_id: PublicKey,
        payer_note: Option<String>,
+       paths: Option<Vec<BlindedPath>>,
 }
 
 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<RefundTlvStream> 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<RefundTlvStream> 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<RefundTlvStream> 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]