diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 1f7147f3203..7c1e26522cd 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -1127,7 +1127,7 @@ mod tests { use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::router::{CandidateRouteHop, DefaultRouter, Path, RouteHop}; use lightning::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp, ScoreUpdate}; - use lightning::sign::{ChangeDestinationSource, InMemorySigner, KeysManager}; + use lightning::sign::{ChangeDestinationSource, InMemorySigner, KeysManager, NodeSigner}; use lightning::types::features::{ChannelFeatures, NodeFeatures}; use lightning::types::payment::PaymentHash; use lightning::util::config::UserConfig; @@ -1603,6 +1603,7 @@ mod tests { let msg_router = Arc::new(DefaultMessageRouter::new( network_graph.clone(), Arc::clone(&keys_manager), + keys_manager.get_inbound_payment_key(), )); let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); let kv_store = diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 6de6b77be90..c5ee0e75bfc 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -232,7 +232,14 @@ mod test { secp_ctx: &Secp256k1, ) -> Result, ()> { let keys = KeysManager::new(&[0; 32], 42, 43); - Ok(vec![BlindedMessagePath::one_hop(recipient, context, &keys, secp_ctx).unwrap()]) + Ok(vec![BlindedMessagePath::one_hop( + recipient, + context, + keys.get_inbound_payment_key(), + &keys, + secp_ctx, + ) + .unwrap()]) } } impl Deref for DirectlyConnectedRouter { @@ -334,8 +341,14 @@ mod test { let (msg, context) = payer.resolver.resolve_name(payment_id, name.clone(), &*payer_keys).unwrap(); let query_context = MessageContext::DNSResolver(context); - let reply_path = - BlindedMessagePath::one_hop(payer_id, query_context, &*payer_keys, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::one_hop( + payer_id, + query_context, + payer_keys.get_inbound_payment_key(), + &*payer_keys, + &secp_ctx, + ) + .unwrap(); payer.pending_messages.lock().unwrap().push(( DNSResolverMessage::DNSSECQuery(msg), MessageSendInstructions::WithSpecifiedReplyPath { diff --git a/lightning-liquidity/tests/common/mod.rs b/lightning-liquidity/tests/common/mod.rs index 2259d1eae06..cccdd6954d3 100644 --- a/lightning-liquidity/tests/common/mod.rs +++ b/lightning-liquidity/tests/common/mod.rs @@ -5,7 +5,7 @@ #![allow(unused_macros)] use lightning::chain::Filter; -use lightning::sign::EntropySource; +use lightning::sign::{EntropySource, NodeSigner}; use bitcoin::blockdata::constants::{genesis_block, ChainHash}; use bitcoin::blockdata::transaction::Transaction; @@ -418,8 +418,11 @@ pub(crate) fn create_liquidity_node( scorer.clone(), Default::default(), )); - let msg_router = - Arc::new(DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&keys_manager))); + let msg_router = Arc::new(DefaultMessageRouter::new( + Arc::clone(&network_graph), + Arc::clone(&keys_manager), + keys_manager.get_inbound_payment_key(), + )); let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); let kv_store = Arc::new(FilesystemStore::new(format!("{}_persister_{}", &persist_dir, i).into())); diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 0e3977ab68e..69ff4112e18 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -11,6 +11,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; +use crate::offers::signer; #[allow(unused_imports)] use crate::prelude::*; @@ -19,9 +20,9 @@ use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode, use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::io; use crate::io::Cursor; -use crate::ln::channelmanager::PaymentId; +use crate::ln::channelmanager::{PaymentId, Verification}; use crate::ln::msgs::DecodeError; -use crate::ln::onion_utils; +use crate::ln::{inbound_payment, onion_utils}; use crate::offers::nonce::Nonce; use crate::onion_message::packet::ControlTlvs; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; @@ -55,23 +56,51 @@ impl Readable for BlindedMessagePath { impl BlindedMessagePath { /// Create a one-hop blinded path for a message. pub fn one_hop( - recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES, - secp_ctx: &Secp256k1, + recipient_node_id: PublicKey, context: MessageContext, + expanded_key: inbound_payment::ExpandedKey, entropy_source: ES, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, { - Self::new(&[], recipient_node_id, context, entropy_source, secp_ctx) + Self::new(&[], recipient_node_id, context, entropy_source, expanded_key, secp_ctx) } /// Create a path for an onion message, to be forwarded along `node_pks`. The last node /// pubkey in `node_pks` will be the destination node. /// /// Errors if no hops are provided or if `node_pk`(s) are invalid. - // TODO: make all payloads the same size with padding + add dummy hops pub fn new( intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey, - context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1, + context: MessageContext, entropy_source: ES, expanded_key: inbound_payment::ExpandedKey, + secp_ctx: &Secp256k1, + ) -> Result + where + ES::Target: EntropySource, + { + BlindedMessagePath::new_with_dummy_hops( + intermediate_nodes, + 0, + recipient_node_id, + context, + entropy_source, + expanded_key, + secp_ctx, + ) + } + + /// Create a path for an onion message, to be forwarded along `node_pks`. + /// + /// Additionally allows appending a number of dummy hops before the final hop, + /// increasing the total path length and enhancing privacy by obscuring the true + /// distance between sender and recipient. + /// + /// The last node pubkey in `node_pks` will be the destination node. + /// + /// Errors if no hops are provided or if `node_pk`(s) are invalid. + pub fn new_with_dummy_hops( + intermediate_nodes: &[MessageForwardNode], dummy_hops_count: u8, + recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES, + expanded_key: inbound_payment::ExpandedKey, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, @@ -88,9 +117,12 @@ impl BlindedMessagePath { blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: blinded_hops( secp_ctx, + entropy_source, + expanded_key, intermediate_nodes, recipient_node_id, context, + dummy_hops_count, &blinding_secret, ) .map_err(|_| ())?, @@ -258,6 +290,45 @@ pub(crate) struct ForwardTlvs { pub(crate) next_blinding_override: Option, } +pub(crate) struct UnauthenticatedDummyTlvs {} + +impl Writeable for UnauthenticatedDummyTlvs { + fn write(&self, _writer: &mut W) -> Result<(), io::Error> { + Ok(()) + } +} + +impl Verification for UnauthenticatedDummyTlvs { + /// Constructs an HMAC to include in [`OffersContext`] for the data along with the given + /// [`Nonce`]. + fn hmac_data(&self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey) -> Hmac { + signer::hmac_for_dummy_tlvs(self, nonce, expanded_key) + } + + /// Authenticates the data using an HMAC and a [`Nonce`] taken from an [`OffersContext`]. + fn verify_data( + &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, + ) -> Result<(), ()> { + signer::verify_dummy_tlvs(self, hmac, nonce, expanded_key) + } +} + +pub(crate) struct DummyTlvs { + pub(crate) dummy_tlvs: UnauthenticatedDummyTlvs, + /// An HMAC of `tlvs` along with a nonce used to construct it. + pub(crate) authentication: (Hmac, Nonce), +} + +impl Writeable for DummyTlvs { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + encode_tlv_stream!(writer, { + (65539, self.authentication, required), + }); + + Ok(()) + } +} + /// Similar to [`ForwardTlvs`], but these TLVs are for the final node. pub(crate) struct ReceiveTlvs { /// If `context` is `Some`, it is used to identify the blinded path that this onion message is @@ -505,13 +576,18 @@ impl_writeable_tlv_based!(DNSResolverContext, { pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100; /// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. -pub(super) fn blinded_hops( - secp_ctx: &Secp256k1, intermediate_nodes: &[MessageForwardNode], - recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey, -) -> Result, secp256k1::Error> { +pub(super) fn blinded_hops( + secp_ctx: &Secp256k1, entropy_source: ES, expanded_key: inbound_payment::ExpandedKey, + intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey, + context: MessageContext, dummy_hops_count: u8, session_priv: &SecretKey, +) -> Result, secp256k1::Error> +where + ES::Target: EntropySource, +{ let pks = intermediate_nodes .iter() .map(|node| node.node_id) + .chain((0..dummy_hops_count).map(|_| recipient_node_id)) .chain(core::iter::once(recipient_node_id)); let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some()); @@ -526,6 +602,12 @@ pub(super) fn blinded_hops( .map(|next_hop| { ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None }) }) + .chain((0..dummy_hops_count).map(|_| { + let dummy_tlvs = UnauthenticatedDummyTlvs {}; + let nonce = Nonce::from_entropy_source(&*entropy_source); + let hmac = dummy_tlvs.hmac_data(nonce, &expanded_key); + ControlTlvs::Dummy(DummyTlvs { dummy_tlvs, authentication: (hmac, nonce) }) + })) .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { context: Some(context) }))); if is_compact { diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index f015886933a..ae26d51b1f2 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -351,10 +351,7 @@ 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, - } + ReceiveTlvs { authentication: (self.hmac_data(nonce, expanded_key), nonce), tlvs: self } } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ea186c4e329..939e3928dba 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -475,12 +475,12 @@ impl Ord for ClaimableHTLC { pub trait Verification { /// Constructs an HMAC to include in [`OffersContext`] for the data along with the given /// [`Nonce`]. - fn hmac_for_offer_payment( + fn hmac_data( &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( + fn verify_data( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()>; } @@ -488,7 +488,7 @@ pub trait Verification { impl Verification for PaymentHash { /// Constructs an HMAC to include in [`OffersContext::InboundPayment`] for the payment hash /// along with the given [`Nonce`]. - fn hmac_for_offer_payment( + fn hmac_data( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac { signer::hmac_for_payment_hash(*self, nonce, expanded_key) @@ -496,7 +496,7 @@ impl Verification for PaymentHash { /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an /// [`OffersContext::InboundPayment`]. - fn verify_for_offer_payment( + fn verify_data( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()> { signer::verify_payment_hash(*self, hmac, nonce, expanded_key) @@ -504,13 +504,13 @@ impl Verification for PaymentHash { } impl Verification for UnauthenticatedReceiveTlvs { - fn hmac_for_offer_payment( + fn hmac_data( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac { signer::hmac_for_payment_tlvs(self, nonce, expanded_key) } - fn verify_for_offer_payment( + fn verify_data( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()> { signer::verify_payment_tlvs(self, hmac, nonce, expanded_key) @@ -550,7 +550,7 @@ impl PaymentId { impl Verification for PaymentId { /// Constructs an HMAC to include in [`OffersContext::OutboundPayment`] for the payment id /// along with the given [`Nonce`]. - fn hmac_for_offer_payment( + fn hmac_data( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac { signer::hmac_for_offer_payment_id(*self, nonce, expanded_key) @@ -558,7 +558,7 @@ impl Verification for PaymentId { /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an /// [`OffersContext::OutboundPayment`]. - fn verify_for_offer_payment( + fn verify_data( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()> { signer::verify_offer_payment_id(*self, hmac, nonce, expanded_key) @@ -10535,7 +10535,7 @@ where }; let invoice_request = builder.build_and_sign()?; - let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); + let hmac = payment_id.hmac_data(nonce, expanded_key); let context = MessageContext::Offers( OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) } ); @@ -10639,7 +10639,7 @@ where let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let hmac = payment_hash.hmac_data(nonce, expanded_key); let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash: invoice.payment_hash(), nonce, hmac }); @@ -12419,7 +12419,7 @@ where .release_invoice_requests_awaiting_invoice() { let RetryableInvoiceRequest { invoice_request, nonce, .. } = retryable_invoice_request; - let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); + let hmac = payment_id.hmac_data(nonce, &self.inbound_payment_key); let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, nonce, @@ -12602,7 +12602,7 @@ where match response { Ok(invoice) => { let nonce = Nonce::from_entropy_source(&*self.entropy_source); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let hmac = payment_hash.hmac_data(nonce, expanded_key); let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash, nonce, hmac }); Some((OffersMessage::Invoice(invoice), responder.respond_with_reply_path(context))) }, @@ -12639,7 +12639,7 @@ where OffersMessage::StaticInvoice(invoice) => { let payment_id = match context { Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { - if payment_id.verify_for_offer_payment(hmac, nonce, expanded_key).is_err() { + if payment_id.verify_data(hmac, nonce, expanded_key).is_err() { return None } payment_id @@ -12652,7 +12652,7 @@ where OffersMessage::InvoiceError(invoice_error) => { let payment_hash = match context { Some(OffersContext::InboundPayment { payment_hash, nonce, hmac }) => { - match payment_hash.verify_for_offer_payment(hmac, nonce, expanded_key) { + match payment_hash.verify_data(hmac, nonce, expanded_key) { Ok(_) => Some(payment_hash), Err(_) => None, } @@ -12665,7 +12665,7 @@ where match context { Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { - if let Ok(()) = payment_id.verify_for_offer_payment(hmac, nonce, expanded_key) { + if let Ok(()) = payment_id.verify_data(hmac, nonce, expanded_key) { self.abandon_payment_with_reason( payment_id, PaymentFailureReason::InvoiceRequestRejected, ); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 73af3a83729..7397c2eddc8 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -27,7 +27,7 @@ use crate::onion_message::messenger::OnionMessenger; use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; -use crate::sign::{EntropySource, RandomBytes}; +use crate::sign::{EntropySource, NodeSigner, RandomBytes}; use crate::util::config::{MaxDustHTLCExposure, UserConfig}; use crate::util::logger::Logger; use crate::util::scid_utils; @@ -723,7 +723,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { signer_provider: self.keys_manager, fee_estimator: &test_utils::TestFeeEstimator::new(253), router: &test_utils::TestRouter::new(Arc::clone(&network_graph), &self.logger, &scorer), - message_router: &test_utils::TestMessageRouter::new(network_graph, self.keys_manager), + message_router: &test_utils::TestMessageRouter::new(network_graph, self.keys_manager, self.keys_manager.get_inbound_payment_key()), chain_monitor: self.chain_monitor, tx_broadcaster: &broadcaster, logger: &self.logger, @@ -3347,7 +3347,7 @@ pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster, fee_estimator: &chanmon_cfgs[i].fee_estimator, router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].logger, &chanmon_cfgs[i].scorer), - message_router: test_utils::TestMessageRouter::new(network_graph.clone(), &chanmon_cfgs[i].keys_manager), + message_router: test_utils::TestMessageRouter::new(network_graph.clone(), &chanmon_cfgs[i].keys_manager, chanmon_cfgs[i].keys_manager.get_inbound_payment_key()), chain_monitor, keys_manager: &chanmon_cfgs[i].keys_manager, node_seed: seed, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 3fdd579535b..10afca6b99b 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -18,7 +18,7 @@ use crate::chain::channelmonitor; use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE}; use crate::chain::transaction::OutPoint; use crate::ln::onion_utils::LocalHTLCFailureReason; -use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider}; +use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, NodeSigner, OutputSpender, SignerProvider}; use crate::events::bump_transaction::WalletSource; use crate::events::{Event, FundingInfo, PathFailure, PaymentPurpose, ClosureReason, HTLCHandlingFailureType, PaymentFailureReason}; use crate::ln::types::ChannelId; @@ -5992,7 +5992,7 @@ pub fn test_key_derivation_params() { let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); let router = test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[0].logger, &scorer); - let message_router = test_utils::TestMessageRouter::new(network_graph.clone(), &keys_manager); + let message_router = test_utils::TestMessageRouter::new(network_graph.clone(), &keys_manager, keys_manager.get_inbound_payment_key()); let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, tx_broadcaster: &chanmon_cfgs[0].tx_broadcaster, fee_estimator: &chanmon_cfgs[0].fee_estimator, router, message_router, chain_monitor, keys_manager: &keys_manager, network_graph, node_seed: seed, override_init_features: alloc::rc::Rc::new(core::cell::RefCell::new(None)) }; let mut node_cfgs = create_node_cfgs(3, &chanmon_cfgs); node_cfgs.remove(0); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index c27db4a55b4..07023eb7b7a 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -3363,7 +3363,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => { let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs; let expanded_key = node_signer.get_inbound_payment_key(); - if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() { + if tlvs.verify_data(hmac, nonce, &expanded_key).is_err() { return Err(DecodeError::InvalidValue); } @@ -3489,7 +3489,7 @@ impl ReadableArgs<(Option, NS)> for InboundTrampolinePaylo ChaChaPolyReadAdapter { readable: BlindedTrampolineTlvs::Receive(receive_tlvs) } => { let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs; let expanded_key = node_signer.get_inbound_payment_key(); - if tlvs.verify_for_offer_payment(hmac, nonce, &expanded_key).is_err() { + if tlvs.verify_data(hmac, nonce, &expanded_key).is_err() { return Err(DecodeError::InvalidValue); } diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 329b90d2076..5124c7e9ce6 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -9,6 +9,7 @@ //! Utilities for signing offer messages and verifying metadata. +use crate::blinded_path::message::UnauthenticatedDummyTlvs; use crate::blinded_path::payment::UnauthenticatedReceiveTlvs; use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; @@ -570,3 +571,26 @@ pub(crate) fn verify_held_htlc_available_context( Err(()) } } + +pub(crate) fn hmac_for_dummy_tlvs( + tlvs: &UnauthenticatedDummyTlvs, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Hmac { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Msgs Dummies"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + hmac.input(PAYMENT_TLVS_HMAC_INPUT); + tlvs.write(&mut hmac).unwrap(); + + Hmac::from_engine(hmac) +} + +pub(crate) fn verify_dummy_tlvs( + tlvs: &UnauthenticatedDummyTlvs, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Result<(), ()> { + if hmac_for_dummy_tlvs(tlvs, nonce, expanded_key) == hmac { + Ok(()) + } else { + Err(()) + } +} diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index b28819ee692..99e724380b6 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -26,6 +26,7 @@ use crate::blinded_path::message::{ use crate::blinded_path::utils::is_padded; use crate::blinded_path::EmptyNodeIdLookUp; use crate::events::{Event, EventsProvider}; +use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{self, BaseMessageHandler, DecodeError, OnionMessageHandler}; use crate::routing::gossip::{NetworkGraph, P2PGossipSync}; use crate::routing::test_utils::{add_channel, add_or_update_node}; @@ -279,8 +280,11 @@ fn create_nodes_using_cfgs(cfgs: Vec) -> Vec { let node_signer = Arc::new(TestNodeSigner::new(secret_key)); let node_id_lookup = Arc::new(EmptyNodeIdLookUp {}); - let message_router = - Arc::new(DefaultMessageRouter::new(network_graph.clone(), entropy_source.clone())); + let message_router = Arc::new(DefaultMessageRouter::new( + network_graph.clone(), + entropy_source.clone(), + node_signer.get_inbound_payment_key(), + )); let offers_message_handler = Arc::new(TestOffersMessageHandler {}); let async_payments_message_handler = Arc::new(TestAsyncPaymentsMessageHandler {}); let dns_resolver_message_handler = Arc::new(TestDNSResolverMessageHandler {}); @@ -409,8 +413,38 @@ fn one_blinded_hop() { let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; + let expanded_key = ExpandedKey::new([42; 32]); let blinded_path = - BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, &secp_ctx).unwrap(); + BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, expanded_key, &secp_ctx) + .unwrap(); + let destination = Destination::BlindedPath(blinded_path); + let instructions = MessageSendInstructions::WithoutReplyPath { destination }; + nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); + nodes[1].custom_message_handler.expect_message(TestCustomMessage::Pong); + pass_along_path(&nodes); +} + +#[test] +fn blinded_path_with_dummy() { + let nodes = create_nodes(2); + let test_msg = TestCustomMessage::Pong; + + let secp_ctx = Secp256k1::new(); + let context = MessageContext::Custom(Vec::new()); + let entropy = &*nodes[1].entropy_source; + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new_with_dummy_hops( + &[], + 5, + nodes[1].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); + // Make sure that dummy hops are do added to the blinded path. + assert_eq!(blinded_path.blinded_hops().len(), 6); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -428,9 +462,16 @@ fn two_unblinded_two_blinded() { [MessageForwardNode { node_id: nodes[3].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[4].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[4].node_id, context, entropy, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[4].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let path = OnionMessagePath { intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id], destination: Destination::BlindedPath(blinded_path), @@ -454,9 +495,16 @@ fn three_blinded_hops() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[3].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, entropy, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[3].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -481,8 +529,10 @@ fn async_response_over_one_blinded_hop() { let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; + let expanded_key = ExpandedKey::new([42; 32]); let reply_path = - BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, &secp_ctx).unwrap(); + BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, expanded_key, &secp_ctx) + .unwrap(); // 4. Create a responder using the reply path for Alice. let responder = Some(Responder::new(reply_path)); @@ -520,9 +570,16 @@ fn async_response_with_reply_path_succeeds() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); - let reply_path = - BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let reply_path = BlindedMessagePath::new( + &[], + bob.node_id, + context, + &*bob.entropy_source, + expanded_key, + &secp_ctx, + ) + .unwrap(); // Alice asynchronously responds to Bob, expecting a response back from him. let responder = Responder::new(reply_path); @@ -561,9 +618,16 @@ fn async_response_with_reply_path_fails() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); - let reply_path = - BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let reply_path = BlindedMessagePath::new( + &[], + bob.node_id, + context, + &*bob.entropy_source, + expanded_key, + &secp_ctx, + ) + .unwrap(); // Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced and // disconnected. Thus, a reply path could no be created for the response. @@ -604,6 +668,7 @@ fn test_blinded_path_padding_for_full_length_path() { let test_msg = TestCustomMessage::Pong; let secp_ctx = Secp256k1::new(); + let expanded_key = ExpandedKey::new([42; 32]); let intermediate_nodes = [ MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, @@ -611,11 +676,13 @@ fn test_blinded_path_padding_for_full_length_path() { // Update the context to create a larger final receive TLVs, ensuring that // the hop sizes vary before padding. let context = MessageContext::Custom(vec![0u8; 42]); - let blinded_path = BlindedMessagePath::new( + let blinded_path = BlindedMessagePath::new_with_dummy_hops( &intermediate_nodes, + 5, nodes[3].node_id, context, &*nodes[3].entropy_source, + expanded_key, &secp_ctx, ) .unwrap(); @@ -635,6 +702,7 @@ fn test_blinded_path_no_padding_for_compact_path() { // Check that for a compact blinded path, no padding is applied. let nodes = create_nodes(4); let secp_ctx = Secp256k1::new(); + let expanded_key = ExpandedKey::new([42; 32]); // Include some short_channel_id, so that MessageRouter uses this to create compact blinded paths. let intermediate_nodes = [ @@ -644,11 +712,13 @@ fn test_blinded_path_no_padding_for_compact_path() { // Update the context to create a larger final receive TLVs, ensuring that // the hop sizes vary before padding. let context = MessageContext::Custom(vec![0u8; 42]); - let blinded_path = BlindedMessagePath::new( + let blinded_path = BlindedMessagePath::new_with_dummy_hops( &intermediate_nodes, + 5, nodes[3].node_id, context, &*nodes[3].entropy_source, + expanded_key, &secp_ctx, ) .unwrap(); @@ -664,15 +734,22 @@ fn we_are_intro_node() { let test_msg = TestCustomMessage::Pong; let secp_ctx = Secp256k1::new(); + let expanded_key = ExpandedKey::new([42; 32]); let intermediate_nodes = [ MessageForwardNode { node_id: nodes[0].node_id, short_channel_id: None }, MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -685,9 +762,15 @@ fn we_are_intro_node() { [MessageForwardNode { node_id: nodes[0].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[1].node_id, context, entropy, &secp_ctx) - .unwrap(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[1].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -704,13 +787,20 @@ fn invalid_blinded_path_error() { let test_msg = TestCustomMessage::Pong; let secp_ctx = Secp256k1::new(); + let expanded_key = ExpandedKey::new([42; 32]); let intermediate_nodes = [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let mut blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let mut blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); blinded_path.clear_blinded_hops(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -724,6 +814,7 @@ fn reply_path() { let mut nodes = create_nodes(4); let test_msg = TestCustomMessage::Ping; let secp_ctx = Secp256k1::new(); + let expanded_key = ExpandedKey::new([42; 32]); // Destination::Node let path = OnionMessagePath { @@ -737,9 +828,15 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let reply_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, entropy, &secp_ctx) - .unwrap(); + let reply_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[0].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); nodes[0] .messenger .send_onion_message_using_path(path, test_msg.clone(), Some(reply_path)) @@ -758,9 +855,15 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[3].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, entropy, &secp_ctx) - .unwrap(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[3].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let intermediate_nodes = [ MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, @@ -768,9 +871,15 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let reply_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, entropy, &secp_ctx) - .unwrap(); + let reply_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[0].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let instructions = MessageSendInstructions::WithSpecifiedReplyPath { destination, reply_path }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -866,9 +975,16 @@ fn requests_peer_connection_for_buffered_messages() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -908,9 +1024,16 @@ fn drops_buffered_messages_waiting_for_peer_connection() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -966,9 +1089,16 @@ fn intercept_offline_peer_oms() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let expanded_key = ExpandedKey::new([42; 32]); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + context, + entropy, + expanded_key, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 6009a276976..790047488a3 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -20,8 +20,8 @@ use super::async_payments::AsyncPaymentsMessage; use super::async_payments::AsyncPaymentsMessageHandler; use super::dns_resolution::{DNSResolverMessage, DNSResolverMessageHandler}; use super::offers::{OffersMessage, OffersMessageHandler}; -use super::packet::OnionMessageContents; use super::packet::ParsedOnionMessageContents; +use super::packet::{DummyControlTlvs, OnionMessageContents}; use super::packet::{ ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, BIG_PACKET_HOP_DATA_LEN, SMALL_PACKET_HOP_DATA_LEN, @@ -29,16 +29,17 @@ use super::packet::{ #[cfg(async_payments)] use crate::blinded_path::message::AsyncPaymentsContext; use crate::blinded_path::message::{ - BlindedMessagePath, DNSResolverContext, ForwardTlvs, MessageContext, MessageForwardNode, - NextMessageHop, OffersContext, ReceiveTlvs, + BlindedMessagePath, DNSResolverContext, DummyTlvs, ForwardTlvs, MessageContext, + MessageForwardNode, NextMessageHop, OffersContext, ReceiveTlvs, }; use crate::blinded_path::utils; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; use crate::events::{Event, EventHandler, EventsProvider, ReplayEvent}; +use crate::ln::channelmanager::Verification; use crate::ln::msgs::{ self, BaseMessageHandler, MessageSendEvent, OnionMessage, OnionMessageHandler, SocketAddress, }; -use crate::ln::onion_utils; +use crate::ln::{inbound_payment, onion_utils}; use crate::routing::gossip::{NetworkGraph, NodeId, ReadOnlyNetworkGraph}; use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::types::features::{InitFeatures, NodeFeatures}; @@ -191,7 +192,7 @@ where /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; /// # use lightning::blinded_path::EmptyNodeIdLookUp; /// # use lightning::blinded_path::message::{BlindedMessagePath, MessageForwardNode, MessageContext}; -/// # use lightning::sign::{EntropySource, KeysManager}; +/// # use lightning::sign::{EntropySource, KeysManager, NodeSigner}; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; /// # use lightning::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions, OnionMessagePath, OnionMessenger}; /// # use lightning::onion_message::packet::OnionMessageContents; @@ -272,8 +273,10 @@ where /// MessageForwardNode { node_id: hop_node_id3, short_channel_id: None }, /// MessageForwardNode { node_id: hop_node_id4, short_channel_id: None }, /// ]; +/// /// let context = MessageContext::Custom(Vec::new()); -/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, context, &keys_manager, &secp_ctx).unwrap(); +/// let expanded_key = keys_manager.get_inbound_payment_key(); +/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, context, &keys_manager, expanded_key, &secp_ctx).unwrap(); /// /// // Send a custom onion message to a blinded path. /// let destination = Destination::BlindedPath(blinded_path); @@ -539,6 +542,7 @@ where { network_graph: G, entropy_source: ES, + inbound_payment_key: inbound_payment::ExpandedKey, } impl>, L: Deref, ES: Deref> DefaultMessageRouter @@ -547,8 +551,10 @@ where ES::Target: EntropySource, { /// Creates a [`DefaultMessageRouter`] using the given [`NetworkGraph`]. - pub fn new(network_graph: G, entropy_source: ES) -> Self { - Self { network_graph, entropy_source } + pub fn new( + network_graph: G, entropy_source: ES, expanded_key: inbound_payment::ExpandedKey, + ) -> Self { + Self { network_graph, entropy_source, inbound_payment_key: expanded_key } } fn create_blinded_paths_from_iter< @@ -556,7 +562,8 @@ where T: secp256k1::Signing + secp256k1::Verification, >( network_graph: &G, recipient: PublicKey, context: MessageContext, peers: I, - entropy_source: &ES, secp_ctx: &Secp256k1, compact_paths: bool, + entropy_source: &ES, expanded_key: &inbound_payment::ExpandedKey, secp_ctx: &Secp256k1, + compact_paths: bool, ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PATHS: usize = 3; @@ -565,6 +572,13 @@ where // recipient's node_id. const MIN_PEER_CHANNELS: usize = 3; + // Add a random number (1 to 5) of dummy hops to each non-compact blinded path + // to make it harder to infer the recipient's position. + let dummy_hops_count = compact_paths.then_some(0).unwrap_or_else(|| { + let random_byte = entropy_source.get_secure_random_bytes()[0]; + (random_byte % 5) + 1 + }); + let network_graph = network_graph.deref().read_only(); let is_recipient_announced = network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); @@ -595,7 +609,15 @@ where let paths = peer_info .into_iter() .map(|(peer, _, _)| { - BlindedMessagePath::new(&[peer], recipient, context.clone(), entropy, secp_ctx) + BlindedMessagePath::new_with_dummy_hops( + &[peer], + dummy_hops_count, + recipient, + context.clone(), + entropy, + *expanded_key, + secp_ctx, + ) }) .take(MAX_PATHS) .collect::, _>>(); @@ -604,8 +626,15 @@ where Ok(paths) if !paths.is_empty() => Ok(paths), _ => { if is_recipient_announced { - BlindedMessagePath::new(&[], recipient, context, &**entropy_source, secp_ctx) - .map(|path| vec![path]) + BlindedMessagePath::new( + &[], + recipient, + context, + &**entropy_source, + *expanded_key, + secp_ctx, + ) + .map(|path| vec![path]) } else { Err(()) } @@ -664,7 +693,7 @@ where pub(crate) fn create_blinded_paths( network_graph: &G, recipient: PublicKey, context: MessageContext, peers: Vec, - entropy_source: &ES, secp_ctx: &Secp256k1, + entropy_source: &ES, expanded_key: &inbound_payment::ExpandedKey, secp_ctx: &Secp256k1, ) -> Result, ()> { let peers = peers.into_iter().map(|node_id| MessageForwardNode { node_id, short_channel_id: None }); @@ -674,6 +703,7 @@ where context, peers.into_iter(), entropy_source, + expanded_key, secp_ctx, false, ) @@ -681,7 +711,8 @@ where pub(crate) fn create_compact_blinded_paths( network_graph: &G, recipient: PublicKey, context: MessageContext, - peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, + peers: Vec, entropy_source: &ES, + expanded_key: &inbound_payment::ExpandedKey, secp_ctx: &Secp256k1, ) -> Result, ()> { Self::create_blinded_paths_from_iter( network_graph, @@ -689,6 +720,7 @@ where context, peers.into_iter(), entropy_source, + expanded_key, secp_ctx, true, ) @@ -717,6 +749,7 @@ where context, peers, &self.entropy_source, + &self.inbound_payment_key, secp_ctx, ) } @@ -731,6 +764,7 @@ where context, peers, &self.entropy_source, + &self.inbound_payment_key, secp_ctx, ) } @@ -1115,6 +1149,45 @@ where Err(()) }, }, + Ok(( + Payload::Dummy(DummyControlTlvs::Unblinded(DummyTlvs { dummy_tlvs, authentication })), + Some((next_hop_hmac, new_packet_bytes)), + )) => { + let expanded_key = node_signer.get_inbound_payment_key(); + dummy_tlvs.verify_data(authentication.0, authentication.1, &expanded_key)?; + + let packet_pubkey = msg.onion_routing_packet.public_key; + let new_pubkey_opt = + onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss); + let new_pubkey = match new_pubkey_opt { + Ok(pk) => pk, + Err(e) => { + log_trace!(logger, "Failed to compute next hop packet pubkey: {}", e); + return Err(()); + }, + }; + let outgoing_packet = Packet { + version: 0, + public_key: new_pubkey, + hop_data: new_packet_bytes, + hmac: next_hop_hmac, + }; + let onion_message = OnionMessage { + blinding_point: match onion_utils::next_hop_pubkey( + &secp_ctx, + msg.blinding_point, + control_tlvs_ss.as_ref(), + ) { + Ok(bp) => bp, + Err(e) => { + log_trace!(logger, "Failed to compute next blinding point: {}", e); + return Err(()); + }, + }, + onion_routing_packet: outgoing_packet, + }; + peel_onion_message(&onion_message, secp_ctx, node_signer, logger, custom_handler) + }, Ok(( Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs { next_hop, @@ -1122,11 +1195,6 @@ where })), Some((next_hop_hmac, new_packet_bytes)), )) => { - // TODO: we need to check whether `next_hop` is our node, in which case this is a dummy - // blinded hop and this onion message is destined for us. In this situation, we should keep - // unwrapping the onion layers to get to the final payload. Since we don't have the option - // of creating blinded paths with dummy hops currently, we should be ok to not handle this - // for now. let packet_pubkey = msg.onion_routing_packet.public_key; let new_pubkey_opt = onion_utils::next_hop_pubkey(&secp_ctx, packet_pubkey, &onion_decode_ss); diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 632cbc9c8a3..246281efa7a 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -17,7 +17,10 @@ use super::async_payments::AsyncPaymentsMessage; use super::dns_resolution::DNSResolverMessage; use super::messenger::CustomOnionMessageHandler; use super::offers::OffersMessage; -use crate::blinded_path::message::{BlindedMessagePath, ForwardTlvs, NextMessageHop, ReceiveTlvs}; +use crate::blinded_path::message::{ + BlindedMessagePath, DummyTlvs, ForwardTlvs, NextMessageHop, ReceiveTlvs, + UnauthenticatedDummyTlvs, +}; use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; @@ -111,6 +114,8 @@ impl LengthReadable for Packet { pub(super) enum Payload { /// This payload is for an intermediate hop. Forward(ForwardControlTlvs), + /// This payload is dummy, and is inteded to be peeled. + Dummy(DummyControlTlvs), /// This payload is for the final hop. Receive { control_tlvs: ReceiveControlTlvs, reply_path: Option, message: T }, } @@ -204,6 +209,11 @@ pub(super) enum ForwardControlTlvs { Unblinded(ForwardTlvs), } +pub(super) enum DummyControlTlvs { + /// See [`ForwardControlTlvs::Unblinded`] + Unblinded(DummyTlvs), +} + /// Receive control TLVs in their blinded and unblinded form. pub(super) enum ReceiveControlTlvs { /// See [`ForwardControlTlvs::Blinded`]. @@ -234,6 +244,10 @@ impl Writeable for (Payload, [u8; 32]) { let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs); _encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) }) }, + Payload::Dummy(DummyControlTlvs::Unblinded(control_tlvs)) => { + let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs); + _encode_varint_length_prefixed_tlv!(w, { (4, write_adapter, required) }) + }, Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs), reply_path, @@ -310,6 +324,9 @@ impl ReadableArgs<(Sh } Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs))) }, + Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Dummy(tlvs) }) => { + Ok(Payload::Dummy(DummyControlTlvs::Unblinded(tlvs))) + }, Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs) }) => { Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs), @@ -328,6 +345,8 @@ impl ReadableArgs<(Sh pub(crate) enum ControlTlvs { /// This onion message is intended to be forwarded. Forward(ForwardTlvs), + /// This onion message is a dummy, and is intended to be peeled. + Dummy(DummyTlvs), /// This onion message is intended to be received. Receive(ReceiveTlvs), } @@ -343,6 +362,7 @@ impl Readable for ControlTlvs { (4, next_node_id, option), (8, next_blinding_override, option), (65537, context, option), + (65539, authentication, option), }); let next_hop = match (short_channel_id, next_node_id) { @@ -353,7 +373,8 @@ impl Readable for ControlTlvs { }; let valid_fwd_fmt = next_hop.is_some(); - let valid_recv_fmt = next_hop.is_none() && next_blinding_override.is_none(); + let valid_recv_fmt = + next_hop.is_none() && next_blinding_override.is_none() && authentication.is_none(); let payload_fmt = if valid_fwd_fmt { ControlTlvs::Forward(ForwardTlvs { @@ -363,7 +384,10 @@ impl Readable for ControlTlvs { } else if valid_recv_fmt { ControlTlvs::Receive(ReceiveTlvs { context }) } else { - return Err(DecodeError::InvalidValue); + ControlTlvs::Dummy(DummyTlvs { + dummy_tlvs: UnauthenticatedDummyTlvs {}, + authentication: authentication.ok_or(DecodeError::InvalidValue)?, + }) }; Ok(payload_fmt) @@ -374,6 +398,7 @@ impl Writeable for ControlTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { match self { Self::Forward(tlvs) => tlvs.write(w), + Self::Dummy(tlvs) => tlvs.write(w), Self::Receive(tlvs) => tlvs.write(w), } } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index f90bfb97ef7..31b9a6b3cd4 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -25,11 +25,11 @@ use crate::events::bump_transaction::{Utxo, WalletSource}; #[cfg(any(test, feature = "_externalize_tests"))] use crate::ln::chan_utils::CommitmentTransaction; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{BaseMessageHandler, MessageSendEvent}; use crate::ln::script::ShutdownScript; use crate::ln::types::ChannelId; +use crate::ln::{channelmanager, inbound_payment}; use crate::ln::{msgs, wire}; use crate::offers::invoice::UnsignedBolt12Invoice; use crate::onion_message::messenger::{ @@ -324,8 +324,9 @@ pub struct TestMessageRouter<'a> { impl<'a> TestMessageRouter<'a> { pub fn new( network_graph: Arc>, entropy_source: &'a TestKeysInterface, + expanded_key: inbound_payment::ExpandedKey, ) -> Self { - Self { inner: DefaultMessageRouter::new(network_graph, entropy_source) } + Self { inner: DefaultMessageRouter::new(network_graph, entropy_source, expanded_key) } } } @@ -1487,7 +1488,7 @@ impl TestNodeSigner { impl NodeSigner for TestNodeSigner { fn get_inbound_payment_key(&self) -> ExpandedKey { - unreachable!() + ExpandedKey::new([42; 32]) } fn get_node_id(&self, recipient: Recipient) -> Result {