Utility for creating and sending Bolt12Invoices
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 15 Sep 2023 18:40:41 +0000 (13:40 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 20 Oct 2023 14:49:56 +0000 (09:49 -0500)
Add a utility to ChannelManager for creating a Bolt12Invoice for a
Refund such that the ChannelManager can recognize the PaymentHash and
reconstruct the PaymentPreimage from the PaymentSecret, the latter of
which is contained in a BlindedPath within the invoice.

lightning/src/blinded_path/mod.rs
lightning/src/ln/channelmanager.rs

index 1415836c01fee6cacaa8d2808f28d6d9ecfba37a..d75b4f25b368481d8d8a08ca1966d416b0886745 100644 (file)
@@ -84,7 +84,7 @@ impl BlindedPath {
        }
 
        /// Create a one-hop blinded path for a payment.
-       pub fn one_hop_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
+       pub fn one_hop_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
                payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES,
                secp_ctx: &Secp256k1<T>
        ) -> Result<(BlindedPayInfo, Self), ()> {
@@ -105,7 +105,7 @@ impl BlindedPath {
        ///
        /// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
        //  TODO: make all payloads the same size with padding + add dummy hops
-       pub(crate) fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
+       pub(crate) fn new_for_payment<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
                intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
                payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
                secp_ctx: &Secp256k1<T>
index 3e7bac3d43b2d140cffaef960673b425420b74b5..2df3b5d416376cdeed5c62ba26a40b92f6b530a2 100644 (file)
@@ -31,6 +31,7 @@ use bitcoin::secp256k1::Secp256k1;
 use bitcoin::{LockTime, secp256k1, Sequence};
 
 use crate::blinded_path::BlindedPath;
+use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
 use crate::chain;
 use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
 use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
@@ -56,9 +57,10 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
 use crate::ln::outbound_payment;
 use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
 use crate::ln::wire::Encode;
+use crate::offers::invoice::{BlindedPayInfo, DEFAULT_RELATIVE_EXPIRY};
 use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
 use crate::offers::parse::Bolt12SemanticError;
-use crate::offers::refund::RefundBuilder;
+use crate::offers::refund::{Refund, RefundBuilder};
 use crate::onion_message::{Destination, OffersMessage, PendingOnionMessage};
 use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
 use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
@@ -7445,6 +7447,67 @@ where
                Ok(())
        }
 
+       /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion
+       /// message.
+       ///
+       /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a
+       /// [`BlindedPath`] containing the [`PaymentSecret`] needed to reconstruct the corresponding
+       /// [`PaymentPreimage`].
+       ///
+       /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+       pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
+               let expanded_key = &self.inbound_payment_key;
+               let entropy = &*self.entropy_source;
+               let secp_ctx = &self.secp_ctx;
+
+               let amount_msats = refund.amount_msats();
+               let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+
+               match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
+                       Ok((payment_hash, payment_secret)) => {
+                               let payment_paths = vec![
+                                       self.create_one_hop_blinded_payment_path(payment_secret),
+                               ];
+                               #[cfg(not(feature = "no-std"))]
+                               let builder = refund.respond_using_derived_keys(
+                                       payment_paths, payment_hash, expanded_key, entropy
+                               )?;
+                               #[cfg(feature = "no-std")]
+                               let created_at = Duration::from_secs(
+                                       self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+                               );
+                               #[cfg(feature = "no-std")]
+                               let builder = refund.respond_using_derived_keys_no_std(
+                                       payment_paths, payment_hash, created_at, expanded_key, entropy
+                               )?;
+                               let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
+                               let reply_path = self.create_one_hop_blinded_path();
+
+                               let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
+                               if refund.paths().is_empty() {
+                                       let message = PendingOnionMessage {
+                                               contents: OffersMessage::Invoice(invoice),
+                                               destination: Destination::Node(refund.payer_id()),
+                                               reply_path: Some(reply_path),
+                                       };
+                                       pending_offers_messages.push(message);
+                               } else {
+                                       for path in refund.paths() {
+                                               let message = PendingOnionMessage {
+                                                       contents: OffersMessage::Invoice(invoice.clone()),
+                                                       destination: Destination::BlindedPath(path.clone()),
+                                                       reply_path: Some(reply_path.clone()),
+                                               };
+                                               pending_offers_messages.push(message);
+                                       }
+                               }
+
+                               Ok(())
+                       },
+                       Err(()) => Err(Bolt12SemanticError::InvalidAmount),
+               }
+       }
+
        /// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
        /// to pay us.
        ///
@@ -7553,6 +7616,29 @@ where
                BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
        }
 
+       /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
+       /// node.
+       fn create_one_hop_blinded_payment_path(
+               &self, payment_secret: PaymentSecret
+       ) -> (BlindedPayInfo, BlindedPath) {
+               let entropy_source = self.entropy_source.deref();
+               let secp_ctx = &self.secp_ctx;
+
+               let payee_node_id = self.get_our_node_id();
+               let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS;
+               let payee_tlvs = ReceiveTlvs {
+                       payment_secret,
+                       payment_constraints: PaymentConstraints {
+                               max_cltv_expiry,
+                               htlc_minimum_msat: 1,
+                       },
+               };
+               // TODO: Err for overflow?
+               BlindedPath::one_hop_for_payment(
+                       payee_node_id, payee_tlvs, entropy_source, secp_ctx
+               ).unwrap()
+       }
+
        /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
        /// are used when constructing the phantom invoice's route hints.
        ///