diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index b3cf867d6e3..5004580231e 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -31,6 +31,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{BlockHash, WPubkeyHash}; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::blinded_path::payment::ReceiveTlvs; use lightning::chain; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch}; @@ -119,7 +120,7 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index d07def30ff9..adb997ab22c 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -28,6 +28,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash}; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::blinded_path::payment::ReceiveTlvs; use lightning::chain; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen}; @@ -157,7 +158,7 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 414ce1cdd1c..1aad5bfd897 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self} use crate::utils::test_logger; use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; @@ -73,9 +74,19 @@ fn build_response( invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1 ) -> Result { let entropy_source = Randomness {}; + let intermediate_nodes = [ + [ + ForwardNode { node_id: pubkey(43), short_channel_id: None }, + ForwardNode { node_id: pubkey(44), short_channel_id: None }, + ], + [ + ForwardNode { node_id: pubkey(45), short_channel_id: None }, + ForwardNode { node_id: pubkey(46), short_channel_id: None }, + ], + ]; let paths = vec![ - BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), - BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(), ]; let payinfo = vec![ diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 19ba4cb6aac..4e563e28a46 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -7,6 +7,7 @@ use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::schnorr; use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; +use lightning::blinded_path::message::ForwardNode; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; @@ -88,7 +89,7 @@ impl MessageRouter for TestMessageRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 14b136ec35b..660ad5e2c1d 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; @@ -62,9 +63,19 @@ fn build_response( refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1 ) -> Result { let entropy_source = Randomness {}; + let intermediate_nodes = [ + [ + ForwardNode { node_id: pubkey(43), short_channel_id: None }, + ForwardNode { node_id: pubkey(44), short_channel_id: None }, + ], + [ + ForwardNode { node_id: pubkey(45), short_channel_id: None }, + ForwardNode { node_id: pubkey(46), short_channel_id: None }, + ], + ]; let paths = vec![ - BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), - BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(), ]; let payinfo = vec![ diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 9a282a5f87d..1f3f5a1fa38 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -1,3 +1,16 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Data structures and methods for constructing [`BlindedPath`]s to send a message over. +//! +//! [`BlindedPath`]: crate::blinded_path::BlindedPath + use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; #[allow(unused_imports)] @@ -16,6 +29,17 @@ use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer} use core::mem; use core::ops::Deref; +/// An intermediate node, and possibly a short channel id leading to the next node. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct ForwardNode { + /// This node's pubkey. + pub node_id: PublicKey, + /// The channel between `node_id` and the next hop. If set, the constructed [`BlindedHop`]'s + /// `encrypted_payload` will use this instead of the next [`ForwardNode::node_id`] for a more + /// compact representation. + pub short_channel_id: Option, +} + /// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded /// route, they are encoded into [`BlindedHop::encrypted_payload`]. pub(crate) struct ForwardTlvs { @@ -60,17 +84,24 @@ impl Writeable for ReceiveTlvs { } } -/// Construct blinded onion message hops for the given `unblinded_path`. +/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. pub(super) fn blinded_hops( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey + secp_ctx: &Secp256k1, intermediate_nodes: &[ForwardNode], recipient_node_id: PublicKey, + session_priv: &SecretKey ) -> Result, secp256k1::Error> { - let blinded_tlvs = unblinded_path.iter() + let pks = intermediate_nodes.iter().map(|node| &node.node_id) + .chain(core::iter::once(&recipient_node_id)); + let tlvs = pks.clone() .skip(1) // The first node's TLVs contains the next node's pubkey - .map(|pk| ForwardTlvs { next_hop: NextMessageHop::NodeId(*pk), next_blinding_override: None }) - .map(|tlvs| ControlTlvs::Forward(tlvs)) + .zip(intermediate_nodes.iter().map(|node| node.short_channel_id)) + .map(|(pubkey, scid)| match scid { + Some(scid) => NextMessageHop::ShortChannelId(scid), + None => NextMessageHop::NodeId(*pubkey), + }) + .map(|next_hop| ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })) .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None }))); - utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv) + utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) } // Advance the blinded onion message path by one hop, so make the second hop into the new diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 7f4cfe2e300..2d3d085bddf 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -10,7 +10,7 @@ //! Creating blinded paths and related utilities live here. pub mod payment; -pub(crate) mod message; +pub mod message; pub(crate) mod utils; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; @@ -21,6 +21,7 @@ use crate::offers::invoice::BlindedPayInfo; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; +use crate::util::scid_utils; use crate::io; use crate::prelude::*; @@ -124,7 +125,7 @@ impl BlindedPath { pub fn one_hop_for_message( recipient_node_id: PublicKey, entropy_source: ES, secp_ctx: &Secp256k1 ) -> Result where ES::Target: EntropySource { - Self::new_for_message(&[recipient_node_id], entropy_source, secp_ctx) + Self::new_for_message(&[], recipient_node_id, entropy_source, secp_ctx) } /// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node @@ -133,17 +134,21 @@ impl BlindedPath { /// 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_for_message( - node_pks: &[PublicKey], entropy_source: ES, secp_ctx: &Secp256k1 + intermediate_nodes: &[message::ForwardNode], recipient_node_id: PublicKey, + entropy_source: ES, secp_ctx: &Secp256k1 ) -> Result where ES::Target: EntropySource { - if node_pks.is_empty() { return Err(()) } + let introduction_node = IntroductionNode::NodeId( + intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id) + ); let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); - let introduction_node = IntroductionNode::NodeId(node_pks[0]); Ok(BlindedPath { introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), - blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, + blinded_hops: message::blinded_hops( + secp_ctx, intermediate_nodes, recipient_node_id, &blinding_secret, + ).map_err(|_| ())?, }) } @@ -213,6 +218,35 @@ impl BlindedPath { }, } } + + /// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed + /// short channel id from a channel in `network_graph` leading to the introduction node. + /// + /// While this may result in a smaller encoding, there is a trade off in that the path may + /// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore, + /// calling this may not be suitable for long-lived blinded paths. + pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) { + if let IntroductionNode::NodeId(pubkey) = &self.introduction_node { + let node_id = NodeId::from_pubkey(pubkey); + if let Some(node_info) = network_graph.node(&node_id) { + if let Some((scid, channel_info)) = node_info + .channels + .iter() + .filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info))) + .min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid)) + { + let direction = if node_id == channel_info.node_one { + Direction::NodeOne + } else { + debug_assert_eq!(node_id, channel_info.node_two); + Direction::NodeTwo + }; + self.introduction_node = + IntroductionNode::DirectedShortChannelId(direction, scid); + } + } + } + } } impl Writeable for BlindedPath { diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index dfe89e0ebc6..5e44c792d33 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -1,3 +1,12 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + //! Data structures and methods for constructing [`BlindedPath`]s to send a payment over. //! //! [`BlindedPath`]: crate::blinded_path::BlindedPath diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 5fbdb595b77..f6b7630bf9c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1403,7 +1403,7 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// Either the height at which this channel was created or the height at which it was last /// serialized if it was serialized by versions prior to 0.0.103. /// We use this to close if funding is never broadcasted. - channel_creation_height: u32, + pub(super) channel_creation_height: u32, counterparty_dust_limit_satoshis: u64, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f2b3bf15d39..a382b38ba6d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -32,6 +32,7 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::{secp256k1, Sequence}; use crate::blinded_path::{BlindedPath, NodeIdLookUp}; +use crate::blinded_path::message::ForwardNode; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, ReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; @@ -8996,8 +8997,16 @@ where let peers = self.per_peer_state.read().unwrap() .iter() - .filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages()) - .map(|(node_id, _)| *node_id) + .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) + .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) + .map(|(node_id, peer)| ForwardNode { + node_id: *node_id, + short_channel_id: peer.channel_by_id + .iter() + .filter(|(_, channel)| channel.context().is_usable()) + .min_by_key(|(_, channel)| channel.context().channel_creation_height) + .and_then(|(_, channel)| channel.context().get_short_channel_id()), + }) .collect::>(); self.router diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 6ae87e6001d..016afd32a09 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -41,6 +41,7 @@ //! blinded paths are used. use bitcoin::network::constants::Network; +use bitcoin::secp256k1::PublicKey; use core::time::Duration; use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; @@ -133,6 +134,12 @@ fn announce_node_address<'a, 'b, 'c>( } } +fn resolve_introduction_node<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &BlindedPath) -> PublicKey { + path.public_introduction_node_id(&node.network_graph.read_only()) + .and_then(|node_id| node_id.as_pubkey().ok()) + .unwrap() +} + fn route_bolt12_payment<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice ) { @@ -178,11 +185,11 @@ fn claim_bolt12_payment<'a, 'b, 'c>( fn extract_invoice_request<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, message: &OnionMessage -) -> (InvoiceRequest, Option) { +) -> (InvoiceRequest, BlindedPath) { match node.onion_messenger.peel_onion_message(message) { Ok(PeeledOnion::Receive(message, _, reply_path)) => match message { ParsedOnionMessageContents::Offers(offers_message) => match offers_message { - OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path), + OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path.unwrap()), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), }, @@ -273,8 +280,9 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), Some(bob_id)); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id)); - assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); + let introduction_node_id = resolve_introduction_node(david, &path); + assert_ne!(introduction_node_id, bob_id); + assert_ne!(introduction_node_id, charlie_id); } // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only. @@ -288,7 +296,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), Some(bob_id)); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + let introduction_node_id = resolve_introduction_node(david, &path); + assert_eq!(introduction_node_id, bob_id); } } @@ -338,7 +347,8 @@ fn prefers_more_connected_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), Some(bob_id)); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id())); + let introduction_node_id = resolve_introduction_node(david, &path); + assert_eq!(introduction_node_id, nodes[4].node.get_our_node_id()); } } @@ -388,7 +398,9 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), Some(alice_id)); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + let introduction_node_id = resolve_introduction_node(david, &path); + assert_eq!(introduction_node_id, bob_id); + assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); } let payment_id = PaymentId([1; 32]); @@ -415,9 +427,11 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { payer_note_truncated: None, }, }); + let introduction_node_id = resolve_introduction_node(alice, &reply_path); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), david_id); - assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(charlie_id)); + assert_eq!(introduction_node_id, charlie_id); + assert!(matches!(reply_path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -489,7 +503,9 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { assert_ne!(refund.payer_id(), david_id); assert!(!refund.paths().is_empty()); for path in refund.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); + let introduction_node_id = resolve_introduction_node(alice, &path); + assert_eq!(introduction_node_id, charlie_id); + assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); } expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -545,7 +561,9 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), Some(alice_id)); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); + let introduction_node_id = resolve_introduction_node(bob, &path); + assert_eq!(introduction_node_id, alice_id); + assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); } let payment_id = PaymentId([1; 32]); @@ -564,9 +582,11 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { payer_note_truncated: None, }, }); + let introduction_node_id = resolve_introduction_node(alice, &reply_path); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), bob_id); - assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(bob_id)); + assert_eq!(introduction_node_id, bob_id); + assert!(matches!(reply_path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -614,7 +634,9 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { assert_ne!(refund.payer_id(), bob_id); assert!(!refund.paths().is_empty()); for path in refund.paths() { - assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + let introduction_node_id = resolve_introduction_node(alice, &path); + assert_eq!(introduction_node_id, bob_id); + assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..))); } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 5a824b9162f..b10a87866a2 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -1860,6 +1860,9 @@ mod bolt12_tests { // with blinded path via Bob (0x424242...), blinding 020202... "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + // ... and with sciddir introduction node + "lno1pgx9getnwss8vetrw3hhyucs3yqqqqqqqqqqqqp2qgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqyqqqqqqqqqqqqqqqqqqqqqqqqqqqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqgzyg3zyg3zyg3z93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + // ... and with second blinded path via Carol (0x434343...), blinding 020202... "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 764a2bdcbdc..029038a9fe7 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -10,6 +10,7 @@ //! Onion message testing and test utilities live here. use crate::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; +use crate::blinded_path::message::ForwardNode; use crate::events::{Event, EventsProvider}; use crate::ln::features::{ChannelFeatures, InitFeatures}; use crate::ln::msgs::{self, DecodeError, OnionMessageHandler}; @@ -327,7 +328,7 @@ fn one_blinded_hop() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id], &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedPath::new_for_message(&[], nodes[1].node_id, &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response); @@ -340,7 +341,8 @@ fn two_unblinded_two_blinded() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[3].node_id, nodes[4].node_id], &*nodes[4].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[3].node_id, short_channel_id: None }]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[4].node_id, &*nodes[4].entropy_source, &secp_ctx).unwrap(); let path = OnionMessagePath { intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id], destination: Destination::BlindedPath(blinded_path), @@ -358,7 +360,11 @@ fn three_blinded_hops() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id, nodes[3].node_id], &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[3].node_id, &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); @@ -391,7 +397,11 @@ fn we_are_intro_node() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[0].node_id, nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[0].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg.clone(), destination, None).unwrap(); @@ -399,7 +409,8 @@ fn we_are_intro_node() { pass_along_path(&nodes); // Try with a two-hop blinded path where we are the introduction node. - let blinded_path = BlindedPath::new_for_message(&[nodes[0].node_id, nodes[1].node_id], &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[0].node_id, short_channel_id: None }]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[1].node_id, &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response); @@ -414,7 +425,8 @@ fn invalid_blinded_path_error() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let mut blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; + let mut blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx).unwrap(); blinded_path.blinded_hops.clear(); let destination = Destination::BlindedPath(blinded_path); let err = nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap_err(); @@ -433,7 +445,11 @@ fn reply_path() { destination: Destination::Node(nodes[3].node_id), first_node_addresses: None, }; - let reply_path = BlindedPath::new_for_message(&[nodes[2].node_id, nodes[1].node_id, nodes[0].node_id], &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let reply_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[0].node_id, &*nodes[0].entropy_source, &secp_ctx).unwrap(); nodes[0].messenger.send_onion_message_using_path(path, test_msg.clone(), Some(reply_path)).unwrap(); nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request); pass_along_path(&nodes); @@ -443,9 +459,17 @@ fn reply_path() { pass_along_path(&nodes); // Destination::BlindedPath - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id, nodes[3].node_id], &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[3].node_id, &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); - let reply_path = BlindedPath::new_for_message(&[nodes[2].node_id, nodes[1].node_id, nodes[0].node_id], &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let reply_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[0].node_id, &*nodes[0].entropy_source, &secp_ctx).unwrap(); nodes[0].messenger.send_onion_message(test_msg, destination, Some(reply_path)).unwrap(); nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request); @@ -525,8 +549,9 @@ fn requests_peer_connection_for_buffered_messages() { let secp_ctx = Secp256k1::new(); add_channel_to_graph(&nodes[0], &nodes[1], &secp_ctx, 42); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); @@ -562,8 +587,9 @@ fn drops_buffered_messages_waiting_for_peer_connection() { let secp_ctx = Secp256k1::new(); add_channel_to_graph(&nodes[0], &nodes[1], &secp_ctx, 42); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); @@ -611,8 +637,9 @@ fn intercept_offline_peer_oms() { let message = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index b10bd185100..fc3eead1e4f 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -16,7 +16,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use crate::blinded_path::{BlindedPath, IntroductionNode, NextMessageHop, NodeIdLookUp}; -use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::message::{advance_path_by_one, ForwardNode, ForwardTlvs, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; use crate::sign::{EntropySource, NodeSigner, Recipient}; @@ -71,6 +71,7 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # use bitcoin::hashes::hex::FromHex; /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; /// # use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; +/// # use lightning::blinded_path::message::ForwardNode; /// # use lightning::sign::{EntropySource, KeysManager}; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; /// # use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath, OnionMessenger}; @@ -97,7 +98,7 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # }) /// # } /// # fn create_blinded_paths( -/// # &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1 +/// # &self, _recipient: PublicKey, _peers: Vec, _secp_ctx: &Secp256k1 /// # ) -> Result, ()> { /// # unreachable!() /// # } @@ -145,8 +146,11 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// /// // Create a blinded path to yourself, for someone to send an onion message to. /// # let your_node_id = hop_node_id1; -/// let hops = [hop_node_id3, hop_node_id4, your_node_id]; -/// let blinded_path = BlindedPath::new_for_message(&hops, &keys_manager, &secp_ctx).unwrap(); +/// let hops = [ +/// ForwardNode { node_id: hop_node_id3, short_channel_id: None }, +/// ForwardNode { node_id: hop_node_id4, short_channel_id: None }, +/// ]; +/// let blinded_path = BlindedPath::new_for_message(&hops, your_node_id, &keys_manager, &secp_ctx).unwrap(); /// /// // Send a custom onion message to a blinded path. /// let destination = Destination::BlindedPath(blinded_path); @@ -337,7 +341,7 @@ pub trait MessageRouter { fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()>; } @@ -404,7 +408,7 @@ where fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PATHS: usize = 3; @@ -417,13 +421,13 @@ where let is_recipient_announced = network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); - let mut peer_info = peers.iter() + let mut peer_info = peers.into_iter() // Limit to peers with announced channels - .filter_map(|pubkey| + .filter_map(|peer| network_graph - .node(&NodeId::from_pubkey(pubkey)) + .node(&NodeId::from_pubkey(&peer.node_id)) .filter(|info| info.channels.len() >= MIN_PEER_CHANNELS) - .map(|info| (*pubkey, info.is_tor_only(), info.channels.len())) + .map(|info| (peer, info.is_tor_only(), info.channels.len())) ) // Exclude Tor-only nodes when the recipient is announced. .filter(|(_, is_tor_only, _)| !(*is_tor_only && is_recipient_announced)) @@ -435,12 +439,13 @@ where }); let paths = peer_info.into_iter() - .map(|(pubkey, _, _)| vec![pubkey, recipient]) - .map(|node_pks| BlindedPath::new_for_message(&node_pks, &*self.entropy_source, secp_ctx)) + .map(|(peer, _, _)| { + BlindedPath::new_for_message(&[peer], recipient, &*self.entropy_source, secp_ctx) + }) .take(MAX_PATHS) .collect::, _>>(); - match paths { + let mut paths = match paths { Ok(paths) if !paths.is_empty() => Ok(paths), _ => { if is_recipient_announced { @@ -450,7 +455,12 @@ where Err(()) } }, + }?; + for path in &mut paths { + path.use_compact_introduction_node(&network_graph); } + + Ok(paths) } } diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 95c03c4431e..b4b238e820b 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -12,7 +12,8 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode}; -use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; +use crate::blinded_path::message; +use crate::blinded_path::payment::{ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs, self}; use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA, RecipientOnionFields}; use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; @@ -122,7 +123,7 @@ impl> + Clone, L: Deref, ES: Deref, S: Deref, max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta, htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0), }; - Some(ForwardNode { + Some(payment::ForwardNode { tlvs: ForwardTlvs { short_channel_id, payment_relay, @@ -171,7 +172,7 @@ impl< G: Deref> + Clone, L: Deref, ES: Deref, S: Deref, fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification > ( - &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { self.message_router.create_blinded_paths(recipient, peers, secp_ctx) } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 9ff5d76ef8d..92d43193e30 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -8,6 +8,7 @@ // licenses. use crate::blinded_path::BlindedPath; +use crate::blinded_path::message::ForwardNode; use crate::blinded_path::payment::ReceiveTlvs; use crate::chain; use crate::chain::WatchedOutput; @@ -246,7 +247,7 @@ impl<'a> MessageRouter for TestRouter<'a> { fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { self.router.create_blinded_paths(recipient, peers, secp_ctx) } @@ -281,7 +282,7 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { } fn create_blinded_paths( - &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { self.inner.create_blinded_paths(recipient, peers, secp_ctx) }