Optional OfferContents::signing_pubkey
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 23 Apr 2024 22:38:13 +0000 (17:38 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 26 Apr 2024 23:14:09 +0000 (18:14 -0500)
If an Offer contains a path, the blinded_node_id of the path's final hop
can be used as the signing pubkey. Make Offer::signing_pubkey and
OfferContents::signing_pubkey return an Option to support this. Upcoming
commits will implement this behavior.

lightning/src/ln/channelmanager.rs
lightning/src/ln/offers_tests.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/offer.rs

index 130eab49384ce884c99cc637422c61729dd1617f..d10c627916a39e021fc34d86c366e3f50f2b771d 100644 (file)
@@ -8765,14 +8765,7 @@ where
                        .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
 
                let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
-               if offer.paths().is_empty() {
-                       let message = new_pending_onion_message(
-                               OffersMessage::InvoiceRequest(invoice_request),
-                               Destination::Node(offer.signing_pubkey()),
-                               Some(reply_path),
-                       );
-                       pending_offers_messages.push(message);
-               } else {
+               if !offer.paths().is_empty() {
                        // Send as many invoice requests as there are paths in the offer (with an upper bound).
                        // Using only one path could result in a failure if the path no longer exists. But only
                        // one invoice for a given payment id will be paid, even if more than one is received.
@@ -8785,6 +8778,16 @@ where
                                );
                                pending_offers_messages.push(message);
                        }
+               } else if let Some(signing_pubkey) = offer.signing_pubkey() {
+                       let message = new_pending_onion_message(
+                               OffersMessage::InvoiceRequest(invoice_request),
+                               Destination::Node(signing_pubkey),
+                               Some(reply_path),
+                       );
+                       pending_offers_messages.push(message);
+               } else {
+                       debug_assert!(false);
+                       return Err(Bolt12SemanticError::MissingSigningPubkey);
                }
 
                Ok(())
index 75a2e290f39e824514fdaa3b6333eb20b319663e..e89e113e8ea887e37373c39f4b6e9f8a28754342 100644 (file)
@@ -271,7 +271,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
                .create_offer_builder("coffee".to_string()).unwrap()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_ne!(offer.signing_pubkey(), bob_id);
+       assert_ne!(offer.signing_pubkey(), Some(bob_id));
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
                assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id));
@@ -286,7 +286,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
                .create_offer_builder("coffee".to_string()).unwrap()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_ne!(offer.signing_pubkey(), bob_id);
+       assert_ne!(offer.signing_pubkey(), Some(bob_id));
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
                assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
@@ -336,7 +336,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
                .create_offer_builder("coffee".to_string()).unwrap()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_ne!(offer.signing_pubkey(), bob_id);
+       assert_ne!(offer.signing_pubkey(), Some(bob_id));
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
                assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id()));
@@ -386,7 +386,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
                .unwrap()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_ne!(offer.signing_pubkey(), alice_id);
+       assert_ne!(offer.signing_pubkey(), Some(alice_id));
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
                assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
@@ -547,7 +547,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
                .create_offer_builder("coffee".to_string()).unwrap()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_ne!(offer.signing_pubkey(), alice_id);
+       assert_ne!(offer.signing_pubkey(), Some(alice_id));
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
                assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
@@ -672,7 +672,7 @@ fn pays_for_offer_without_blinded_paths() {
                .clear_paths()
                .amount_msats(10_000_000)
                .build().unwrap();
-       assert_eq!(offer.signing_pubkey(), alice_id);
+       assert_eq!(offer.signing_pubkey(), Some(alice_id));
        assert!(offer.paths().is_empty());
 
        let payment_id = PaymentId([1; 32]);
index ee5e6deb4086bb69992015a053efdffab89c8816..ab9e7bf8e64c16f49c41149a06832b1035bc8b7d 100644 (file)
@@ -210,10 +210,9 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $s
        #[cfg_attr(c_bindings, allow(dead_code))]
        pub(super) fn for_offer(
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
-               created_at: Duration, payment_hash: PaymentHash
+               created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey
        ) -> Result<Self, Bolt12SemanticError> {
                let amount_msats = Self::amount_msats(invoice_request)?;
-               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
                        fields: Self::fields(
@@ -272,7 +271,7 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se
                created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
        ) -> Result<Self, Bolt12SemanticError> {
                let amount_msats = Self::amount_msats(invoice_request)?;
-               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
+               let signing_pubkey = keys.public_key();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
                        fields: Self::fields(
index 9157613fcd977a132e9f7b5751f196af0592269a..a858e5afb2c0b688399c74189cebc862c3ac9d7e 100644 (file)
@@ -747,7 +747,12 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
-               <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash)
+               let signing_pubkey = match $contents.contents.inner.offer.signing_pubkey() {
+                       Some(signing_pubkey) => signing_pubkey,
+                       None => return Err(Bolt12SemanticError::MissingSigningPubkey),
+               };
+
+               <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey)
        }
 } }
 
@@ -855,6 +860,11 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
                        Some(keys) => keys,
                };
 
+               match $contents.contents.inner.offer.signing_pubkey() {
+                       Some(signing_pubkey) => debug_assert_eq!(signing_pubkey, keys.public_key()),
+                       None => return Err(Bolt12SemanticError::MissingSigningPubkey),
+               }
+
                <$builder>::for_offer_using_keys(
                        &$self.inner, payment_paths, created_at, payment_hash, keys
                )
@@ -1233,7 +1243,7 @@ mod tests {
                assert_eq!(unsigned_invoice_request.paths(), &[]);
                assert_eq!(unsigned_invoice_request.issuer(), None);
                assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One);
-               assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey());
+               assert_eq!(unsigned_invoice_request.signing_pubkey(), Some(recipient_pubkey()));
                assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
                assert_eq!(unsigned_invoice_request.amount_msats(), None);
                assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
@@ -1265,7 +1275,7 @@ mod tests {
                assert_eq!(invoice_request.paths(), &[]);
                assert_eq!(invoice_request.issuer(), None);
                assert_eq!(invoice_request.supported_quantity(), Quantity::One);
-               assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey());
+               assert_eq!(invoice_request.signing_pubkey(), Some(recipient_pubkey()));
                assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
                assert_eq!(invoice_request.amount_msats(), None);
                assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
@@ -2260,7 +2270,7 @@ mod tests {
                        .amount_msats(1000)
                        .supported_quantity(Quantity::Unbounded)
                        .build().unwrap();
-               assert_eq!(offer.signing_pubkey(), node_id);
+               assert_eq!(offer.signing_pubkey(), Some(node_id));
 
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .chain(Network::Testnet).unwrap()
index 3dedc6cd35d25166263603fb58fb6173faeb8935..b7852a0fc4b474edd5fff65d199d63a30e787783 100644 (file)
@@ -226,7 +226,7 @@ macro_rules! offer_explicit_metadata_builder_methods { (
                        offer: OfferContents {
                                chains: None, metadata: None, amount: None, description,
                                features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                               supported_quantity: Quantity::One, signing_pubkey,
+                               supported_quantity: Quantity::One, signing_pubkey: Some(signing_pubkey),
                        },
                        metadata_strategy: core::marker::PhantomData,
                        secp_ctx: None,
@@ -265,7 +265,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
                        offer: OfferContents {
                                chains: None, metadata: Some(metadata), amount: None, description,
                                features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                               supported_quantity: Quantity::One, signing_pubkey: node_id,
+                               supported_quantity: Quantity::One, signing_pubkey: Some(node_id),
                        },
                        metadata_strategy: core::marker::PhantomData,
                        secp_ctx: Some(secp_ctx),
@@ -391,7 +391,7 @@ macro_rules! offer_builder_methods { (
                                let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
                                metadata = derived_metadata;
                                if let Some(keys) = keys {
-                                       $self.offer.signing_pubkey = keys.public_key();
+                                       $self.offer.signing_pubkey = Some(keys.public_key());
                                }
                        }
 
@@ -542,7 +542,7 @@ pub(super) struct OfferContents {
        issuer: Option<String>,
        paths: Option<Vec<BlindedPath>>,
        supported_quantity: Quantity,
-       signing_pubkey: PublicKey,
+       signing_pubkey: Option<PublicKey>,
 }
 
 macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
@@ -604,7 +604,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
        }
 
        /// The public key used by the recipient to sign invoices.
-       pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
+       pub fn signing_pubkey(&$self) -> Option<bitcoin::secp256k1::PublicKey> {
                $contents.signing_pubkey()
        }
 } }
@@ -886,7 +886,7 @@ impl OfferContents {
                }
        }
 
-       pub(super) fn signing_pubkey(&self) -> PublicKey {
+       pub(super) fn signing_pubkey(&self) -> Option<PublicKey> {
                self.signing_pubkey
        }
 
@@ -905,8 +905,12 @@ impl OfferContents {
                                                _ => true,
                                        }
                                });
+                               let signing_pubkey = match self.signing_pubkey() {
+                                       Some(signing_pubkey) => signing_pubkey,
+                                       None => return Err(()),
+                               };
                                let keys = signer::verify_recipient_metadata(
-                                       metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
+                                       metadata, key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx
                                )?;
 
                                let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
@@ -941,7 +945,7 @@ impl OfferContents {
                        paths: self.paths.as_ref(),
                        issuer: self.issuer.as_ref(),
                        quantity_max: self.supported_quantity.to_tlv_record(),
-                       node_id: Some(&self.signing_pubkey),
+                       node_id: self.signing_pubkey.as_ref(),
                }
        }
 }
@@ -1091,7 +1095,7 @@ impl TryFrom<OfferTlvStream> for OfferContents {
 
                let signing_pubkey = match node_id {
                        None => return Err(Bolt12SemanticError::MissingSigningPubkey),
-                       Some(node_id) => node_id,
+                       Some(node_id) => Some(node_id),
                };
 
                Ok(OfferContents {
@@ -1154,7 +1158,7 @@ mod tests {
                assert_eq!(offer.paths(), &[]);
                assert_eq!(offer.issuer(), None);
                assert_eq!(offer.supported_quantity(), Quantity::One);
-               assert_eq!(offer.signing_pubkey(), pubkey(42));
+               assert_eq!(offer.signing_pubkey(), Some(pubkey(42)));
 
                assert_eq!(
                        offer.as_tlv_stream(),
@@ -1251,7 +1255,7 @@ mod tests {
                        ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
                        .amount_msats(1000)
                        .build().unwrap();
-               assert_eq!(offer.signing_pubkey(), node_id);
+               assert_eq!(offer.signing_pubkey(), Some(node_id));
 
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
@@ -1313,7 +1317,7 @@ mod tests {
                        .amount_msats(1000)
                        .path(blinded_path)
                        .build().unwrap();
-               assert_ne!(offer.signing_pubkey(), node_id);
+               assert_ne!(offer.signing_pubkey(), Some(node_id));
 
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
@@ -1471,7 +1475,7 @@ mod tests {
                        .unwrap();
                let tlv_stream = offer.as_tlv_stream();
                assert_eq!(offer.paths(), paths.as_slice());
-               assert_eq!(offer.signing_pubkey(), pubkey(42));
+               assert_eq!(offer.signing_pubkey(), Some(pubkey(42)));
                assert_ne!(pubkey(42), pubkey(44));
                assert_eq!(tlv_stream.paths, Some(&paths));
                assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));