diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 40a840eb164..8637ef8ad3c 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -128,8 +128,9 @@ impl Router for FuzzRouter { } fn create_blinded_payment_paths( - &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: Option, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _receive_auth_key: ReceiveAuthKey, + _first_hops: Vec, _tlvs: ReceiveTlvs, _amount_msats: Option, + _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index ee5f4572eb2..f37a1ac17bb 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -62,7 +62,6 @@ use lightning::sign::{ }; use lightning::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::util::config::{ChannelConfig, UserConfig}; -use lightning::util::errors::APIError; use lightning::util::hash_tables::*; use lightning::util::logger::Logger; use lightning::util::ser::{Readable, Writeable}; @@ -157,8 +156,9 @@ impl Router for FuzzRouter { } fn create_blinded_payment_paths( - &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: Option, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _receive_auth_key: ReceiveAuthKey, + _first_hops: Vec, _tlvs: ReceiveTlvs, _amount_msats: Option, + _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 96d8515f0b5..a21303debd7 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -12,16 +12,15 @@ use bitcoin::secp256k1::{self, Keypair, Parity, PublicKey, Secp256k1, SecretKey} use core::convert::TryFrom; use lightning::blinded_path::payment::{ BlindedPaymentPath, Bolt12OfferContext, ForwardTlvs, PaymentConstraints, PaymentContext, - PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs, + PaymentForwardNode, PaymentRelay, ReceiveTlvs, }; use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; use lightning::ln::inbound_payment::ExpandedKey; use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; -use lightning::offers::nonce::Nonce; use lightning::offers::offer::OfferId; use lightning::offers::parse::Bolt12SemanticError; -use lightning::sign::EntropySource; +use lightning::sign::{EntropySource, ReceiveAuthKey}; use lightning::types::features::BlindedHopFeatures; use lightning::types::payment::{PaymentHash, PaymentSecret}; use lightning::types::string::UntrustedString; @@ -84,7 +83,7 @@ fn build_response( ) -> Result { let expanded_key = ExpandedKey::new([42; 32]); let entropy_source = Randomness {}; - let nonce = Nonce::from_entropy_source(&entropy_source); + let receive_auth_key = ReceiveAuthKey([41; 32]); let invoice_request_fields = if let Ok(ver) = invoice_request.clone().verify_using_metadata(&expanded_key, secp_ctx) { @@ -106,7 +105,7 @@ fn build_response( offer_id: OfferId([42; 32]), invoice_request: invoice_request_fields, }); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([42; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 1_000_000, @@ -114,7 +113,6 @@ fn build_response( }, payment_context, }; - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let intermediate_nodes = [PaymentForwardNode { tlvs: ForwardTlvs { short_channel_id: 43, @@ -124,7 +122,7 @@ fn build_response( fee_base_msat: 1, }, payment_constraints: PaymentConstraints { - max_cltv_expiry: payee_tlvs.tlvs().payment_constraints.max_cltv_expiry + 40, + max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40, htlc_minimum_msat: 100, }, features: BlindedHopFeatures::empty(), @@ -136,6 +134,7 @@ fn build_response( let payment_path = BlindedPaymentPath::new( &intermediate_nodes, pubkey(42), + receive_auth_key, payee_tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 6151d810344..446ac704455 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -12,15 +12,13 @@ use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1, SecretKey}; use core::convert::TryFrom; use lightning::blinded_path::payment::{ BlindedPaymentPath, Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext, - PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs, + PaymentForwardNode, PaymentRelay, ReceiveTlvs, }; use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; -use lightning::ln::inbound_payment::ExpandedKey; use lightning::offers::invoice::UnsignedBolt12Invoice; -use lightning::offers::nonce::Nonce; use lightning::offers::parse::Bolt12SemanticError; use lightning::offers::refund::Refund; -use lightning::sign::EntropySource; +use lightning::sign::{EntropySource, ReceiveAuthKey}; use lightning::types::features::BlindedHopFeatures; use lightning::types::payment::{PaymentHash, PaymentSecret}; use lightning::util::ser::Writeable; @@ -69,11 +67,10 @@ fn privkey(byte: u8) -> SecretKey { fn build_response( refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1, ) -> Result { - let expanded_key = ExpandedKey::new([42; 32]); let entropy_source = Randomness {}; - let nonce = Nonce::from_entropy_source(&entropy_source); + let receive_auth_key = ReceiveAuthKey([41; 32]); let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([42; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 1_000_000, @@ -81,7 +78,6 @@ fn build_response( }, payment_context, }; - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let intermediate_nodes = [PaymentForwardNode { tlvs: ForwardTlvs { short_channel_id: 43, @@ -91,7 +87,7 @@ fn build_response( fee_base_msat: 1, }, payment_constraints: PaymentConstraints { - max_cltv_expiry: payee_tlvs.tlvs().payment_constraints.max_cltv_expiry + 40, + max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40, htlc_minimum_msat: 100, }, features: BlindedHopFeatures::empty(), @@ -103,6 +99,7 @@ fn build_response( let payment_path = BlindedPaymentPath::new( &intermediate_nodes, pubkey(42), + receive_auth_key, payee_tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 4ae10f75961..7c203c2a2ef 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -9,26 +9,23 @@ //! Data structures and methods for constructing [`BlindedPaymentPath`]s to send a payment over. -use bitcoin::hashes::hmac::Hmac; -use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; +use crate::blinded_path::message::MAX_DUMMY_HOPS_COUNT; use crate::blinded_path::utils::{self, BlindedPathWithPadding}; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; -use crate::crypto::streams::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaDualPolyReadAdapter; use crate::io; use crate::io::Cursor; use crate::ln::channel_state::CounterpartyForwardingInfo; -use crate::ln::channelmanager::Verification; -use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::offers::invoice_request::InvoiceRequestFields; use crate::offers::nonce::Nonce; use crate::offers::offer::OfferId; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey, Recipient}; use crate::types::features::BlindedHopFeatures; use crate::types::payment::PaymentSecret; use crate::types::routing::RoutingFees; @@ -93,8 +90,8 @@ pub struct BlindedPaymentPath { impl BlindedPaymentPath { /// Create a one-hop blinded path for a payment. pub fn one_hop( - payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, min_final_cltv_expiry_delta: u16, - entropy_source: ES, secp_ctx: &Secp256k1, + payee_node_id: PublicKey, receive_auth_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, + min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, @@ -105,6 +102,7 @@ impl BlindedPaymentPath { Self::new( &[], payee_node_id, + receive_auth_key, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, @@ -121,8 +119,34 @@ impl BlindedPaymentPath { // TODO: make all payloads the same size with padding + add dummy hops pub fn new( intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, - payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, - entropy_source: ES, secp_ctx: &Secp256k1, + receive_auth_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64, + min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1, + ) -> Result + where + ES::Target: EntropySource, + { + BlindedPaymentPath::new_with_dummy_hops( + intermediate_nodes, + payee_node_id, + 0, + receive_auth_key, + payee_tlvs, + htlc_maximum_msat, + min_final_cltv_expiry_delta, + entropy_source, + secp_ctx, + ) + } + + /// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops. + /// + /// Note: + /// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path. + pub fn new_with_dummy_hops( + intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, + dummy_hop_count: usize, receive_auth_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, + htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: ES, + secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, @@ -136,7 +160,7 @@ impl BlindedPaymentPath { let blinded_payinfo = compute_payinfo( intermediate_nodes, - &payee_tlvs.tlvs, + &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, )?; @@ -148,8 +172,10 @@ impl BlindedPaymentPath { secp_ctx, intermediate_nodes, payee_node_id, + dummy_hop_count, payee_tlvs, &blinding_secret, + receive_auth_key, ), }, payinfo: blinded_payinfo, @@ -226,12 +252,13 @@ impl BlindedPaymentPath { let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?; let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); + let receive_auth_key = node_signer.get_receive_auth_key(); let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload; let mut s = Cursor::new(encrypted_control_tlvs); let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { readable, .. }) => Ok((readable, control_tlvs_ss)), + match ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0)) { + Ok(ChaChaDualPolyReadAdapter { readable, .. }) => Ok((readable, control_tlvs_ss)), _ => Err(()), } } @@ -323,28 +350,19 @@ pub struct TrampolineForwardTlvs { pub next_blinding_override: Option, } +/// Represents the dummy TLV encoded immediately before the actual [`ReceiveTlvs`] in a blinded path. +/// These TLVs are intended for the final node and are recursively authenticated until the real +/// [`ReceiveTlvs`] is reached. +/// +/// Their purpose is to arbitrarily extend the path length, obscuring the receiver's position in the +/// route and thereby enhancing privacy. +#[derive(Debug)] +pub(crate) struct PaymentDummyTlv; + /// 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. -/// -/// Can only be constructed by calling [`UnauthenticatedReceiveTlvs::authenticate`]. #[derive(Clone, Debug)] pub struct ReceiveTlvs { - /// The TLVs for which the HMAC in `authentication` is derived. - pub(crate) tlvs: UnauthenticatedReceiveTlvs, - /// An HMAC of `tlvs` along with a nonce used to construct it. - pub(crate) authentication: (Hmac, Nonce), -} - -impl ReceiveTlvs { - /// Returns the underlying TLVs. - pub fn tlvs(&self) -> &UnauthenticatedReceiveTlvs { - &self.tlvs - } -} - -/// An unauthenticated [`ReceiveTlvs`]. -#[derive(Clone, Debug)] -pub struct UnauthenticatedReceiveTlvs { /// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together. pub payment_secret: PaymentSecret, /// Constraints for the receiver of this payment. @@ -353,23 +371,14 @@ pub struct UnauthenticatedReceiveTlvs { pub payment_context: PaymentContext, } -impl UnauthenticatedReceiveTlvs { - /// Creates an authenticated [`ReceiveTlvs`], which includes an HMAC and the provide [`Nonce`] - /// that can be use later to verify it authenticity. - pub fn authenticate(self, nonce: Nonce, expanded_key: &ExpandedKey) -> ReceiveTlvs { - ReceiveTlvs { - authentication: (self.hmac_for_offer_payment(nonce, expanded_key), nonce), - tlvs: self, - } - } -} - /// 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 dummy and is to be peeled by receiving node. + Dummy(PaymentDummyTlv), /// This blinded payment data is for the receiving node. Receive(ReceiveTlvs), } @@ -387,6 +396,7 @@ pub(crate) enum BlindedTrampolineTlvs { // Used to include forward and receive TLVs in the same iterator for encoding. enum BlindedPaymentTlvsRef<'a> { Forward(&'a ForwardTlvs), + Dummy(&'a PaymentDummyTlv), Receive(&'a ReceiveTlvs), } @@ -536,19 +546,21 @@ impl Writeable for TrampolineForwardTlvs { } } -impl Writeable for ReceiveTlvs { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - encode_tlv_stream!(w, { - (12, self.tlvs.payment_constraints, required), - (65536, self.tlvs.payment_secret, required), - (65537, self.tlvs.payment_context, required), - (65539, self.authentication, required), +impl Writeable for PaymentDummyTlv { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + encode_tlv_stream!(writer, { + (65539, (), required), }); Ok(()) } } -impl Writeable for UnauthenticatedReceiveTlvs { +// Note: Authentication TLV field was removed in LDK v0.2 following the +// introduction of `ReceiveAuthKey`-based authentication for inbound +// `BlindedPaymentPaths`s. Because we do not support receiving to those +// contexts anymore (they will fail the `ReceiveAuthKey`-based +// authentication checks), we can reuse those fields here. +impl Writeable for ReceiveTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { (12, self.payment_constraints, required), @@ -563,6 +575,7 @@ impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { match self { Self::Forward(tlvs) => tlvs.write(w)?, + Self::Dummy(tlv) => tlv.write(w)?, Self::Receive(tlvs) => tlvs.write(w)?, } Ok(()) @@ -579,36 +592,50 @@ impl Readable for BlindedPaymentTlvs { (2, scid, option), (8, next_blinding_override, option), (10, payment_relay, option), - (12, payment_constraints, required), + (12, payment_constraints, option), (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, option), - (65539, authentication, option), + (65539, is_dummy, option) }); - if let Some(short_channel_id) = scid { - if payment_secret.is_some() { - return Err(DecodeError::InvalidValue); - } - Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { + match ( + scid, + next_blinding_override, + payment_relay, + payment_constraints, + features, + payment_secret, + payment_context, + is_dummy, + ) { + ( + Some(short_channel_id), + next_override, + Some(relay), + Some(constraints), + features, + None, + None, + None, + ) => Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, - payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, - payment_constraints: payment_constraints.0.unwrap(), - next_blinding_override, + payment_relay: relay, + payment_constraints: constraints, + next_blinding_override: next_override, features: features.unwrap_or_else(BlindedHopFeatures::empty), - })) - } else { - if payment_relay.is_some() || features.is_some() { - return Err(DecodeError::InvalidValue); - } - Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { - tlvs: UnauthenticatedReceiveTlvs { - payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, - payment_constraints: payment_constraints.0.unwrap(), - payment_context: payment_context.ok_or(DecodeError::InvalidValue)?, - }, - authentication: authentication.ok_or(DecodeError::InvalidValue)?, - })) + })), + (None, None, None, Some(constraints), None, Some(secret), Some(context), None) => { + Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { + payment_secret: secret, + payment_constraints: constraints, + payment_context: context, + })) + }, + (None, None, None, None, None, None, None, Some(())) => { + Ok(BlindedPaymentTlvs::Dummy(PaymentDummyTlv)) + }, + _ => return Err(DecodeError::InvalidValue), } } } @@ -623,7 +650,6 @@ impl Readable for BlindedTrampolineTlvs { (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, option), - (65539, authentication, option), }); if let Some(next_trampoline) = next_trampoline { @@ -642,33 +668,33 @@ impl Readable for BlindedTrampolineTlvs { return Err(DecodeError::InvalidValue); } Ok(BlindedTrampolineTlvs::Receive(ReceiveTlvs { - tlvs: UnauthenticatedReceiveTlvs { - payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, - payment_constraints: payment_constraints.0.unwrap(), - payment_context: payment_context.ok_or(DecodeError::InvalidValue)?, - }, - authentication: authentication.ok_or(DecodeError::InvalidValue)?, + payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + payment_context: payment_context.ok_or(DecodeError::InvalidValue)?, })) } } } -/// Represents the padding round off size (in bytes) that -/// is used to pad payment bilnded path's [`BlindedHop`] +/// Represents the padding round-off size (in bytes) used to pad payment blinded path's [`BlindedHop`]. pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30; /// Construct blinded payment hops for the given `intermediate_nodes` and payee info. pub(super) fn blinded_hops( secp_ctx: &Secp256k1, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, - payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, + dummy_hop_count: usize, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, + local_node_receive_key: ReceiveAuthKey, ) -> Vec { + let dummy_count = core::cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT); let pks = intermediate_nodes .iter() .map(|node| (node.node_id, None)) - .chain(core::iter::once((payee_node_id, None))); + .chain(core::iter::repeat((payee_node_id, Some(local_node_receive_key))).take(dummy_count)) + .chain(core::iter::once((payee_node_id, Some(local_node_receive_key)))); let tlvs = intermediate_nodes .iter() .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) + .chain((0..dummy_count).map(|_| BlindedPaymentTlvsRef::Dummy(&PaymentDummyTlv))) .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); let path = pks.zip( @@ -734,7 +760,7 @@ where } pub(super) fn compute_payinfo( - intermediate_nodes: &[PaymentForwardNode], payee_tlvs: &UnauthenticatedReceiveTlvs, + intermediate_nodes: &[PaymentForwardNode], payee_tlvs: &ReceiveTlvs, payee_htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, ) -> Result { let (aggregated_base_fee, aggregated_prop_fee) = @@ -857,7 +883,7 @@ impl_writeable_tlv_based!(Bolt12RefundContext, {}); mod tests { use crate::blinded_path::payment::{ Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentForwardNode, - PaymentRelay, UnauthenticatedReceiveTlvs, + PaymentRelay, ReceiveTlvs, }; use crate::ln::functional_test_utils::TEST_FINAL_CLTV; use crate::types::features::BlindedHopFeatures; @@ -907,7 +933,7 @@ mod tests { htlc_maximum_msat: u64::max_value(), }, ]; - let recv_tlvs = UnauthenticatedReceiveTlvs { + let recv_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), @@ -925,7 +951,7 @@ mod tests { #[test] fn compute_payinfo_1_hop() { - let recv_tlvs = UnauthenticatedReceiveTlvs { + let recv_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), @@ -982,7 +1008,7 @@ mod tests { htlc_maximum_msat: u64::max_value(), }, ]; - let recv_tlvs = UnauthenticatedReceiveTlvs { + let recv_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 3 }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), @@ -1041,7 +1067,7 @@ mod tests { htlc_maximum_msat: u64::max_value(), }, ]; - let recv_tlvs = UnauthenticatedReceiveTlvs { + let recv_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), @@ -1110,7 +1136,7 @@ mod tests { htlc_maximum_msat: 10_000, }, ]; - let recv_tlvs = UnauthenticatedReceiveTlvs { + let recv_tlvs = ReceiveTlvs { payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, htlc_minimum_msat: 1 }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index d56670f4d67..78250e69f83 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -272,7 +272,6 @@ fn pass_async_payments_oms( fn create_static_invoice_builder<'a>( recipient: &Node, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, ) -> StaticInvoiceBuilder<'a> { - let entropy = recipient.keys_manager; let amount_msat = offer.amount().and_then(|amount| match amount { Amount::Bitcoin { amount_msats } => Some(amount_msats), Amount::Currency { .. } => None, @@ -296,7 +295,6 @@ fn create_static_invoice_builder<'a>( .flow .create_static_invoice_builder( &recipient.router, - entropy, offer, offer_nonce, payment_secret, @@ -1860,7 +1858,7 @@ fn expired_static_invoice_payment_path() { .advance_path_by_one(&nodes[1].keys_manager, &nodes[1].node, &secp_ctx) .unwrap(); match blinded_path.decrypt_intro_payload(&nodes[2].keys_manager).unwrap().0 { - BlindedPaymentTlvs::Receive(tlvs) => tlvs.tlvs.payment_constraints.max_cltv_expiry, + BlindedPaymentTlvs::Receive(tlvs) => tlvs.payment_constraints.max_cltv_expiry, _ => panic!(), } }; @@ -3106,7 +3104,6 @@ fn intercepted_hold_htlc() { .flow .test_create_blinded_payment_paths( &recipient.router, - recipient.keys_manager, first_hops, None, payment_secret, diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 25fa5e71e5d..1f191bd90f8 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -17,7 +17,7 @@ use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use crate::blinded_path; -use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs, PAYMENT_PADDING_ROUND_OFF}; +use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentForwardNode, PaymentRelay, ReceiveTlvs, PAYMENT_PADDING_ROUND_OFF}; use crate::blinded_path::utils::is_padded; use crate::events::{Event, HTLCHandlingFailureType, PaymentFailureReason}; use crate::ln::types::ChannelId; @@ -33,7 +33,6 @@ use crate::ln::onion_payment; use crate::ln::onion_utils::{self, LocalHTLCFailureReason}; use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::nonce::Nonce; use crate::prelude::*; use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters, TrampolineHop}; use crate::sign::{NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient}; @@ -76,7 +75,7 @@ pub fn blinded_payment_path( }); } - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -86,14 +85,12 @@ pub fn blinded_payment_path( payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = keys_manager.get_receive_auth_key(); let mut secp_ctx = Secp256k1::new(); BlindedPaymentPath::new( - &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, - intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat), + &intermediate_nodes[..], *node_ids.last().unwrap(), receive_auth_key, + payee_tlvs, intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat), TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx ).unwrap() } @@ -163,7 +160,7 @@ fn do_one_hop_blinded_path(success: bool) { let amt_msat = 5000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -171,13 +168,12 @@ fn do_one_hop_blinded_path(success: bool) { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &[], nodes[1].node.get_our_node_id(), receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); @@ -196,6 +192,58 @@ fn do_one_hop_blinded_path(success: bool) { } } +#[test] +fn one_hop_blinded_path_with_dummy_hops() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); + let payee_tlvs = ReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: chan_upd.htlc_minimum_msat, + }, + payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), + }; + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); + + let mut secp_ctx = Secp256k1::new(); + let blinded_path = BlindedPaymentPath::new_with_dummy_hops( + &[], nodes[1].node.get_our_node_id(), 1, receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &chanmon_cfgs[1].keys_manager, &secp_ctx + ).unwrap(); + + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::blinded(vec![blinded_path]), + amt_msat, + ); + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), + PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + pass_along_path_with_dummy( + &nodes[0], + &[&nodes[1]], + Some(1), + amt_msat, + payment_hash, + Some(payment_secret), + ev, + true, + None, + ); + claim_payment(&nodes[0], &[&nodes[1]], payment_preimage); +} + #[test] fn mpp_to_one_hop_blinded_path() { let chanmon_cfgs = create_chanmon_cfgs(4); @@ -217,7 +265,7 @@ fn mpp_to_one_hop_blinded_path() { let amt_msat = 15_000_000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -225,11 +273,10 @@ fn mpp_to_one_hop_blinded_path() { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = chanmon_cfgs[3].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = chanmon_cfgs[3].keys_manager.get_receive_auth_key(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &[], nodes[3].node.get_our_node_id(), receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[3].keys_manager, &secp_ctx ).unwrap(); @@ -1327,7 +1374,7 @@ fn custom_tlvs_to_blinded_path() { let amt_msat = 5000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -1335,12 +1382,12 @@ fn custom_tlvs_to_blinded_path() { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); + let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &[], nodes[1].node.get_our_node_id(), receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); @@ -1381,7 +1428,7 @@ fn fails_receive_tlvs_authentication() { let amt_msat = 5000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -1389,13 +1436,12 @@ fn fails_receive_tlvs_authentication() { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &[], nodes[1].node.get_our_node_id(), receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); @@ -1412,7 +1458,7 @@ fn fails_receive_tlvs_authentication() { // Swap in a different nonce to force authentication to fail. let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -1420,13 +1466,13 @@ fn fails_receive_tlvs_authentication() { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([43u8; 16]); - let mut payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); - payee_tlvs.authentication.1 = Nonce([0u8; 16]); + // Use a mismatched ReceiveAuthKey to force auth failure: + let mismatched_receive_auth_key = ReceiveAuthKey([0u8; 32]); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &[], nodes[1].node.get_our_node_id(), mismatched_receive_auth_key, + payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); @@ -1629,7 +1675,7 @@ fn route_blinding_spec_test_vector() { &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } fn get_peer_storage_key(&self) -> PeerStorageKey { unreachable!() } - fn get_receive_auth_key(&self) -> ReceiveAuthKey { unreachable!() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { ReceiveAuthKey([41; 32]) } fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { unreachable!() } @@ -1671,8 +1717,9 @@ fn route_blinding_spec_test_vector() { hop_data: carol_packet_bytes, hmac: carol_hmac, }; + let carol_forward_info = carol_packet_details.forward_info.unwrap(); let carol_update_add = update_add_msg( - carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, + carol_forward_info.outgoing_amt_msat, carol_forward_info.outgoing_cltv_value, Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")), carol_onion ); @@ -1705,8 +1752,9 @@ fn route_blinding_spec_test_vector() { hop_data: dave_packet_bytes, hmac: dave_hmac, }; + let dave_forward_info = dave_packet_details.forward_info.unwrap(); let dave_update_add = update_add_msg( - dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value, + dave_forward_info.outgoing_amt_msat, dave_forward_info.outgoing_cltv_value, Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), dave_onion ); @@ -1739,8 +1787,9 @@ fn route_blinding_spec_test_vector() { hop_data: eve_packet_bytes, hmac: eve_hmac, }; + let eve_forward_info = eve_packet_details.forward_info.unwrap(); let eve_update_add = update_add_msg( - eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value, + eve_forward_info.outgoing_amt_msat, eve_forward_info.outgoing_cltv_value, Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), eve_onion ); @@ -1942,7 +1991,7 @@ fn test_trampoline_inbound_payment_decoding() { &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } fn get_peer_storage_key(&self) -> PeerStorageKey { unreachable!() } - fn get_receive_auth_key(&self) -> ReceiveAuthKey { unreachable!() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { ReceiveAuthKey([41; 32]) } fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { unreachable!() } @@ -1971,7 +2020,8 @@ fn test_trampoline_inbound_payment_decoding() { hop_data: carol_packet_bytes, hmac: carol_hmac, }; - let carol_update_add = update_add_msg(carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, None, carol_onion); + let carol_forward_info = carol_packet_details.forward_info.unwrap(); + let carol_update_add = update_add_msg(carol_forward_info.outgoing_amt_msat, carol_forward_info.outgoing_cltv_value, None, carol_onion); let carol_node_signer = TestEcdhSigner { node_secret: carol_secret }; let (carol_peeled_onion, _) = onion_payment::decode_incoming_update_add_htlc_onion( @@ -2014,7 +2064,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv); let carol_blinded_hops = if success { - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -2023,12 +2073,10 @@ fn do_test_trampoline_single_hop_receive(success: bool) { payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = nodes[2].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let carol_unblinded_tlvs = payee_tlvs.encode(); + let receive_auth_key = nodes[2].keys_manager.get_receive_auth_key(); - let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; + let path = [((carol_node_id, Some(receive_auth_key)), WithoutLength(&carol_unblinded_tlvs))]; blinded_path::utils::construct_blinded_hops( &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, ) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4365407a2d0..67dfa9f1d62 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -35,9 +35,7 @@ use bitcoin::{secp256k1, Sequence, SignedAmount}; use crate::blinded_path::message::{ AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, OffersContext, }; -use crate::blinded_path::payment::{ - AsyncBolt12OfferContext, Bolt12OfferContext, PaymentContext, UnauthenticatedReceiveTlvs, -}; +use crate::blinded_path::payment::{AsyncBolt12OfferContext, Bolt12OfferContext, PaymentContext}; use crate::blinded_path::NodeIdLookUp; use crate::chain; use crate::chain::chaininterface::{ @@ -73,9 +71,9 @@ use crate::ln::msgs::{ MessageSendEvent, }; use crate::ln::onion_payment::{ - check_incoming_htlc_cltv, create_fwd_pending_htlc_info, create_recv_pending_htlc_info, - decode_incoming_update_add_htlc_onion, invalid_payment_err_data, HopConnector, InboundHTLCErr, - NextPacketDetails, + check_incoming_htlc_cltv, create_fwd_pending_htlc_info, create_new_update_add_htlc, + create_recv_pending_htlc_info, decode_incoming_update_add_htlc_onion, invalid_payment_err_data, + ForwardInfo, HopConnector, InboundHTLCErr, NextPacketDetails, }; use crate::ln::onion_utils::{self}; use crate::ln::onion_utils::{ @@ -101,7 +99,6 @@ use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferFromHrn}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::Refund; -use crate::offers::signer; use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{ AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, @@ -563,34 +560,6 @@ impl Ord for ClaimableHTLC { } } -/// A trait defining behavior for creating and verifing the HMAC for authenticating a given data. -pub trait Verification { - /// Constructs an HMAC to include in [`OffersContext`] for the data along with the given - /// [`Nonce`]. - fn hmac_for_offer_payment( - &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Hmac; - - /// Authenticates the data using an HMAC and a [`Nonce`] taken from an [`OffersContext`]. - fn verify_for_offer_payment( - &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Result<(), ()>; -} - -impl Verification for UnauthenticatedReceiveTlvs { - fn hmac_for_offer_payment( - &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Hmac { - signer::hmac_for_payment_tlvs(self, nonce, expanded_key) - } - - fn verify_for_offer_payment( - &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Result<(), ()> { - signer::verify_payment_tlvs(self, hmac, nonce, expanded_key) - } -} - /// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify /// a payment and ensure idempotency in LDK. /// @@ -4716,7 +4685,7 @@ where #[rustfmt::skip] fn can_forward_htlc_to_outgoing_channel( - &self, chan: &mut FundedChannel, msg: &msgs::UpdateAddHTLC, next_packet: &NextPacketDetails + &self, chan: &mut FundedChannel, msg: &msgs::UpdateAddHTLC, forward_info: &ForwardInfo ) -> Result<(), LocalHTLCFailureReason> { if !chan.context.should_announce() && !self.config.read().unwrap().accept_forwards_to_priv_channels @@ -4726,7 +4695,8 @@ where // we don't allow forwards outbound over them. return Err(LocalHTLCFailureReason::PrivateChannelForward); } - if let HopConnector::ShortChannelId(outgoing_scid) = next_packet.outgoing_connector { + + if let HopConnector::ShortChannelId(outgoing_scid) = forward_info.outgoing_connector { if chan.funding.get_channel_type().supports_scid_privacy() && outgoing_scid != chan.context.outbound_scid_alias() { // `option_scid_alias` (referred to in LDK as `scid_privacy`) means // "refuse to forward unless the SCID alias was used", so we pretend @@ -4749,10 +4719,10 @@ where return Err(LocalHTLCFailureReason::ChannelNotReady); } } - if next_packet.outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { + if forward_info.outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { return Err(LocalHTLCFailureReason::AmountBelowMinimum); } - chan.htlc_satisfies_config(msg, next_packet.outgoing_amt_msat, next_packet.outgoing_cltv_value)?; + chan.htlc_satisfies_config(msg, forward_info.outgoing_amt_msat, forward_info.outgoing_cltv_value)?; Ok(()) } @@ -4784,14 +4754,20 @@ where fn can_forward_htlc( &self, msg: &msgs::UpdateAddHTLC, next_packet_details: &NextPacketDetails ) -> Result<(), LocalHTLCFailureReason> { - let outgoing_scid = match next_packet_details.outgoing_connector { + let forward_info = next_packet_details + .forward_info + .as_ref() + .ok_or(LocalHTLCFailureReason::InvalidOnionPayload)?; + + let outgoing_scid = match forward_info.outgoing_connector { HopConnector::ShortChannelId(scid) => scid, HopConnector::Trampoline(_) => { return Err(LocalHTLCFailureReason::InvalidTrampolineForward); } }; + match self.do_funded_channel_callback(outgoing_scid, |chan: &mut FundedChannel| { - self.can_forward_htlc_to_outgoing_channel(chan, msg, next_packet_details) + self.can_forward_htlc_to_outgoing_channel(chan, msg, forward_info) }) { Some(Ok(())) => {}, Some(Err(e)) => return Err(e), @@ -4808,7 +4784,7 @@ where } let cur_height = self.best_block.read().unwrap().height + 1; - check_incoming_htlc_cltv(cur_height, next_packet_details.outgoing_cltv_value, msg.cltv_expiry)?; + check_incoming_htlc_cltv(cur_height, forward_info.outgoing_cltv_value, msg.cltv_expiry)?; Ok(()) } @@ -4912,11 +4888,19 @@ where let current_height: u32 = self.best_block.read().unwrap().height; create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, - current_height) + current_height, &*self.logger) }, onion_utils::Hop::Forward { .. } | onion_utils::Hop::BlindedForward { .. } => { create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) }, + onion_utils::Hop::Dummy { .. } => { + debug_assert!(false, "Shouldn't be triggered."); + return Err(InboundHTLCErr { + msg: "Failed to decode update add htlc onion", + reason: LocalHTLCFailureReason::InvalidOnionPayload, + err_data: Vec::new(), + }) + }, onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) }, @@ -5440,12 +5424,10 @@ where fn check_refresh_async_receive_offer_cache(&self, timer_tick_occurred: bool) { let peers = self.get_peers_for_blinded_path(); let channels = self.list_usable_channels(); - let entropy = &*self.entropy_source; let router = &*self.router; let refresh_res = self.flow.check_refresh_async_receive_offer_cache( peers, channels, - entropy, router, timer_tick_occurred, ); @@ -6568,6 +6550,7 @@ where pub(crate) fn process_pending_update_add_htlcs(&self) -> bool { let mut should_persist = false; let mut decode_update_add_htlcs = new_hash_map(); + let mut dummy_update_add_htlcs = new_hash_map(); mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap()); let get_htlc_failure_type = |outgoing_scid_opt: Option, payment_hash: PaymentHash| { @@ -6631,7 +6614,39 @@ where &*self.logger, &self.secp_ctx, ) { - Ok(decoded_onion) => decoded_onion, + Ok(decoded_onion) => match decoded_onion { + ( + onion_utils::Hop::Dummy { + intro_node_blinding_point, + next_hop_hmac, + new_packet_bytes, + .. + }, + Some(NextPacketDetails { next_packet_pubkey, forward_info }), + ) => { + debug_assert!( + forward_info.is_none(), + "Dummy hops must not contain any forward info, since they are not actually forwarded." + ); + let new_update_add_htlc = create_new_update_add_htlc( + update_add_htlc.clone(), + &*self.node_signer, + &self.secp_ctx, + intro_node_blinding_point, + next_packet_pubkey, + next_hop_hmac, + new_packet_bytes, + ); + + dummy_update_add_htlcs + .entry(incoming_scid_alias) + .or_insert_with(Vec::new) + .push(new_update_add_htlc); + + continue; + }, + _ => decoded_onion, + }, Err((htlc_fail, reason)) => { let failure_type = HTLCHandlingFailureType::InvalidOnion; @@ -6641,11 +6656,12 @@ where }; let is_intro_node_blinded_forward = next_hop.is_intro_node_blinded_forward(); - let outgoing_scid_opt = - next_packet_details_opt.as_ref().and_then(|d| match d.outgoing_connector { + let outgoing_scid_opt = next_packet_details_opt.as_ref().and_then(|d| { + d.forward_info.as_ref().and_then(|f| match f.outgoing_connector { HopConnector::ShortChannelId(scid) => Some(scid), HopConnector::Trampoline(_) => None, - }); + }) + }); let shared_secret = next_hop.shared_secret().secret_bytes(); // Nodes shouldn't expect us to hold HTLCs for them if we don't advertise htlc_hold feature @@ -6787,6 +6803,11 @@ where )); } } + + // Replace the decode queue with the peeled dummy HTLCs so they can be processed in the next iteration. + let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); + mem::swap(&mut *decode_update_add_htlc_source, &mut dummy_update_add_htlcs); + should_persist } @@ -7025,6 +7046,7 @@ where false, None, current_height, + &*self.logger, ); match create_res { Ok(info) => phantom_receives.push(( @@ -12963,11 +12985,8 @@ where &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, relative_expiry_seconds: u32, ) -> Result, ()> { - let entropy = &*self.entropy_source; - self.flow.test_create_blinded_payment_paths( &self.router, - entropy, self.list_usable_channels(), amount_msats, payment_secret, @@ -14846,9 +14865,8 @@ where }, }; - let entropy = &*self.entropy_source; let (response, context) = self.flow.create_response_for_invoice_request( - &self.node_signer, &self.router, entropy, invoice_request, amount_msats, + &self.node_signer, &self.router, invoice_request, amount_msats, payment_hash, payment_secret, self.list_usable_channels() ); @@ -17912,7 +17930,7 @@ mod tests { use crate::util::config::{ChannelConfig, ChannelConfigUpdate}; use crate::util::errors::APIError; use crate::util::ser::Writeable; - use crate::util::test_utils; + use crate::util::test_utils::{self, TestLogger}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use core::sync::atomic::Ordering; @@ -18737,6 +18755,7 @@ mod tests { let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]); let node = create_network(1, &node_cfg, &node_chanmgr); let sender_intended_amt_msat = 100; + let logger = TestLogger::new(); let extra_fee_msat = 10; let hop_data = onion_utils::Hop::Receive { hop_data: msgs::InboundOnionReceivePayload { @@ -18758,7 +18777,7 @@ mod tests { if let Err(crate::ln::channelmanager::InboundHTLCErr { reason, .. }) = create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat), - current_height) + current_height, &logger) { assert_eq!(reason, LocalHTLCFailureReason::FinalIncorrectHTLCAmount); } else { panic!(); } @@ -18781,7 +18800,7 @@ mod tests { let current_height: u32 = node[0].node.best_block.read().unwrap().height; assert!(create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat), - current_height).is_ok()); + current_height, &logger).is_ok()); } #[test] @@ -18791,6 +18810,7 @@ mod tests { let node_cfg = create_node_cfgs(1, &chanmon_cfg); let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]); let node = create_network(1, &node_cfg, &node_chanmgr); + let logger = TestLogger::new(); let current_height: u32 = node[0].node.best_block.read().unwrap().height; let result = create_recv_pending_htlc_info(onion_utils::Hop::Receive { @@ -18806,7 +18826,7 @@ mod tests { custom_tlvs: Vec::new(), }, shared_secret: SharedSecret::from_bytes([0; 32]), - }, [0; 32], PaymentHash([0; 32]), 100, TEST_FINAL_CLTV + 1, None, true, None, current_height); + }, [0; 32], PaymentHash([0; 32]), 100, TEST_FINAL_CLTV + 1, None, true, None, current_height, &logger); // Should not return an error as this condition: // https://github.com/lightning/bolts/blob/4dcc377209509b13cf89a4b91fde7d478f5b46d8/04-onion-routing.md?plain=1#L334 diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index f26ef03a74f..d741940e91c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -37,7 +37,9 @@ use crate::ln::peer_handler::IgnoringMessageHandler; use crate::ln::types::ChannelId; use crate::onion_message::messenger::OnionMessenger; use crate::routing::gossip::{NetworkGraph, NetworkUpdate, P2PGossipSync}; -use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; +use crate::routing::router::{ + self, PaymentParameters, Route, RouteParameters, DEFAULT_PAYMENT_DUMMY_HOPS, +}; use crate::sign::{EntropySource, RandomBytes}; use crate::types::features::ChannelTypeFeatures; use crate::types::features::InitFeatures; @@ -3276,6 +3278,7 @@ fn fail_payment_along_path<'a, 'b, 'c>(expected_path: &[&Node<'a, 'b, 'c>]) { pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> { pub origin_node: &'a Node<'b, 'c, 'd>, pub expected_path: &'a [&'a Node<'b, 'c, 'd>], + pub dummy_hop_override: Option, pub recv_value: u64, pub payment_hash: PaymentHash, pub payment_secret: Option, @@ -3297,6 +3300,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> { Self { origin_node, expected_path, + dummy_hop_override: None, recv_value, payment_hash, payment_secret: None, @@ -3344,12 +3348,17 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> { self.expected_failure = Some(failure); self } + pub fn with_dummy_override(mut self, dummy_override: usize) -> Self { + self.dummy_hop_override = Some(dummy_override); + self + } } pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option { let PassAlongPathArgs { origin_node, expected_path, + dummy_hop_override, recv_value, payment_hash: our_payment_hash, payment_secret: our_payment_secret, @@ -3383,6 +3392,14 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option node.node.process_pending_htlc_forwards(); } + if is_last_hop { + let dummy_count = dummy_hop_override.unwrap_or(DEFAULT_PAYMENT_DUMMY_HOPS); + for _ in 0..dummy_count { + println!("This ran"); + node.node.process_pending_htlc_forwards(); + } + } + if is_last_hop && clear_recipient_events { let events_2 = node.node.get_and_clear_pending_events(); if payment_claimable_expected { @@ -3494,9 +3511,33 @@ pub fn pass_along_path<'a, 'b, 'c>( origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option, ev: MessageSendEvent, payment_claimable_expected: bool, expected_preimage: Option, +) -> Option { + pass_along_path_with_dummy( + origin_node, + expected_path, + None, // no dummy hops + recv_value, + our_payment_hash, + our_payment_secret, + ev, + payment_claimable_expected, + expected_preimage, + ) +} + +pub fn pass_along_path_with_dummy<'a, 'b, 'c>( + origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], + dummy_count: Option, recv_value: u64, our_payment_hash: PaymentHash, + our_payment_secret: Option, ev: MessageSendEvent, + payment_claimable_expected: bool, expected_preimage: Option, ) -> Option { let mut args = PassAlongPathArgs::new(origin_node, expected_path, recv_value, our_payment_hash, ev); + + if let Some(count) = dummy_count { + args = args.with_dummy_override(count); + } + if !payment_claimable_expected { args = args.without_claimable_event(); } diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 8425e1928ad..f67ad442c29 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -12,7 +12,7 @@ use crate::blinded_path::payment::{ BlindedPayInfo, BlindedPaymentPath, Bolt12RefundContext, PaymentConstraints, PaymentContext, - UnauthenticatedReceiveTlvs, + ReceiveTlvs, }; use crate::blinded_path::BlindedHop; use crate::events::Event; @@ -24,7 +24,6 @@ use crate::ln::msgs::{BaseMessageHandler, OnionMessageHandler}; use crate::ln::onion_utils; use crate::ln::onion_utils::MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY; use crate::ln::outbound_payment::{RecipientOnionFields, Retry, RetryableSendFailure}; -use crate::offers::nonce::Nonce; use crate::prelude::*; use crate::routing::router::{ PaymentParameters, RouteParameters, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, @@ -213,7 +212,7 @@ fn one_hop_blinded_path_with_custom_tlv() { let amt_msat = 100_000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -221,13 +220,12 @@ fn one_hop_blinded_path_with_custom_tlv() { }, payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = chanmon_cfgs[2].keys_manager.get_expanded_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let receive_auth_key = chanmon_cfgs[2].keys_manager.get_receive_auth_key(); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( &[], nodes[2].node.get_our_node_id(), + receive_auth_key, payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index fd806a27951..de2dd4c76b3 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -32,11 +32,8 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::{secp256k1, Transaction, Witness}; use crate::blinded_path::message::BlindedMessagePath; -use crate::blinded_path::payment::{ - BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs, UnauthenticatedReceiveTlvs, -}; +use crate::blinded_path::payment::{BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs}; use crate::blinded_path::payment::{BlindedTrampolineTlvs, TrampolineForwardTlvs}; -use crate::ln::channelmanager::Verification; use crate::ln::onion_utils; use crate::ln::types::ChannelId; use crate::offers::invoice_request::InvoiceRequest; @@ -59,7 +56,7 @@ use core::str::FromStr; #[cfg(feature = "std")] use std::net::SocketAddr; -use crate::crypto::streams::ChaChaPolyReadAdapter; +use crate::crypto::streams::ChaChaDualPolyReadAdapter; use crate::util::base32; use crate::util::logger; use crate::util::ser::{ @@ -2342,6 +2339,7 @@ mod fuzzy_internal_msgs { pub keysend_preimage: Option, pub invoice_request: Option, pub custom_tlvs: Vec<(u64, Vec)>, + pub payment_tlvs_authenticated: bool, } pub enum InboundOnionPayload { @@ -2350,6 +2348,7 @@ mod fuzzy_internal_msgs { Receive(InboundOnionReceivePayload), BlindedForward(InboundOnionBlindedForwardPayload), BlindedReceive(InboundOnionBlindedReceivePayload), + Dummy { intro_node_blinding_point: Option, payment_tlvs_authenticated: bool }, } pub struct InboundTrampolineForwardPayload { @@ -3659,10 +3658,11 @@ where .ecdh(Recipient::Node, &blinding_point, None) .map_err(|_| DecodeError::InvalidValue)?; let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes()); + let receive_auth_key = node_signer.get_receive_auth_key(); let mut s = Cursor::new(&enc_tlvs); let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho)? { - ChaChaPolyReadAdapter { + match ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0))? { + ChaChaDualPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, @@ -3671,11 +3671,13 @@ where features, next_blinding_override, }), + used_aad, } => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || keysend_preimage.is_some() || invoice_request.is_some() + || used_aad { return Err(DecodeError::InvalidValue); } @@ -3688,18 +3690,29 @@ where next_blinding_override, })) }, - ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => { - let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs; - let expanded_key = node_signer.get_expanded_key(); - if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() { + ChaChaDualPolyReadAdapter { + readable: BlindedPaymentTlvs::Dummy(_dummy_tlv), + used_aad, + } => { + if amt.is_some() + || cltv_value.is_some() || total_msat.is_some() + || keysend_preimage.is_some() + || invoice_request.is_some() + { return Err(DecodeError::InvalidValue); } + Ok(Self::Dummy { + intro_node_blinding_point, + payment_tlvs_authenticated: used_aad, + }) + }, + ChaChaDualPolyReadAdapter { + readable: BlindedPaymentTlvs::Receive(receive_tlvs), + used_aad, + } => { + let ReceiveTlvs { payment_secret, payment_constraints, payment_context } = + receive_tlvs; - let UnauthenticatedReceiveTlvs { - payment_secret, - payment_constraints, - payment_context, - } = tlvs; if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue); } @@ -3714,6 +3727,7 @@ where keysend_preimage, invoice_request, custom_tlvs, + payment_tlvs_authenticated: used_aad, })) }, } @@ -3758,6 +3772,7 @@ where { fn read(r: &mut R, args: (Option, NS)) -> Result { let (update_add_blinding_point, node_signer) = args; + let receive_auth_key = node_signer.get_receive_auth_key(); let mut amt = None; let mut cltv_value = None; @@ -3811,8 +3826,8 @@ where let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes()); let mut s = Cursor::new(&enc_tlvs); let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho)? { - ChaChaPolyReadAdapter { + match ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0))? { + ChaChaDualPolyReadAdapter { readable: BlindedTrampolineTlvs::Forward(TrampolineForwardTlvs { next_trampoline, @@ -3821,11 +3836,13 @@ where features, next_blinding_override, }), + used_aad, } => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || keysend_preimage.is_some() || invoice_request.is_some() + || used_aad { return Err(DecodeError::InvalidValue); } @@ -3838,20 +3855,13 @@ where next_blinding_override, })) }, - ChaChaPolyReadAdapter { + ChaChaDualPolyReadAdapter { readable: BlindedTrampolineTlvs::Receive(receive_tlvs), + used_aad, } => { - let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs; - let expanded_key = node_signer.get_expanded_key(); - if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() { - return Err(DecodeError::InvalidValue); - } + let ReceiveTlvs { payment_secret, payment_constraints, payment_context } = + receive_tlvs; - let UnauthenticatedReceiveTlvs { - payment_secret, - payment_constraints, - payment_context, - } = tlvs; if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue); } @@ -3866,6 +3876,7 @@ where keysend_preimage, invoice_request, custom_tlvs, + payment_tlvs_authenticated: used_aad, })) }, } diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 0934c6c812b..845d433bca6 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -15,7 +15,7 @@ use crate::ln::channelmanager::{ BlindedFailure, BlindedForward, HTLCFailureMsg, PendingHTLCInfo, PendingHTLCRouting, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, }; -use crate::ln::msgs; +use crate::ln::msgs::{self, OnionPacket, UpdateAddHTLC}; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, LocalHTLCFailureReason, ONION_DATA_LEN}; use crate::sign::{NodeSigner, Recipient}; @@ -123,6 +123,14 @@ pub(super) fn create_fwd_pending_htlc_info( (RoutingInfo::Direct { short_channel_id, new_packet_bytes, next_hop_hmac }, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point, next_blinding_override) }, + onion_utils::Hop::Dummy { .. } => { + debug_assert!(false, "This case shall not be triggered"); + return Err(InboundHTLCErr { + msg: "Dummy Hop OnionHopData provided for us as an intermediary node", + reason: LocalHTLCFailureReason::InvalidOnionPayload, + err_data: Vec::new(), + }) + }, onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } => return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", @@ -245,11 +253,26 @@ pub(super) fn create_fwd_pending_htlc_info( } #[rustfmt::skip] -pub(super) fn create_recv_pending_htlc_info( +pub(super) fn create_recv_pending_htlc_info( hop_data: onion_utils::Hop, shared_secret: [u8; 32], payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool, - counterparty_skimmed_fee_msat: Option, current_height: u32 -) -> Result { + counterparty_skimmed_fee_msat: Option, current_height: u32, logger: L +) -> Result +where + L::Target: Logger, +{ + let check_authentication = |payment_tlvs_authenticated: bool| -> Result<(), InboundHTLCErr> { + if !payment_tlvs_authenticated { + log_warn!(logger, "Received an unauthenticated receive payment message"); + return Err(InboundHTLCErr { + reason: LocalHTLCFailureReason::UnauthenticatedPayload, + err_data: Vec::new(), + msg: "Received unauthenticated receive payment htlc", + }); + } + Ok(()) + }; + let ( payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry, payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret, @@ -264,8 +287,10 @@ pub(super) fn create_recv_pending_htlc_info( onion_utils::Hop::BlindedReceive { hop_data: msgs::InboundOnionBlindedReceivePayload { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, - custom_tlvs, invoice_request + custom_tlvs, invoice_request, payment_tlvs_authenticated }, .. } => { + check_authentication(payment_tlvs_authenticated)?; + check_blinded_payment_constraints( sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints ) @@ -293,9 +318,11 @@ pub(super) fn create_recv_pending_htlc_info( trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, - custom_tlvs, invoice_request + custom_tlvs, invoice_request, payment_tlvs_authenticated }, .. } => { + check_authentication(payment_tlvs_authenticated)?; + check_blinded_payment_constraints( sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints, ) @@ -325,6 +352,14 @@ pub(super) fn create_recv_pending_htlc_info( msg: "Got blinded non final data with an HMAC of 0", }) }, + onion_utils::Hop::Dummy { .. } => { + debug_assert!(false, "This case shall not be triggered."); + return Err(InboundHTLCErr { + reason: LocalHTLCFailureReason::InvalidOnionBlinding, + err_data: vec![0; 32], + msg: "Got blinded non final data with an HMAC of 0", + }) + } onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { return Err(InboundHTLCErr { reason: LocalHTLCFailureReason::InvalidOnionPayload, @@ -438,7 +473,7 @@ where L::Target: Logger, { let (hop, next_packet_details_opt) = - decode_incoming_update_add_htlc_onion(msg, node_signer, logger, secp_ctx + decode_incoming_update_add_htlc_onion(msg, node_signer, logger.deref(), secp_ctx ).map_err(|(msg, failure_reason)| { let (reason, err_data) = match msg { HTLCFailureMsg::Malformed(_) => (failure_reason, Vec::new()), @@ -450,16 +485,19 @@ where Ok(match hop { onion_utils::Hop::Forward { shared_secret, .. } | onion_utils::Hop::BlindedForward { shared_secret, .. } => { - let NextPacketDetails { - next_packet_pubkey, outgoing_amt_msat: _, outgoing_connector: _, outgoing_cltv_value - } = match next_packet_details_opt { - Some(next_packet_details) => next_packet_details, + let (next_packet_pubkey, outgoing_cltv_value) = match next_packet_details_opt { + Some(NextPacketDetails { + next_packet_pubkey, + forward_info: Some(ForwardInfo { outgoing_cltv_value, .. }), + }) => (next_packet_pubkey, outgoing_cltv_value), // Forward should always include the next hop details - None => return Err(InboundHTLCErr { - msg: "Failed to decode update add htlc onion", - reason: LocalHTLCFailureReason::InvalidOnionPayload, - err_data: Vec::new(), - }), + _ => { + return Err(InboundHTLCErr { + msg: "Failed to decode update add htlc onion", + reason: LocalHTLCFailureReason::InvalidOnionPayload, + err_data: Vec::new(), + }); + } }; if let Err(reason) = check_incoming_htlc_cltv( @@ -480,7 +518,8 @@ where let shared_secret = hop.shared_secret().secret_bytes(); create_recv_pending_htlc_info( hop, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, - None, allow_skimmed_fees, msg.skimmed_fee_msat, cur_height, + None, allow_skimmed_fees, msg.skimmed_fee_msat, + cur_height, logger.deref() )? } }) @@ -496,6 +535,10 @@ pub(super) enum HopConnector { pub(super) struct NextPacketDetails { pub(super) next_packet_pubkey: Result, + pub(super) forward_info: Option, +} + +pub(super) struct ForwardInfo { pub(super) outgoing_connector: HopConnector, pub(super) outgoing_amt_msat: u64, pub(super) outgoing_cltv_value: u32, @@ -572,8 +615,12 @@ where let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes()); Some(NextPacketDetails { - next_packet_pubkey, outgoing_connector: HopConnector::ShortChannelId(short_channel_id), - outgoing_amt_msat: amt_to_forward, outgoing_cltv_value + next_packet_pubkey, + forward_info: Some(ForwardInfo { + outgoing_connector: HopConnector::ShortChannelId(short_channel_id), + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + }), }) } onion_utils::Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, ref payment_relay, ref payment_constraints, ref features, .. }, shared_secret, .. } => { @@ -589,8 +636,12 @@ where let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes()); Some(NextPacketDetails { - next_packet_pubkey, outgoing_connector: HopConnector::ShortChannelId(short_channel_id), outgoing_amt_msat: amt_to_forward, - outgoing_cltv_value + next_packet_pubkey, + forward_info: Some(ForwardInfo { + outgoing_connector: HopConnector::ShortChannelId(short_channel_id), + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + }), }) } onion_utils::Hop::TrampolineForward { next_trampoline_hop_data: msgs::InboundTrampolineForwardPayload { amt_to_forward, outgoing_cltv_value, next_trampoline }, trampoline_shared_secret, incoming_trampoline_public_key, .. } => { @@ -598,10 +649,18 @@ where incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes()); Some(NextPacketDetails { next_packet_pubkey: next_trampoline_packet_pubkey, - outgoing_connector: HopConnector::Trampoline(next_trampoline), - outgoing_amt_msat: amt_to_forward, - outgoing_cltv_value, + forward_info: Some(ForwardInfo { + outgoing_connector: HopConnector::Trampoline(next_trampoline), + outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value, + }), }) + }, + onion_utils::Hop::Dummy { shared_secret, .. } => { + let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, + msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes()); + + Some(NextPacketDetails { next_packet_pubkey, forward_info: None }) } _ => None }; @@ -609,6 +668,32 @@ where Ok((next_hop, next_packet_details)) } +pub(super) fn create_new_update_add_htlc( + msg: msgs::UpdateAddHTLC, node_signer: NS, secp_ctx: &Secp256k1, + intro_node_blinding_point: Option, + new_packet_pubkey: Result, next_hop_hmac: [u8; 32], + new_packet_bytes: [u8; 1300], +) -> UpdateAddHTLC +where + NS::Target: NodeSigner, +{ + let new_packet = OnionPacket { + version: 0, + public_key: new_packet_pubkey, + hop_data: new_packet_bytes, + hmac: next_hop_hmac, + }; + + let next_blinding_point = + intro_node_blinding_point.or(msg.blinding_point).and_then(|blinding_point| { + let encrypted_tlvs_ss = + node_signer.ecdh(Recipient::Node, &blinding_point, None).unwrap().secret_bytes(); + onion_utils::next_hop_pubkey(&secp_ctx, blinding_point, &encrypted_tlvs_ss).ok() + }); + + UpdateAddHTLC { onion_routing_packet: new_packet, blinding_point: next_blinding_point, ..msg } +} + pub(super) fn check_incoming_htlc_cltv( cur_height: u32, outgoing_cltv_value: u32, cltv_expiry: u32, ) -> Result<(), LocalHTLCFailureReason> { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 6bba2b59e10..b3b265aa993 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1680,6 +1680,13 @@ pub enum LocalHTLCFailureReason { PeerOffline, /// The HTLC was failed because the channel balance was overdrawn. ChannelBalanceOverdrawn, + /// The payload we received was not authenticated by our node. + /// + /// This can occur if we constructed an invalid [`BlindedPaymentPath`] + /// for our counterparty to send a payment over. + /// + /// [`BlindedPaymentPath`]: crate::blinded_path::payment::BlindedPaymentPath + UnauthenticatedPayload, } impl LocalHTLCFailureReason { @@ -1720,7 +1727,7 @@ impl LocalHTLCFailureReason { Self::CLTVExpiryTooFar => 21, Self::InvalidOnionPayload | Self::InvalidTrampolinePayload => PERM | 22, Self::MPPTimeout => 23, - Self::InvalidOnionBlinding => BADONION | PERM | 24, + Self::InvalidOnionBlinding | Self::UnauthenticatedPayload => BADONION | PERM | 24, Self::UnknownFailureCode { code } => *code, } } @@ -1880,7 +1887,8 @@ ser_failure_reasons!( (39, HTLCMinimum), (40, HTLCMaximum), (41, PeerOffline), - (42, ChannelBalanceOverdrawn) + (42, ChannelBalanceOverdrawn), + (43, UnauthenticatedPayload) ); impl From<&HTLCFailReason> for HTLCHandlingFailureReason { @@ -2041,7 +2049,8 @@ impl HTLCFailReason { LocalHTLCFailureReason::InvalidOnionPayload | LocalHTLCFailureReason::InvalidTrampolinePayload => debug_assert!(data.len() <= 11), LocalHTLCFailureReason::MPPTimeout => debug_assert!(data.is_empty()), - LocalHTLCFailureReason::InvalidOnionBlinding => debug_assert_eq!(data.len(), 32), + LocalHTLCFailureReason::InvalidOnionBlinding + | LocalHTLCFailureReason::UnauthenticatedPayload => debug_assert_eq!(data.len(), 32), LocalHTLCFailureReason::UnknownFailureCode { code } => { // We set some bogus BADONION failure codes in tests, so allow unknown BADONION. if code & BADONION == 0 { @@ -2219,6 +2228,17 @@ pub(crate) enum Hop { /// Bytes of the onion packet we're forwarding. new_packet_bytes: [u8; ONION_DATA_LEN], }, + /// This onion payload is dummy, and needs to be peeled by us. + Dummy { + /// Onion payload data used in interpreting the dummy hop + intro_node_blinding_point: Option, + /// Shared secret that was used to decrypt next_hop_data. + shared_secret: SharedSecret, + /// HMAC of the next hop's onion packet. + next_hop_hmac: [u8; 32], + /// Bytes of the onion packet we're forwarding. + new_packet_bytes: [u8; ONION_DATA_LEN], + }, /// This onion payload was for us, not for forwarding to a next-hop. Contains information for /// verifying the incoming payment. Receive { @@ -2275,6 +2295,7 @@ impl Hop { match self { Hop::Forward { shared_secret, .. } => shared_secret, Hop::BlindedForward { shared_secret, .. } => shared_secret, + Hop::Dummy { shared_secret, .. } => shared_secret, Hop::TrampolineForward { outer_shared_secret, .. } => outer_shared_secret, Hop::TrampolineBlindedForward { outer_shared_secret, .. } => outer_shared_secret, Hop::Receive { shared_secret, .. } => shared_secret, @@ -2318,6 +2339,18 @@ where let shared_secret = node_signer.ecdh(recipient, hop_pubkey, blinded_node_id_tweak.as_ref()).unwrap(); + let check_authentication = |payment_tlvs_authenticated: bool| -> Result<(), OnionDecodeErr> { + if !payment_tlvs_authenticated { + return Err(OnionDecodeErr::Relay { + err_msg: "Final Node OnionHopData provided for us as an intermediary node", + reason: LocalHTLCFailureReason::InvalidOnionPayload, + shared_secret, + trampoline_shared_secret: None, + }); + } + Ok(()) + }; + let decoded_hop: Result<(msgs::InboundOnionPayload, Option<_>), _> = decode_next_hop( shared_secret.secret_bytes(), hop_data, @@ -2342,6 +2375,18 @@ where new_packet_bytes, }) }, + msgs::InboundOnionPayload::Dummy { + intro_node_blinding_point, + payment_tlvs_authenticated, + } => { + check_authentication(payment_tlvs_authenticated)?; + Ok(Hop::Dummy { + intro_node_blinding_point, + shared_secret, + next_hop_hmac, + new_packet_bytes, + }) + }, _ => { if blinding_point.is_some() { return Err(OnionDecodeErr::Malformed { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 615b2991f17..24cd25aebee 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -23,7 +23,7 @@ use crate::blinded_path::message::{ }; use crate::blinded_path::payment::{ AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, - PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs, + PaymentConstraints, PaymentContext, ReceiveTlvs, }; use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; @@ -319,18 +319,16 @@ where /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. - fn create_blinded_payment_paths( - &self, router: &R, entropy_source: ES, usable_channels: Vec, - amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, + fn create_blinded_payment_paths( + &self, router: &R, usable_channels: Vec, amount_msats: Option, + payment_secret: PaymentSecret, payment_context: PaymentContext, relative_expiry_seconds: u32, ) -> Result, ()> where - ES::Target: EntropySource, R::Target: Router, { - let expanded_key = &self.inbound_payment_key; - let entropy = &*entropy_source; let secp_ctx = &self.secp_ctx; + let receive_auth_key = self.receive_auth_key; let payee_node_id = self.get_our_node_id(); @@ -341,16 +339,15 @@ where .saturating_add(LATENCY_GRACE_PERIOD_BLOCKS) .saturating_add(self.best_block.read().unwrap().height); - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry, htlc_minimum_msat: 1 }, payment_context, }; - let nonce = Nonce::from_entropy_source(entropy); - let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key); router.create_blinded_payment_paths( payee_node_id, + receive_auth_key, usable_channels, payee_tlvs, amount_msats, @@ -361,18 +358,16 @@ where #[cfg(test)] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. - pub(crate) fn test_create_blinded_payment_paths( - &self, router: &R, entropy_source: ES, usable_channels: Vec, - amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, + pub(crate) fn test_create_blinded_payment_paths( + &self, router: &R, usable_channels: Vec, amount_msats: Option, + payment_secret: PaymentSecret, payment_context: PaymentContext, relative_expiry_seconds: u32, ) -> Result, ()> where - ES::Target: EntropySource, R::Target: Router, { self.create_blinded_payment_paths( router, - entropy_source, usable_channels, amount_msats, payment_secret, @@ -814,17 +809,15 @@ where /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were /// created via [`Self::create_async_receive_offer_builder`]. - pub fn create_static_invoice_builder<'a, ES: Deref, R: Deref>( - &self, router: &R, entropy_source: ES, offer: &'a Offer, offer_nonce: Nonce, - payment_secret: PaymentSecret, relative_expiry_secs: u32, - usable_channels: Vec, peers: Vec, + pub fn create_static_invoice_builder<'a, R: Deref>( + &self, router: &R, offer: &'a Offer, offer_nonce: Nonce, payment_secret: PaymentSecret, + relative_expiry_secs: u32, usable_channels: Vec, + peers: Vec, ) -> Result, Bolt12SemanticError> where - ES::Target: EntropySource, R::Target: Router, { let expanded_key = &self.inbound_payment_key; - let entropy = &*entropy_source; let secp_ctx = &self.secp_ctx; let payment_context = @@ -840,7 +833,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, amount_msat, payment_secret, @@ -908,7 +900,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, Some(amount_msats), payment_secret, @@ -947,17 +938,15 @@ where /// An [`OffersMessage::InvoiceError`] will be generated if: /// - We fail to generate valid payment paths to include in the [`Bolt12Invoice`]. /// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`]. - pub fn create_response_for_invoice_request( - &self, signer: &NS, router: &R, entropy_source: ES, - invoice_request: VerifiedInvoiceRequest, amount_msats: u64, payment_hash: PaymentHash, - payment_secret: PaymentSecret, usable_channels: Vec, + pub fn create_response_for_invoice_request( + &self, signer: &NS, router: &R, invoice_request: VerifiedInvoiceRequest, amount_msats: u64, + payment_hash: PaymentHash, payment_secret: PaymentSecret, + usable_channels: Vec, ) -> (OffersMessage, Option) where - ES::Target: EntropySource, NS::Target: NodeSigner, R::Target: Router, { - let entropy = &*entropy_source; let secp_ctx = &self.secp_ctx; let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; @@ -969,7 +958,6 @@ where let payment_paths = match self.create_blinded_payment_paths( router, - entropy, usable_channels, Some(amount_msats), payment_secret, @@ -1332,12 +1320,11 @@ where /// the cache can self-regulate the number of messages sent out. /// /// Errors if we failed to create blinded reply paths when sending an [`OfferPathsRequest`] message. - pub fn check_refresh_async_receive_offer_cache( - &self, peers: Vec, usable_channels: Vec, entropy: ES, - router: R, timer_tick_occurred: bool, + pub fn check_refresh_async_receive_offer_cache( + &self, peers: Vec, usable_channels: Vec, router: R, + timer_tick_occurred: bool, ) -> Result<(), ()> where - ES::Target: EntropySource, R::Target: Router, { // Terminate early if this node does not intend to receive async payments. @@ -1351,7 +1338,7 @@ where self.check_refresh_async_offers(peers.clone(), timer_tick_occurred)?; if timer_tick_occurred { - self.check_refresh_static_invoices(peers, usable_channels, entropy, router); + self.check_refresh_static_invoices(peers, usable_channels, router); } Ok(()) @@ -1408,11 +1395,9 @@ where /// Enqueue onion messages that will used to request invoice refresh from the static invoice /// server, based on the offers provided by the cache. - fn check_refresh_static_invoices( - &self, peers: Vec, usable_channels: Vec, entropy: ES, - router: R, + fn check_refresh_static_invoices( + &self, peers: Vec, usable_channels: Vec, router: R, ) where - ES::Target: EntropySource, R::Target: Router, { let mut serve_static_invoice_msgs = Vec::new(); @@ -1427,7 +1412,6 @@ where offer_nonce, peers.clone(), usable_channels.clone(), - &*entropy, &*router, ) { Ok((invoice, path)) => (invoice, path), @@ -1593,7 +1577,6 @@ where offer_nonce, peers, usable_channels, - &*entropy, router, ) { Ok(res) => res, @@ -1628,12 +1611,11 @@ where /// Creates a [`StaticInvoice`] and a blinded path for the server to forward invoice requests from /// payers to our node. - fn create_static_invoice_for_server( + fn create_static_invoice_for_server( &self, offer: &Offer, offer_nonce: Nonce, peers: Vec, - usable_channels: Vec, entropy: ES, router: R, + usable_channels: Vec, router: R, ) -> Result<(StaticInvoice, BlindedMessagePath), ()> where - ES::Target: EntropySource, R::Target: Router, { let expanded_key = &self.inbound_payment_key; @@ -1660,7 +1642,6 @@ where let invoice = self .create_static_invoice_builder( &router, - &*entropy, &offer, offer_nonce, payment_secret, diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 645949ff866..e51a120b6d7 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -9,7 +9,6 @@ //! Utilities for signing offer messages and verifying metadata. -use crate::blinded_path::payment::UnauthenticatedReceiveTlvs; use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::offers::merkle::TlvRecord; @@ -41,16 +40,14 @@ const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; // The following `HMAC_INPUT` constants were previously used for authenticating fields in // `OffersContext`, but were removed in LDK v0.2 with the introduction of `ReceiveAuthKey`-based // authentication. -// Their corresponding values (`[5; 16]` and `[7; 16]`) are now reserved and must not +// Their corresponding values (`[5; 16]`, `[7; 16]` and `[8; 16]`) are now reserved and must not // be reused to ensure type confusion attacks are impossible. // // Reserved HMAC_INPUT values — do not reuse: // // const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16]; // const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; - -// HMAC input for `ReceiveTlvs`. The HMAC is used in `blinded_path::payment::PaymentContext`. -const PAYMENT_TLVS_HMAC_INPUT: &[u8; 16] = &[8; 16]; +// const PAYMENT_TLVS_HMAC_INPUT: &[u8; 16] = &[8; 16]; /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. @@ -449,27 +446,3 @@ fn hmac_for_message<'a>( Ok(hmac) } - -pub(crate) fn hmac_for_payment_tlvs( - receive_tlvs: &UnauthenticatedReceiveTlvs, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Hmac { - const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment TLVs"; - let mut hmac = expanded_key.hmac_for_offer(); - hmac.input(IV_BYTES); - hmac.input(&nonce.0); - hmac.input(PAYMENT_TLVS_HMAC_INPUT); - receive_tlvs.write(&mut hmac).unwrap(); - - Hmac::from_engine(hmac) -} - -pub(crate) fn verify_payment_tlvs( - receive_tlvs: &UnauthenticatedReceiveTlvs, hmac: Hmac, nonce: Nonce, - expanded_key: &ExpandedKey, -) -> Result<(), ()> { - if hmac_for_payment_tlvs(receive_tlvs, nonce, expanded_key) == hmac { - Ok(()) - } else { - Err(()) - } -} diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index e3443b5e45a..df8ecac3d0a 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -28,7 +28,7 @@ use crate::routing::gossip::{ DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId, ReadOnlyNetworkGraph, }; use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; -use crate::sign::EntropySource; +use crate::sign::{EntropySource, ReceiveAuthKey}; use crate::sync::Mutex; use crate::types::features::{ BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures, @@ -74,6 +74,9 @@ pub struct DefaultRouter< score_params: SP, } +/// Default number of Dummy Hops +pub const DEFAULT_PAYMENT_DUMMY_HOPS: usize = 3; + impl< G: Deref>, L: Deref, @@ -129,8 +132,8 @@ where fn create_blinded_payment_paths< T: secp256k1::Signing + secp256k1::Verification > ( - &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: Option, secp_ctx: &Secp256k1 + &self, recipient: PublicKey, receive_auth_key: ReceiveAuthKey, first_hops: Vec, + tlvs: ReceiveTlvs, amount_msats: Option, secp_ctx: &Secp256k1 ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PAYMENT_PATHS: usize = 3; @@ -180,7 +183,9 @@ where let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32; let payment_constraints = PaymentConstraints { - max_cltv_expiry: tlvs.tlvs().payment_constraints.max_cltv_expiry + cltv_expiry_delta, + max_cltv_expiry: tlvs.payment_constraints + .max_cltv_expiry + .saturating_add(cltv_expiry_delta), htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), }; Some(PaymentForwardNode { @@ -196,9 +201,9 @@ where }) }) .map(|forward_node| { - BlindedPaymentPath::new( - &[forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, - &*self.entropy_source, secp_ctx + BlindedPaymentPath::new_with_dummy_hops( + &[forward_node], recipient, DEFAULT_PAYMENT_DUMMY_HOPS, receive_auth_key, tlvs.clone(), u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ) }) .take(MAX_PAYMENT_PATHS) @@ -208,9 +213,9 @@ where Ok(paths) if !paths.is_empty() => Ok(paths), _ => { if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) { - BlindedPaymentPath::new( - &[], recipient, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, - secp_ctx + BlindedPaymentPath::new_with_dummy_hops( + &[], recipient, DEFAULT_PAYMENT_DUMMY_HOPS, receive_auth_key, tlvs, u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ).map(|path| vec![path]) } else { Err(()) @@ -243,8 +248,9 @@ impl Router for FixedRouter { } fn create_blinded_payment_paths( - &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: Option, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _receive_auth_key: ReceiveAuthKey, + _first_hops: Vec, _tlvs: ReceiveTlvs, _amount_msats: Option, + _secp_ctx: &Secp256k1, ) -> Result, ()> { // Should be unreachable as this router is only intended to provide a one-time payment route. debug_assert!(false); @@ -281,10 +287,11 @@ pub trait Router { /// Creates [`BlindedPaymentPath`]s for payment to the `recipient` node. The channels in `first_hops` /// are assumed to be with the `recipient`'s peers. The payment secret and any constraints are - /// given in `tlvs`. + /// given in `tlvs`. The `receive_auth_key` is required to authenticate the blinded payment paths. fn create_blinded_payment_paths( - &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: Option, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, receive_auth_key: ReceiveAuthKey, + first_hops: Vec, tlvs: ReceiveTlvs, amount_msats: Option, + secp_ctx: &Secp256k1, ) -> Result, ()>; } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index aacc38e366a..d8ce690a452 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -288,13 +288,15 @@ impl<'a> Router for TestRouter<'a> { } fn create_blinded_payment_paths( - &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: Option, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, receive_auth_key: ReceiveAuthKey, + first_hops: Vec, tlvs: ReceiveTlvs, amount_msats: Option, + secp_ctx: &Secp256k1, ) -> Result, ()> { let mut expected_paths = self.next_blinded_payment_paths.lock().unwrap(); if expected_paths.is_empty() { self.router.create_blinded_payment_paths( recipient, + receive_auth_key, first_hops, tlvs, amount_msats,