From 76f8cc1cc66a27124b656d3a50fbf5792e9dd195 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 15:43:13 -0400 Subject: [PATCH] Support constructing BlindedPaths for payments. --- lightning/src/blinded_path/mod.rs | 22 ++++ lightning/src/blinded_path/payment.rs | 162 ++++++++++++++++++++++++++ lightning/src/blinded_path/utils.rs | 4 +- 3 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 lightning/src/blinded_path/payment.rs diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 691eda29a..89087a10c 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -9,6 +9,7 @@ //! Creating blinded paths and related utilities live here. +pub mod payment; pub(crate) mod message; pub(crate) mod utils; @@ -73,6 +74,27 @@ impl BlindedPath { blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) } + + /// Create a blinded path for a payment, to be forwarded along `path`. The last node + /// in `path` will be the destination node. + /// + /// Errors if `path` is empty or a node id in `path` is invalid. + // TODO: make all payloads the same size with padding + add dummy hops + pub fn new_for_payment( + intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey, + payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1 + ) -> Result { + let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); + let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); + + Ok(BlindedPath { + introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0), + blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), + blinded_hops: payment::blinded_hops( + secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret + ).map_err(|_| ())?, + }) + } } impl Writeable for BlindedPath { diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs new file mode 100644 index 000000000..0f6cf0185 --- /dev/null +++ b/lightning/src/blinded_path/payment.rs @@ -0,0 +1,162 @@ +//! Data structures and methods for constructing [`BlindedPath`]s to send a payment over. +//! +//! [`BlindedPath`]: crate::blinded_path::BlindedPath + +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; + +use crate::blinded_path::BlindedHop; +use crate::blinded_path::utils; +use crate::io; +use crate::ln::PaymentSecret; +use crate::ln::features::BlindedHopFeatures; +use crate::ln::msgs::DecodeError; +use crate::prelude::*; +use crate::util::ser::{Readable, Writeable, Writer}; + +/// Data to construct a [`BlindedHop`] for forwarding a payment. +pub struct ForwardTlvs { + /// The short channel id this payment should be forwarded out over. + short_channel_id: u64, + /// Payment parameters for relaying over [`Self::short_channel_id`]. + payment_relay: PaymentRelay, + /// Payment constraints for relaying over [`Self::short_channel_id`]. + payment_constraints: PaymentConstraints, + /// Supported and required features when relaying a payment onion containing this object's + /// corresponding [`BlindedHop::encrypted_payload`]. + /// + /// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload + features: BlindedHopFeatures, +} + +/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and +/// may not be valid if received by another lightning implementation. +pub struct ReceiveTlvs { + /// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together. + payment_secret: PaymentSecret, + /// Constraints for the receiver of this payment. + payment_constraints: PaymentConstraints, +} + +/// Data to construct a [`BlindedHop`] for sending a payment over. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub(crate) enum BlindedPaymentTlvs { + /// This blinded payment data is for a forwarding node. + Forward(ForwardTlvs), + /// This blinded payment data is for the receiving node. + Receive(ReceiveTlvs), +} + +// Used to include forward and receive TLVs in the same iterator for encoding. +enum BlindedPaymentTlvsRef<'a> { + Forward(&'a ForwardTlvs), + Receive(&'a ReceiveTlvs), +} + +/// Parameters for relaying over a given [`BlindedHop`]. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub struct PaymentRelay { + /// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub cltv_expiry_delta: u16, + /// Liquidity fee charged (in millionths of the amount transferred) for relaying a payment over + /// this [`BlindedHop`], (i.e., 10,000 is 1%). + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub fee_proportional_millionths: u32, + /// Base fee charged (in millisatoshi) for relaying a payment over this [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub fee_base_msat: u32, +} + +/// Constraints for relaying over a given [`BlindedHop`]. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub struct PaymentConstraints { + /// The maximum total CLTV delta that is acceptable when relaying a payment over this + /// [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub max_cltv_expiry: u32, + /// The minimum value, in msat, that may be relayed over this [`BlindedHop`]. + pub htlc_minimum_msat: u64, +} + +impl_writeable_tlv_based!(ForwardTlvs, { + (2, short_channel_id, required), + (10, payment_relay, required), + (12, payment_constraints, required), + (14, features, required), +}); + +impl_writeable_tlv_based!(ReceiveTlvs, { + (12, payment_constraints, required), + (65536, payment_secret, required), +}); + +impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + // TODO: write padding + match self { + Self::Forward(tlvs) => tlvs.write(w)?, + Self::Receive(tlvs) => tlvs.write(w)?, + } + Ok(()) + } +} + +impl Readable for BlindedPaymentTlvs { + fn read(r: &mut R) -> Result { + _init_and_read_tlv_stream!(r, { + (1, _padding, option), + (2, scid, option), + (10, payment_relay, option), + (12, payment_constraints, required), + (14, features, option), + (65536, payment_secret, option), + }); + let _padding: Option = _padding; + + if let Some(short_channel_id) = scid { + if payment_secret.is_some() { return Err(DecodeError::InvalidValue) } + Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { + short_channel_id, + payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + features: features.ok_or(DecodeError::InvalidValue)?, + })) + } else { + if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) } + Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { + payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + })) + } + } +} + +/// Construct blinded payment hops for the given `intermediate_nodes` and payee info. +pub(super) fn blinded_hops( + secp_ctx: &Secp256k1, intermediate_nodes: &[(PublicKey, ForwardTlvs)], + payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey +) -> Result, secp256k1::Error> { + let pks = intermediate_nodes.iter().map(|(pk, _)| pk) + .chain(core::iter::once(&payee_node_id)); + let tlvs = intermediate_nodes.iter().map(|(_, tlvs)| BlindedPaymentTlvsRef::Forward(tlvs)) + .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); + utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) +} + +impl_writeable_msg!(PaymentRelay, { + cltv_expiry_delta, + fee_proportional_millionths, + fee_base_msat +}, {}); + +impl_writeable_msg!(PaymentConstraints, { + max_cltv_expiry, + htlc_minimum_msat +}, {}); diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 9b1ce50b1..e1feed97a 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -111,11 +111,11 @@ pub(super) fn construct_blinded_hops<'a, T, I1, I2>( ) -> Result, secp256k1::Error> where T: secp256k1::Signing + secp256k1::Verification, - I1: ExactSizeIterator, + I1: Iterator, I2: Iterator, I2::Item: Writeable { - let mut blinded_hops = Vec::with_capacity(unblinded_pks.len()); + let mut blinded_hops = Vec::with_capacity(unblinded_pks.size_hint().0); construct_keys_callback( secp_ctx, unblinded_pks, None, session_priv, |blinded_node_id, _, _, encrypted_payload_rho, _, _| { -- 2.39.5