Support paying blinded paths.
authorValentine Wallace <vwallace@protonmail.com>
Thu, 13 Jul 2023 02:32:06 +0000 (22:32 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Tue, 12 Sep 2023 22:11:54 +0000 (18:11 -0400)
lightning/src/ln/msgs.rs
lightning/src/ln/onion_utils.rs
lightning/src/ln/outbound_payment.rs

index 6bd5ec3f7293fcac5c043bf3437e47eb90127d9d..8bb264fe9c6332a9950bda2045a27faf35ab553c 100644 (file)
@@ -1517,6 +1517,7 @@ pub trait OnionMessageHandler : OnionMessageProvider {
 }
 
 mod fuzzy_internal_msgs {
+       use bitcoin::secp256k1::PublicKey;
        use crate::prelude::*;
        use crate::ln::{PaymentPreimage, PaymentSecret};
 
@@ -1562,6 +1563,17 @@ mod fuzzy_internal_msgs {
                        amt_msat: u64,
                        outgoing_cltv_value: u32,
                },
+               BlindedForward {
+                       encrypted_tlvs: Vec<u8>,
+                       intro_node_blinding_point: Option<PublicKey>,
+               },
+               BlindedReceive {
+                       amt_msat: u64,
+                       total_msat: u64,
+                       outgoing_cltv_value: u32,
+                       encrypted_tlvs: Vec<u8>,
+                       intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
+               }
        }
 
        pub struct DecodedOnionErrorPacket {
@@ -2097,6 +2109,24 @@ impl Writeable for OutboundOnionPayload {
                                        (16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
                                }, custom_tlvs.iter());
                        },
+                       Self::BlindedForward { encrypted_tlvs, intro_node_blinding_point } => {
+                               _encode_varint_length_prefixed_tlv!(w, {
+                                       (10, *encrypted_tlvs, required_vec),
+                                       (12, intro_node_blinding_point, option)
+                               });
+                       },
+                       Self::BlindedReceive {
+                               amt_msat, total_msat, outgoing_cltv_value, encrypted_tlvs,
+                               intro_node_blinding_point,
+                       } => {
+                               _encode_varint_length_prefixed_tlv!(w, {
+                                       (2, HighZeroBytesDroppedBigSize(*amt_msat), required),
+                                       (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
+                                       (10, *encrypted_tlvs, required_vec),
+                                       (12, intro_node_blinding_point, option),
+                                       (18, HighZeroBytesDroppedBigSize(*total_msat), required)
+                               });
+                       },
                }
                Ok(())
        }
index 8fdbdefef6592766b52b9ad0cf142e55ad045990..8782323ea77333d0ce1e46a17c17aebea2580051 100644 (file)
@@ -12,7 +12,7 @@ use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
 use crate::ln::msgs;
 use crate::ln::wire::Encode;
 use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::{Path, RouteHop};
+use crate::routing::router::{BlindedTail, Path, RouteHop};
 use crate::util::chacha20::{ChaCha20, ChaChaReader};
 use crate::util::errors::{self, APIError};
 use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
@@ -169,7 +169,9 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
        let mut cur_value_msat = 0u64;
        let mut cur_cltv = starting_htlc_offset;
        let mut last_short_channel_id = 0;
-       let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(path.hops.len());
+       let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
+               path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len())
+       );
 
        for (idx, hop) in path.hops.iter().rev().enumerate() {
                // First hop gets special values so that it can check, on receipt, that everything is
@@ -177,27 +179,51 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
                // the intended recipient).
                let value_msat = if cur_value_msat == 0 { hop.fee_msat } else { cur_value_msat };
                let cltv = if cur_cltv == starting_htlc_offset { hop.cltv_expiry_delta + starting_htlc_offset } else { cur_cltv };
-               res.insert(0, if idx == 0 {
-                       msgs::OutboundOnionPayload::Receive {
-                               payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
-                                       Some(msgs::FinalOnionHopData {
-                                               payment_secret: secret,
-                                               total_msat,
-                                       })
-                               } else { None },
-                               payment_metadata: recipient_onion.payment_metadata.take(),
-                               keysend_preimage: *keysend_preimage,
-                               custom_tlvs: recipient_onion.custom_tlvs.clone(),
-                               amt_msat: value_msat,
-                               outgoing_cltv_value: cltv,
+               if idx == 0 {
+                       if let Some(BlindedTail {
+                               blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, ..
+                       }) = &path.blinded_tail {
+                               let mut blinding_point = Some(*blinding_point);
+                               for (i, blinded_hop) in hops.iter().enumerate() {
+                                       if i == hops.len() - 1 {
+                                               cur_value_msat += final_value_msat;
+                                               cur_cltv += excess_final_cltv_expiry_delta;
+                                               res.push(msgs::OutboundOnionPayload::BlindedReceive {
+                                                       amt_msat: *final_value_msat,
+                                                       total_msat,
+                                                       outgoing_cltv_value: cltv,
+                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+                                                       intro_node_blinding_point: blinding_point.take(),
+                                               });
+                                       } else {
+                                               res.push(msgs::OutboundOnionPayload::BlindedForward {
+                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+                                                       intro_node_blinding_point: blinding_point.take(),
+                                               });
+                                       }
+                               }
+                       } else {
+                               res.push(msgs::OutboundOnionPayload::Receive {
+                                       payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
+                                               Some(msgs::FinalOnionHopData {
+                                                       payment_secret: secret,
+                                                       total_msat,
+                                               })
+                                       } else { None },
+                                       payment_metadata: recipient_onion.payment_metadata.take(),
+                                       keysend_preimage: *keysend_preimage,
+                                       custom_tlvs: recipient_onion.custom_tlvs.clone(),
+                                       amt_msat: value_msat,
+                                       outgoing_cltv_value: cltv,
+                               });
                        }
                } else {
-                       msgs::OutboundOnionPayload::Forward {
+                       res.insert(0, msgs::OutboundOnionPayload::Forward {
                                short_channel_id: last_short_channel_id,
                                amt_to_forward: value_msat,
                                outgoing_cltv_value: cltv,
-                       }
-               });
+                       });
+               }
                cur_value_msat += hop.fee_msat;
                if cur_value_msat >= 21000000 * 100000000 * 1000 {
                        return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()});
index 0cc9e7e0531fb3bb6d1bc0d7dd38bff274b8333e..5ea772e5d4ffbfb0b82c9fc19e014781e3219aa0 100644 (file)
@@ -1234,7 +1234,9 @@ impl OutboundPayments {
                if route.paths.len() < 1 {
                        return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()}));
                }
-               if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 {
+               if recipient_onion.payment_secret.is_none() && route.paths.len() > 1
+                       && !route.paths.iter().any(|p| p.blinded_tail.is_some())
+               {
                        return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
                }
                let mut total_value = 0;
@@ -1245,10 +1247,6 @@ impl OutboundPayments {
                                path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
                                continue 'path_check;
                        }
-                       if path.blinded_tail.is_some() {
-                               path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
-                               continue 'path_check;
-                       }
                        let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
                                usize::max_value() } else { path.hops.len() - 1 };
                        for (idx, hop) in path.hops.iter().enumerate() {