From afbc5e3039125591e064aac6725f1636475b7fa6 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 22 Sep 2025 15:54:01 +0530 Subject: [PATCH 1/3] Introduce ReceiveAuthKey-based verification in Blinded Payment Paths Extends the work started in [PR#3917](https://github.com/lightningdevkit/rust-lightning/pull/3917) by adding ReceiveAuthKey-based verification for Blinded Payment Paths. This reduces space previously taken by individual ReceiveTlvs and aligns the verification logic with that used for Blinded Message Paths. --- fuzz/src/chanmon_consistency.rs | 5 +-- fuzz/src/full_stack.rs | 6 ++-- fuzz/src/invoice_request_deser.rs | 4 ++- fuzz/src/refund_deser.rs | 4 ++- lightning/src/blinded_path/payment.rs | 23 +++++++------ lightning/src/ln/blinded_payment_tests.rs | 31 +++++++++++------ lightning/src/ln/channelmanager.rs | 11 +++--- .../src/ln/max_payment_path_len_tests.rs | 2 ++ lightning/src/ln/msgs.rs | 27 +++++++++++---- lightning/src/ln/onion_payment.rs | 34 +++++++++++++++---- lightning/src/ln/onion_utils.rs | 18 ++++++++-- lightning/src/offers/flow.rs | 2 ++ lightning/src/routing/router.rs | 22 ++++++------ lightning/src/util/test_utils.rs | 6 ++-- 14 files changed, 135 insertions(+), 60 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 9f03de47d23..a7b3e57724d 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 97a74871ea4..f92cb13ef0c 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..93618d1c9dc 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -21,7 +21,7 @@ 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; @@ -85,6 +85,7 @@ fn build_response( 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) { @@ -136,6 +137,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..2dea67ca087 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -20,7 +20,7 @@ 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; @@ -71,6 +71,7 @@ fn build_response( ) -> Result { let expanded_key = ExpandedKey::new([42; 32]); let entropy_source = Randomness {}; + let receive_auth_key = ReceiveAuthKey([41; 32]); let nonce = Nonce::from_entropy_source(&entropy_source); let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); let payee_tlvs = UnauthenticatedReceiveTlvs { @@ -103,6 +104,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 37d7a1dba7d..abe128957b1 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -16,7 +16,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; 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; @@ -28,7 +28,7 @@ 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 +93,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 +105,7 @@ impl BlindedPaymentPath { Self::new( &[], payee_node_id, + receive_auth_key, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, @@ -121,8 +122,8 @@ 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, @@ -150,6 +151,7 @@ impl BlindedPaymentPath { payee_node_id, payee_tlvs, &blinding_secret, + receive_auth_key, ), }, payinfo: blinded_payinfo, @@ -226,12 +228,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(()), } } @@ -660,12 +663,12 @@ 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, + payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey, ) -> Vec { let pks = intermediate_nodes .iter() .map(|node| (node.node_id, None)) - .chain(core::iter::once((payee_node_id, None))); + .chain(core::iter::once((payee_node_id, Some(local_node_receive_key)))); let tlvs = intermediate_nodes .iter() .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 8959e347bf7..85be2799702 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -86,12 +86,13 @@ pub fn blinded_payment_path( let nonce = Nonce([42u8; 16]); let expanded_key = keys_manager.get_expanded_key(); + let receive_auth_key = keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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() } @@ -171,11 +172,13 @@ fn do_one_hop_blinded_path(success: bool) { }; let nonce = Nonce([42u8; 16]); let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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(); @@ -225,9 +228,11 @@ fn mpp_to_one_hop_blinded_path() { }; let nonce = Nonce([42u8; 16]); let expanded_key = chanmon_cfgs[3].keys_manager.get_expanded_key(); + let receive_auth_key = chanmon_cfgs[3].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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(); @@ -1335,10 +1340,12 @@ fn custom_tlvs_to_blinded_path() { }; let nonce = Nonce([42u8; 16]); let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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(); @@ -1389,11 +1396,13 @@ fn fails_receive_tlvs_authentication() { }; let nonce = Nonce([42u8; 16]); let expanded_key = chanmon_cfgs[1].keys_manager.get_expanded_key(); + let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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(); @@ -1424,7 +1433,8 @@ fn fails_receive_tlvs_authentication() { 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(); @@ -1627,7 +1637,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!() } @@ -1940,7 +1950,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!() } @@ -2207,8 +2217,9 @@ fn do_test_trampoline_single_hop_receive(success: bool) { }; let nonce = Nonce([42u8; 16]); let expanded_key = nodes[2].keys_manager.get_expanded_key(); + let receive_auth_key = nodes[2].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); - let blinded_path = BlindedPaymentPath::new(&[], carol_node_id, payee_tlvs, u64::MAX, 0, nodes[2].keys_manager, &secp_ctx).unwrap(); + let blinded_path = BlindedPaymentPath::new(&[], carol_node_id, receive_auth_key, payee_tlvs, u64::MAX, 0, nodes[2].keys_manager, &secp_ctx).unwrap(); let route = Route { paths: vec![Path { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 644920557d2..91a7e3239b4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5114,7 +5114,7 @@ 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) @@ -7315,6 +7315,7 @@ where false, None, current_height, + &*self.logger, ); match create_res { Ok(info) => phantom_receives.push(( @@ -19251,6 +19252,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 = node[0].logger; let extra_fee_msat = 10; let hop_data = onion_utils::Hop::Receive { hop_data: msgs::InboundOnionReceivePayload { @@ -19272,7 +19274,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!(); } @@ -19295,7 +19297,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] @@ -19305,6 +19307,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 = node[0].logger; let current_height: u32 = node[0].node.best_block.read().unwrap().height; let result = create_recv_pending_htlc_info(onion_utils::Hop::Receive { @@ -19320,7 +19323,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/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 8425e1928ad..366f54b9c80 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -223,11 +223,13 @@ fn one_hop_blinded_path_with_custom_tlv() { }; let nonce = Nonce([42u8; 16]); let expanded_key = chanmon_cfgs[2].keys_manager.get_expanded_key(); + let receive_auth_key = chanmon_cfgs[2].keys_manager.get_receive_auth_key(); let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_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 c0c8239f621..42b67cfb562 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -59,7 +59,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::{ @@ -2340,6 +2340,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 { @@ -3655,10 +3656,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, @@ -3667,11 +3669,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); } @@ -3684,7 +3688,10 @@ where next_blinding_override, })) }, - ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => { + ChaChaDualPolyReadAdapter { + readable: BlindedPaymentTlvs::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() { @@ -3710,6 +3717,7 @@ where keysend_preimage, invoice_request, custom_tlvs, + payment_tlvs_authenticated: used_aad, })) }, } @@ -3754,6 +3762,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; @@ -3807,8 +3816,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, @@ -3817,11 +3826,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); } @@ -3834,8 +3845,9 @@ 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(); @@ -3862,6 +3874,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 f52a2d56e85..61749de8475 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -245,11 +245,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 +279,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 ) @@ -295,9 +312,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, ) @@ -441,7 +460,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()), @@ -483,7 +502,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() )? } }) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index e32b39775fe..263e65ccd33 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1663,6 +1663,16 @@ pub enum LocalHTLCFailureReason { PeerOffline, /// The HTLC was failed because the channel balance was overdrawn. ChannelBalanceOverdrawn, + /// Indicates that the payload we received failed authentication by our node. + /// + /// This can happen if a malicious party attempted to construct a [`BlindedPaymentPath`] + /// to reach us but failed to produce a valid one over which a payment can be authenticated. + /// + /// Alternatively, it may indicate that we ourselves constructed an invalid [`BlindedPaymentPath`] + /// for the payer, resulting in a payment that cannot be authenticated. + /// + /// [`BlindedPaymentPath`]: crate::blinded_path::payment::BlindedPaymentPath + UnauthenticatedPayload, } impl LocalHTLCFailureReason { @@ -1703,7 +1713,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, } } @@ -1863,7 +1873,8 @@ ser_failure_reasons!( (39, HTLCMinimum), (40, HTLCMaximum), (41, PeerOffline), - (42, ChannelBalanceOverdrawn) + (42, ChannelBalanceOverdrawn), + (43, UnauthenticatedPayload) ); impl From<&HTLCFailReason> for HTLCHandlingFailureReason { @@ -2024,7 +2035,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 { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 74e5f029597..d07765d0c76 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -329,6 +329,7 @@ where 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(); @@ -349,6 +350,7 @@ where router.create_blinded_payment_paths( payee_node_id, + receive_auth_key, usable_channels, payee_tlvs, amount_msats, diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 8434b17698d..e3031d3fe63 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, @@ -129,8 +129,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; @@ -197,7 +197,7 @@ where }) .map(|forward_node| { BlindedPaymentPath::new( - &[forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, + &[forward_node], recipient, receive_auth_key, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ) }) @@ -209,7 +209,7 @@ where _ => { if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) { BlindedPaymentPath::new( - &[], recipient, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, + &[], recipient, receive_auth_key, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ).map(|path| vec![path]) } else { @@ -243,8 +243,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 +282,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 ad8ea224205..01e6d97474b 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -289,13 +289,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, From cc41db0a2d38d9bd972a7777b78934c81ac79f4a Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 25 Sep 2025 14:17:55 +0530 Subject: [PATCH 2/3] Cleanup: Remove redundant (hmac, nonce) from codebase Now that we have introduced an alternate mechanism for authentication in the codebase, we can safely remove the now redundant (hmac, nonce) fields from the Payment ReceiveTlvs's while maintaining the security of the onion messages. --- fuzz/src/invoice_request_deser.rs | 9 +- fuzz/src/refund_deser.rs | 11 +-- lightning/src/blinded_path/payment.rs | 89 +++++-------------- lightning/src/ln/async_payments_tests.rs | 5 +- lightning/src/ln/blinded_payment_tests.rs | 43 +++------ lightning/src/ln/channelmanager.rs | 40 +-------- .../src/ln/max_payment_path_len_tests.rs | 8 +- lightning/src/ln/msgs.rs | 29 ++---- lightning/src/offers/flow.rs | 75 +++++----------- lightning/src/offers/signer.rs | 30 +------ lightning/src/routing/router.rs | 4 +- 11 files changed, 75 insertions(+), 268 deletions(-) diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 93618d1c9dc..a21303debd7 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -12,13 +12,12 @@ 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, ReceiveAuthKey}; @@ -84,7 +83,6 @@ 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 = @@ -107,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, @@ -115,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, @@ -125,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(), diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 2dea67ca087..446ac704455 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -12,12 +12,10 @@ 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, ReceiveAuthKey}; @@ -69,12 +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 receive_auth_key = ReceiveAuthKey([41; 32]); - let nonce = Nonce::from_entropy_source(&entropy_source); 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, @@ -82,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, @@ -92,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(), diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index abe128957b1..cbbd0596835 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -9,8 +9,6 @@ //! 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}; @@ -20,8 +18,6 @@ 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; @@ -137,7 +133,7 @@ impl BlindedPaymentPath { let blinded_payinfo = compute_payinfo( intermediate_nodes, - &payee_tlvs.tlvs, + &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, )?; @@ -328,26 +324,8 @@ pub struct TrampolineForwardTlvs { /// 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. @@ -356,17 +334,6 @@ 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 @@ -539,19 +506,12 @@ impl Writeable for TrampolineForwardTlvs { } } +// Note: Authentication TLV field was removed in LDK v0.3 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.tlvs.payment_constraints, required), - (65536, self.tlvs.payment_secret, required), - (65537, self.tlvs.payment_context, required), - (65539, self.authentication, required), - }); - Ok(()) - } -} - -impl Writeable for UnauthenticatedReceiveTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { (12, self.payment_constraints, required), @@ -586,7 +546,6 @@ impl Readable for BlindedPaymentTlvs { (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, option), - (65539, authentication, option), }); if let Some(short_channel_id) = scid { @@ -605,12 +564,9 @@ impl Readable for BlindedPaymentTlvs { 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)?, + payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + payment_context: payment_context.ok_or(DecodeError::InvalidValue)?, })) } } @@ -626,7 +582,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 { @@ -645,19 +600,15 @@ 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. @@ -737,7 +688,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) = @@ -860,7 +811,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; @@ -910,7 +861,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 {}), @@ -928,7 +879,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 {}), @@ -985,7 +936,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 {}), @@ -1044,7 +995,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 {}), @@ -1113,7 +1064,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 85be2799702..3f65b89821d 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -15,7 +15,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; @@ -31,7 +31,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}; @@ -74,7 +73,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(), @@ -84,10 +83,7 @@ pub fn blinded_payment_path( payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), }; - let nonce = Nonce([42u8; 16]); - let expanded_key = keys_manager.get_expanded_key(); let receive_auth_key = keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let mut secp_ctx = Secp256k1::new(); BlindedPaymentPath::new( @@ -162,7 +158,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(), @@ -170,10 +166,7 @@ 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 receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( @@ -218,7 +211,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(), @@ -226,10 +219,7 @@ 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 receive_auth_key = chanmon_cfgs[3].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let blinded_path = BlindedPaymentPath::new( &[], nodes[3].node.get_our_node_id(), receive_auth_key, payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, @@ -1330,7 +1320,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(), @@ -1338,10 +1328,8 @@ 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 receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( &[], nodes[1].node.get_our_node_id(), receive_auth_key, @@ -1386,7 +1374,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(), @@ -1394,10 +1382,7 @@ 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 receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( @@ -1419,7 +1404,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(), @@ -1427,13 +1412,12 @@ 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(), receive_auth_key, + &[], 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(); @@ -2207,7 +2191,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); // Create a 1-hop blinded path for Carol. - let payee_tlvs = UnauthenticatedReceiveTlvs { + let payee_tlvs = ReceiveTlvs { payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -2215,10 +2199,7 @@ 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 receive_auth_key = nodes[2].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let blinded_path = BlindedPaymentPath::new(&[], carol_node_id, receive_auth_key, payee_tlvs, u64::MAX, 0, nodes[2].keys_manager, &secp_ctx).unwrap(); let route = Route { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 91a7e3239b4..34262f95ece 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::{ @@ -100,7 +98,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, @@ -574,34 +571,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. /// @@ -5642,12 +5611,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, ); @@ -13365,11 +13332,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, @@ -15273,7 +15237,6 @@ where InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => { let result = self.flow.create_invoice_builder_from_invoice_request_with_keys( &self.router, - &*self.entropy_source, &request, self.list_usable_channels(), get_payment_info, @@ -15298,7 +15261,6 @@ where InvoiceRequestVerifiedFromOffer::ExplicitKeys(request) => { let result = self.flow.create_invoice_builder_from_invoice_request_without_keys( &self.router, - &*self.entropy_source, &request, self.list_usable_channels(), get_payment_info, diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 366f54b9c80..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,10 +220,7 @@ 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 receive_auth_key = chanmon_cfgs[2].keys_manager.get_receive_auth_key(); - let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( &[], diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 42b67cfb562..c899972893a 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; @@ -3692,17 +3689,9 @@ where readable: BlindedPaymentTlvs::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); } @@ -3849,17 +3838,9 @@ where 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); } diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index d07765d0c76..cdb6ff936c2 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; @@ -317,17 +317,14 @@ 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; @@ -340,13 +337,11 @@ 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, @@ -361,18 +356,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 +807,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 +831,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, amount_msat, payment_secret, @@ -911,7 +901,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, Some(amount_msats), payment_secret, @@ -956,18 +945,14 @@ where /// Returns a [`Bolt12SemanticError`] if: /// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`]. /// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`]. - pub fn create_invoice_builder_from_invoice_request_with_keys<'a, ES: Deref, R: Deref, F>( - &self, router: &R, entropy_source: ES, - invoice_request: &'a VerifiedInvoiceRequest, + pub fn create_invoice_builder_from_invoice_request_with_keys<'a, R: Deref, F>( + &self, router: &R, invoice_request: &'a VerifiedInvoiceRequest, usable_channels: Vec, get_payment_info: F, ) -> Result<(InvoiceBuilder<'a, DerivedSigningPubkey>, MessageContext), Bolt12SemanticError> where - ES::Target: EntropySource, - R::Target: Router, F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>, { - let entropy = &*entropy_source; let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; let amount_msats = @@ -983,7 +968,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, Some(amount_msats), payment_secret, @@ -1021,17 +1005,14 @@ where /// Returns a [`Bolt12SemanticError`] if: /// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`]. /// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`]. - pub fn create_invoice_builder_from_invoice_request_without_keys<'a, ES: Deref, R: Deref, F>( - &self, router: &R, entropy_source: ES, - invoice_request: &'a VerifiedInvoiceRequest, + pub fn create_invoice_builder_from_invoice_request_without_keys<'a, R: Deref, F>( + &self, router: &R, invoice_request: &'a VerifiedInvoiceRequest, usable_channels: Vec, get_payment_info: F, ) -> Result<(InvoiceBuilder<'a, ExplicitSigningPubkey>, MessageContext), Bolt12SemanticError> where - ES::Target: EntropySource, R::Target: Router, F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>, { - let entropy = &*entropy_source; let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; let amount_msats = @@ -1047,7 +1028,6 @@ where let payment_paths = self .create_blinded_payment_paths( router, - entropy, usable_channels, Some(amount_msats), payment_secret, @@ -1378,12 +1358,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. @@ -1397,7 +1376,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(()) @@ -1454,11 +1433,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(); @@ -1473,7 +1450,6 @@ where offer_nonce, peers.clone(), usable_channels.clone(), - &*entropy, &*router, ) { Ok((invoice, path)) => (invoice, path), @@ -1639,7 +1615,6 @@ where offer_nonce, peers, usable_channels, - &*entropy, router, ) { Ok(res) => res, @@ -1674,12 +1649,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; @@ -1706,7 +1680,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..739bce3c785 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,7 +40,7 @@ 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: @@ -49,9 +48,6 @@ const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; // 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]; - /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. #[derive(Clone)] @@ -449,27 +445,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 e3031d3fe63..9a3548eab83 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -180,7 +180,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 { From 18e6b33a636348eb2b2eb058ad08fce565ff6938 Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 4 Nov 2025 16:11:07 +0530 Subject: [PATCH 3/3] Clean up outdated HMAC input constant names from comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reserved values [5; 16], [7; 16], and [8; 16] are already flagged as unsafe to reuse. Listing their old names from pre-LDK v0.3 days doesn't add value anymore and risks misleading anyone into thinking they’re still meaningful. --- lightning/src/offers/signer.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 739bce3c785..1f5772a6e86 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -42,11 +42,6 @@ const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; // authentication. // 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]; /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified.