From 7002180261d495f15c12f9a341af1e7dbcac2990 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 15 Mar 2024 16:52:36 -0500 Subject: [PATCH 1/1] Generalize BlindedPath::introduction_node_id field Allow using either a node id or a directed short channel id in blinded paths. This allows for a more compact representation of blinded paths, which is advantageous for reducing offer QR code size. Follow-up commits will implement handling the directed short channel id case in OnionMessenger as it requires resolving the introduction node in MessageRouter. --- fuzz/src/router.rs | 4 +- lightning/src/blinded_path/message.rs | 6 +- lightning/src/blinded_path/mod.rs | 90 +++++++++++++++++--- lightning/src/ln/offers_tests.rs | 30 +++---- lightning/src/offers/invoice.rs | 4 +- lightning/src/offers/offer.rs | 12 +-- lightning/src/offers/refund.rs | 12 +-- lightning/src/offers/test_utils.rs | 6 +- lightning/src/onion_message/messenger.rs | 40 ++++++--- lightning/src/routing/router.rs | 101 ++++++++++++++--------- lightning/src/routing/scoring.rs | 4 +- 11 files changed, 212 insertions(+), 97 deletions(-) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index ad4373c4..afe02813 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::transaction::TxOut; -use lightning::blinded_path::{BlindedHop, BlindedPath}; +use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use lightning::chain::transaction::OutPoint; use lightning::ln::ChannelId; use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty}; @@ -363,7 +363,7 @@ pub fn do_test(data: &[u8], out: Out) { }); } (payinfo, BlindedPath { - introduction_node_id: hop.src_node_id, + introduction_node: IntroductionNode::NodeId(hop.src_node_id), blinding_point: dummy_pk, blinded_hops, }) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 967ef612..1d2d624c 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -3,7 +3,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; #[allow(unused_imports)] use crate::prelude::*; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; @@ -96,7 +96,7 @@ pub(crate) fn advance_path_by_one { - let mut next_node_id = match next_hop { + let next_node_id = match next_hop { NextHop::NodeId(pubkey) => pubkey, NextHop::ShortChannelId(_) => todo!(), }; @@ -108,7 +108,7 @@ pub(crate) fn advance_path_by_one Err(()) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 5d199269..daa9f033 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -29,11 +29,11 @@ use crate::prelude::*; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlindedPath { /// To send to a blinded path, the sender first finds a route to the unblinded - /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion + /// `introduction_node`, which can unblind its [`encrypted_payload`] to find out the onion /// message or payment's next hop and forward it along. /// /// [`encrypted_payload`]: BlindedHop::encrypted_payload - pub introduction_node_id: PublicKey, + pub introduction_node: IntroductionNode, /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion /// message or payment. /// @@ -43,6 +43,29 @@ pub struct BlindedPath { pub blinded_hops: Vec, } +/// The unblinded node in a [`BlindedPath`]. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum IntroductionNode { + /// The node id of the introduction node. + NodeId(PublicKey), + /// The short channel id of the channel leading to the introduction node. The [`Direction`] + /// identifies which side of the channel is the introduction node. + DirectedShortChannelId(Direction, u64), +} + +/// The side of a channel that is the [`IntroductionNode`] in a [`BlindedPath`]. [BOLT 7] defines +/// which nodes is which in the [`ChannelAnnouncement`] message. +/// +/// [BOLT 7]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message +/// [`ChannelAnnouncement`]: crate::ln::msgs::ChannelAnnouncement +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum Direction { + /// The lesser node id when compared lexicographically in ascending order. + NodeOne, + /// The greater node id when compared lexicographically in ascending order. + NodeTwo, +} + /// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to /// be encoded in the sender's onion packet. These hops cannot be identified by outside observers /// and thus can be used to hide the identity of the recipient. @@ -75,10 +98,10 @@ impl BlindedPath { if node_pks.is_empty() { return Err(()) } 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_id = node_pks[0]; + let introduction_node = IntroductionNode::NodeId(node_pks[0]); Ok(BlindedPath { - introduction_node_id, + 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(|_| ())?, }) @@ -112,6 +135,9 @@ impl BlindedPath { payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: &ES, secp_ctx: &Secp256k1 ) -> Result<(BlindedPayInfo, Self), ()> { + let introduction_node = IntroductionNode::NodeId( + intermediate_nodes.first().map_or(payee_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"); @@ -119,7 +145,7 @@ impl BlindedPath { intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta )?; Ok((blinded_payinfo, BlindedPath { - introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id), + introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: payment::blinded_hops( secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret @@ -127,18 +153,41 @@ impl BlindedPath { })) } - /// Returns the introduction [`NodeId`] of the blinded path. + /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e., + /// it is found in the network graph). pub fn public_introduction_node_id<'a>( &self, network_graph: &'a ReadOnlyNetworkGraph ) -> Option<&'a NodeId> { - let node_id = NodeId::from_pubkey(&self.introduction_node_id); - network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(pubkey); + network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + network_graph + .channel(*scid) + .map(|c| match direction { + Direction::NodeOne => &c.node_one, + Direction::NodeTwo => &c.node_two, + }) + }, + } } } impl Writeable for BlindedPath { fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.introduction_node_id.write(w)?; + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey.write(w)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => 0u8.write(w)?, + Direction::NodeTwo => 1u8.write(w)?, + } + scid.write(w)?; + }, + } + self.blinding_point.write(w)?; (self.blinded_hops.len() as u8).write(w)?; for hop in &self.blinded_hops { @@ -150,7 +199,17 @@ impl Writeable for BlindedPath { impl Readable for BlindedPath { fn read(r: &mut R) -> Result { - let introduction_node_id = Readable::read(r)?; + let mut first_byte: u8 = Readable::read(r)?; + let introduction_node = match first_byte { + 0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?), + 1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?), + 2|3 => { + use io::Read; + let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref()); + IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?) + }, + _ => return Err(DecodeError::InvalidValue), + }; let blinding_point = Readable::read(r)?; let num_hops: u8 = Readable::read(r)?; if num_hops == 0 { return Err(DecodeError::InvalidValue) } @@ -159,7 +218,7 @@ impl Readable for BlindedPath { blinded_hops.push(Readable::read(r)?); } Ok(BlindedPath { - introduction_node_id, + introduction_node, blinding_point, blinded_hops, }) @@ -171,3 +230,12 @@ impl_writeable!(BlindedHop, { encrypted_payload }); +impl Direction { + /// Returns the [`NodeId`] from the inputs corresponding to the direction. + pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId { + match self { + Direction::NodeOne => core::cmp::min(node_a, node_b), + Direction::NodeTwo => core::cmp::max(node_a, node_b), + } + } +} diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index e75bd2c7..4c8c1ecb 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -42,7 +42,7 @@ use bitcoin::network::constants::Network; use core::time::Duration; -use crate::blinded_path::BlindedPath; +use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self}; use crate::ln::functional_test_utils::*; @@ -260,8 +260,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_ne!(path.introduction_node_id, bob_id); - assert_ne!(path.introduction_node_id, charlie_id); + assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); } // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only. @@ -275,7 +275,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } } @@ -325,7 +325,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id()); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id())); } } @@ -374,7 +374,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), alice_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } let payment_id = PaymentId([1; 32]); @@ -395,7 +395,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), david_id); - assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id); + assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(charlie_id)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -408,7 +408,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } route_bolt12_payment(david, &[charlie, bob, alice], &invoice); @@ -469,7 +469,7 @@ 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_id, charlie_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); } expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -488,7 +488,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } route_bolt12_payment(david, &[charlie, bob, alice], &invoice); @@ -522,7 +522,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), alice_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } let payment_id = PaymentId([1; 32]); @@ -535,7 +535,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), bob_id); - assert_eq!(reply_path.unwrap().introduction_node_id, bob_id); + assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(bob_id)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -545,7 +545,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } route_bolt12_payment(bob, &[alice], &invoice); @@ -585,7 +585,7 @@ 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_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -599,7 +599,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } route_bolt12_payment(bob, &[alice], &invoice); diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index f2fb3879..fbfcffe2 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1455,7 +1455,7 @@ mod tests { use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; @@ -1804,7 +1804,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index f7b75138..d390552f 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -1078,7 +1078,7 @@ mod tests { use bitcoin::secp256k1::Secp256k1; use core::num::NonZeroU64; use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; @@ -1249,7 +1249,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, @@ -1395,7 +1395,7 @@ mod tests { fn builds_offer_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1403,7 +1403,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1585,7 +1585,7 @@ mod tests { fn parses_offer_with_paths() { let offer = OfferBuilder::new("foo".into(), pubkey(42)) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1593,7 +1593,7 @@ mod tests { ], }) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 16014cd3..03253fb6 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -907,7 +907,7 @@ mod tests { use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures}; @@ -1062,7 +1062,7 @@ mod tests { let payment_id = PaymentId([1; 32]); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1151,7 +1151,7 @@ mod tests { fn builds_refund_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1159,7 +1159,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1368,7 +1368,7 @@ mod tests { let past_expiry = Duration::from_secs(0); let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1376,7 +1376,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index b4329803..149ba15c 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -13,7 +13,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; use core::time::Duration; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::EntropySource; use crate::ln::PaymentHash; use crate::ln::features::BlindedHopFeatures; @@ -69,7 +69,7 @@ pub(super) fn privkey(byte: u8) -> SecretKey { pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -77,7 +77,7 @@ pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 85e19cc5..5334d6db 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,7 +15,7 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; -use crate::blinded_path::BlindedPath; +use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, NextHop, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; @@ -444,7 +444,12 @@ impl Destination { fn first_node(&self) -> PublicKey { match self { Destination::Node(node_id) => *node_id, - Destination::BlindedPath(BlindedPath { introduction_node_id: node_id, .. }) => *node_id, + Destination::BlindedPath(BlindedPath { introduction_node, .. }) => { + match introduction_node { + IntroductionNode::NodeId(pubkey) => *pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + } + }, } } } @@ -569,9 +574,13 @@ where // advance the blinded path by 1 hop so the second hop is the new introduction node. if intermediate_nodes.len() == 0 { if let Destination::BlindedPath(ref mut blinded_path) = destination { + let introduction_node_id = match blinded_path.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + }; let our_node_id = node_signer.get_node_id(Recipient::Node) .map_err(|()| SendError::GetNodeIdFailed)?; - if blinded_path.introduction_node_id == our_node_id { + if introduction_node_id == our_node_id { advance_path_by_one(blinded_path, node_signer, &secp_ctx) .map_err(|()| SendError::BlindedPathAdvanceFailed)?; } @@ -583,10 +592,14 @@ where let (first_node_id, blinding_point) = if let Some(first_node_id) = intermediate_nodes.first() { (*first_node_id, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)) } else { - match destination { - Destination::Node(pk) => (pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)), - Destination::BlindedPath(BlindedPath { introduction_node_id, blinding_point, .. }) => - (introduction_node_id, blinding_point), + match &destination { + Destination::Node(pk) => (*pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)), + Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, .. }) => { + match introduction_node { + IntroductionNode::NodeId(pubkey) => (*pubkey, *blinding_point), + IntroductionNode::DirectedShortChannelId(..) => todo!(), + } + } } }; let (packet_payloads, packet_keys) = packet_payloads_and_keys( @@ -1136,9 +1149,16 @@ fn packet_payloads_and_keys (None, 0), + Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, blinded_hops }) => { + let introduction_node_id = match introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + }; + (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) + }, + }; let num_unblinded_hops = num_hops - num_blinded_hops; let mut unblinded_path_idx = 0; diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 576c8a30..f1901d7c 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -11,7 +11,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode}; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::ln::PaymentHash; use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA}; @@ -1735,8 +1735,20 @@ impl<'a> fmt::Display for LoggedCandidateHop<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { - "blinded route hint with introduction node id ".fmt(f)?; - hint.1.introduction_node_id.fmt(f)?; + "blinded route hint with introduction node ".fmt(f)?; + match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => write!(f, "id {}", pubkey)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => { + write!(f, "one on channel with SCID {}", scid)?; + }, + Direction::NodeTwo => { + write!(f, "two on channel with SCID {}", scid)?; + }, + } + } + } " and blinding point ".fmt(f)?; hint.1.blinding_point.fmt(f) }, @@ -2530,12 +2542,27 @@ where L::Target: Logger { // in the regular network graph. let source_node_id = match hint.1.public_introduction_node_id(network_graph) { Some(node_id) => node_id, - None => { - let node_id = NodeId::from_pubkey(&hint.1.introduction_node_id); - match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { - Some(node_id) => node_id, - None => continue, - } + None => match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(&pubkey); + match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { + Some(node_id) => node_id, + None => continue, + } + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + let first_hop = first_hop_targets.iter().find(|(_, channels)| + channels + .iter() + .any(|details| Some(*scid) == details.get_outbound_payment_scid()) + ); + match first_hop { + Some((counterparty_node_id, _)) => { + direction.select_node_id(&our_node_id, counterparty_node_id) + }, + None => continue, + } + }, }, }; if our_node_id == *source_node_id { continue } @@ -3249,7 +3276,7 @@ fn build_route_from_hops_internal( #[cfg(test)] mod tests { - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; use crate::routing::utxo::UtxoResult; use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features, @@ -5116,7 +5143,7 @@ mod tests { // MPP to a 1-hop blinded path for nodes[2] let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }], }; @@ -5134,18 +5161,18 @@ mod tests { // MPP to 3 2-hop blinded paths let mut blinded_path_node_0 = blinded_path.clone(); - blinded_path_node_0.introduction_node_id = nodes[0]; + blinded_path_node_0.introduction_node = IntroductionNode::NodeId(nodes[0]); blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone()); let mut node_0_payinfo = blinded_payinfo.clone(); node_0_payinfo.htlc_maximum_msat = 50_000; let mut blinded_path_node_7 = blinded_path_node_0.clone(); - blinded_path_node_7.introduction_node_id = nodes[7]; + blinded_path_node_7.introduction_node = IntroductionNode::NodeId(nodes[7]); let mut node_7_payinfo = blinded_payinfo.clone(); node_7_payinfo.htlc_maximum_msat = 60_000; let mut blinded_path_node_1 = blinded_path_node_0.clone(); - blinded_path_node_1.introduction_node_id = nodes[1]; + blinded_path_node_1.introduction_node = IntroductionNode::NodeId(nodes[1]); let mut node_1_payinfo = blinded_payinfo.clone(); node_1_payinfo.htlc_maximum_msat = 180_000; @@ -7231,7 +7258,7 @@ mod tests { // Make sure this works for blinded route hints. let blinded_path = BlindedPath { - introduction_node_id: intermed_node_id, + introduction_node: IntroductionNode::NodeId(intermed_node_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] }, @@ -7265,7 +7292,7 @@ mod tests { #[test] fn blinded_route_ser() { let blinded_path_1 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(42), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(42)), blinding_point: ln_test_utils::pubkey(43), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() }, @@ -7273,7 +7300,7 @@ mod tests { ], }; let blinded_path_2 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(46), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(46)), blinding_point: ln_test_utils::pubkey(47), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() }, @@ -7332,7 +7359,7 @@ mod tests { // account for the blinded tail's final amount_msat. let mut inflight_htlcs = InFlightHtlcs::new(); let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(48), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }], }; @@ -7347,7 +7374,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7371,7 +7398,7 @@ mod tests { fn blinded_path_cltv_shadow_offset() { // Make sure we add a shadow offset when sending to blinded paths. let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(44), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }, @@ -7389,7 +7416,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7431,7 +7458,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: Vec::with_capacity(num_blinded_hops), }; @@ -7489,7 +7516,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut invalid_blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] }, @@ -7505,7 +7532,7 @@ mod tests { }; let mut invalid_blinded_path_2 = invalid_blinded_path.clone(); - invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45); + invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(45)); let payment_params = PaymentParameters::blinded(vec![ (blinded_payinfo.clone(), invalid_blinded_path.clone()), (blinded_payinfo.clone(), invalid_blinded_path_2)]); @@ -7519,7 +7546,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = our_id; + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(our_id); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, @@ -7531,7 +7558,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46); + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(46)); invalid_blinded_path.blinded_hops.clear(); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); @@ -7560,7 +7587,7 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path_1 = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7657,7 +7684,7 @@ mod tests { get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7726,7 +7753,7 @@ mod tests { 18446744073709551615)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7782,7 +7809,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7801,7 +7828,7 @@ mod tests { (blinded_payinfo.clone(), blinded_path.clone()), (blinded_payinfo.clone(), blinded_path.clone()), ]; - blinded_hints[1].1.introduction_node_id = nodes[6]; + blinded_hints[1].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7834,7 +7861,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7858,7 +7885,7 @@ mod tests { blinded_hints[1].0.htlc_minimum_msat = 21_7020_5185_1423_0019; blinded_hints[1].0.htlc_maximum_msat = 1844_6744_0737_0955_1615; - blinded_hints[2].1.introduction_node_id = nodes[6]; + blinded_hints[2].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7905,7 +7932,7 @@ mod tests { let htlc_min = 2_5165_8240; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7985,7 +8012,7 @@ mod tests { let htlc_mins = [1_4392, 19_7401, 1027, 6_5535]; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8086,7 +8113,7 @@ mod tests { cltv_expiry_delta: 10, features: BlindedHopFeatures::empty(), }, BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8136,7 +8163,7 @@ mod tests { let htlc_mins = [49_0000, 1125_0000]; let payment_params = { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 4850479b..4cb9144d 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -2152,7 +2152,7 @@ impl Readable for ChannelLiquidity { #[cfg(test)] mod tests { use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer}; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::util::config::UserConfig; use crate::ln::channelmanager; @@ -3567,7 +3567,7 @@ mod tests { let mut path = payment_path_for_amount(768); let recipient_hop = path.hops.pop().unwrap(); let blinded_path = BlindedPath { - introduction_node_id: path.hops.last().as_ref().unwrap().pubkey, + introduction_node: IntroductionNode::NodeId(path.hops.last().as_ref().unwrap().pubkey), blinding_point: test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() } -- 2.30.2