Skip to content

Commit eda709f

Browse files
committed
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.
1 parent a2f2735 commit eda709f

File tree

11 files changed

+210
-97
lines changed

11 files changed

+210
-97
lines changed

fuzz/src/router.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::ChainHash;
1111
use bitcoin::blockdata::script::Builder;
1212
use bitcoin::blockdata::transaction::TxOut;
1313

14-
use lightning::blinded_path::{BlindedHop, BlindedPath};
14+
use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
1515
use lightning::chain::transaction::OutPoint;
1616
use lightning::ln::ChannelId;
1717
use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty};
@@ -362,7 +362,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
362362
});
363363
}
364364
(payinfo, BlindedPath {
365-
introduction_node_id: hop.src_node_id,
365+
introduction_node: IntroductionNode::NodeId(hop.src_node_id),
366366
blinding_point: dummy_pk,
367367
blinded_hops,
368368
})

lightning/src/blinded_path/message.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
22

3-
use crate::blinded_path::{BlindedHop, BlindedPath};
3+
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
44
use crate::blinded_path::utils;
55
use crate::io;
66
use crate::io::Cursor;
@@ -94,7 +94,7 @@ pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::
9494
Ok(ChaChaPolyReadAdapter {
9595
readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override })
9696
}) => {
97-
let mut next_node_id = match next_hop {
97+
let next_node_id = match next_hop {
9898
NextHop::NodeId(pubkey) => pubkey,
9999
NextHop::ShortChannelId(_) => todo!(),
100100
};
@@ -106,7 +106,7 @@ pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::
106106
}
107107
};
108108
mem::swap(&mut path.blinding_point, &mut new_blinding_point);
109-
mem::swap(&mut path.introduction_node_id, &mut next_node_id);
109+
path.introduction_node = IntroductionNode::NodeId(next_node_id);
110110
Ok(())
111111
},
112112
_ => Err(())

lightning/src/blinded_path/mod.rs

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ use crate::prelude::*;
2929
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
3030
pub struct BlindedPath {
3131
/// To send to a blinded path, the sender first finds a route to the unblinded
32-
/// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
32+
/// `introduction_node`, which can unblind its [`encrypted_payload`] to find out the onion
3333
/// message or payment's next hop and forward it along.
3434
///
3535
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
36-
pub introduction_node_id: PublicKey,
36+
pub introduction_node: IntroductionNode,
3737
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
3838
/// message or payment.
3939
///
@@ -43,6 +43,29 @@ pub struct BlindedPath {
4343
pub blinded_hops: Vec<BlindedHop>,
4444
}
4545

46+
/// The unblinded node in a [`BlindedPath`].
47+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
48+
pub enum IntroductionNode {
49+
/// The node id of the introduction node.
50+
NodeId(PublicKey),
51+
/// The short channel id of the channel leading to the introduction node. The [`Direction`]
52+
/// identifies which side of the channel is the introduction node.
53+
DirectedShortChannelId(Direction, u64),
54+
}
55+
56+
/// The side of a channel that is the [`IntroductionNode`] in a [`BlindedPath`]. [BOLT 7] defines
57+
/// which nodes is which in the [`ChannelAnnouncement`] message.
58+
///
59+
/// [BOLT 7]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message
60+
/// [`ChannelAnnouncement`]: crate::ln::msgs::ChannelAnnouncement
61+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
62+
pub enum Direction {
63+
/// The lesser node id when compared lexicographically in ascending order.
64+
NodeOne,
65+
/// The greater node id when compared lexicographically in ascending order.
66+
NodeTwo,
67+
}
68+
4669
/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
4770
/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
4871
/// and thus can be used to hide the identity of the recipient.
@@ -75,10 +98,10 @@ impl BlindedPath {
7598
if node_pks.is_empty() { return Err(()) }
7699
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
77100
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
78-
let introduction_node_id = node_pks[0];
101+
let introduction_node = IntroductionNode::NodeId(node_pks[0]);
79102

80103
Ok(BlindedPath {
81-
introduction_node_id,
104+
introduction_node,
82105
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
83106
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
84107
})
@@ -112,33 +135,58 @@ impl BlindedPath {
112135
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
113136
entropy_source: &ES, secp_ctx: &Secp256k1<T>
114137
) -> Result<(BlindedPayInfo, Self), ()> {
138+
let introduction_node = IntroductionNode::NodeId(
139+
intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id)
140+
);
115141
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
116142
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
117143

118144
let blinded_payinfo = payment::compute_payinfo(
119145
intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
120146
)?;
121147
Ok((blinded_payinfo, BlindedPath {
122-
introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id),
148+
introduction_node,
123149
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
124150
blinded_hops: payment::blinded_hops(
125151
secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
126152
).map_err(|_| ())?,
127153
}))
128154
}
129155

130-
/// Returns the introduction [`NodeId`] of the blinded path.
156+
/// Returns the introduction [`NodeId`] of the blinded path, if found in the network graph.
131157
pub fn introduction_node_id<'a>(
132158
&self, network_graph: &'a ReadOnlyNetworkGraph
133159
) -> Option<&'a NodeId> {
134-
let node_id = NodeId::from_pubkey(&self.introduction_node_id);
135-
network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key)
160+
match &self.introduction_node {
161+
IntroductionNode::NodeId(pubkey) => {
162+
let node_id = NodeId::from_pubkey(pubkey);
163+
network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key)
164+
},
165+
IntroductionNode::DirectedShortChannelId(direction, scid) => {
166+
network_graph
167+
.channel(*scid)
168+
.map(|c| match direction {
169+
Direction::NodeOne => &c.node_one,
170+
Direction::NodeTwo => &c.node_two,
171+
})
172+
},
173+
}
136174
}
137175
}
138176

139177
impl Writeable for BlindedPath {
140178
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
141-
self.introduction_node_id.write(w)?;
179+
match &self.introduction_node {
180+
IntroductionNode::NodeId(pubkey) => pubkey.write(w)?,
181+
IntroductionNode::DirectedShortChannelId(direction, scid) => {
182+
match direction {
183+
Direction::NodeOne => 0u8.write(w)?,
184+
Direction::NodeTwo => 1u8.write(w)?,
185+
}
186+
scid.write(w)?;
187+
},
188+
}
189+
142190
self.blinding_point.write(w)?;
143191
(self.blinded_hops.len() as u8).write(w)?;
144192
for hop in &self.blinded_hops {
@@ -150,7 +198,17 @@ impl Writeable for BlindedPath {
150198

151199
impl Readable for BlindedPath {
152200
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
153-
let introduction_node_id = Readable::read(r)?;
201+
let mut first_byte: u8 = Readable::read(r)?;
202+
let introduction_node = match first_byte {
203+
0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?),
204+
1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?),
205+
2|3 => {
206+
use io::Read;
207+
let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref());
208+
IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?)
209+
},
210+
_ => return Err(DecodeError::InvalidValue),
211+
};
154212
let blinding_point = Readable::read(r)?;
155213
let num_hops: u8 = Readable::read(r)?;
156214
if num_hops == 0 { return Err(DecodeError::InvalidValue) }
@@ -159,7 +217,7 @@ impl Readable for BlindedPath {
159217
blinded_hops.push(Readable::read(r)?);
160218
}
161219
Ok(BlindedPath {
162-
introduction_node_id,
220+
introduction_node,
163221
blinding_point,
164222
blinded_hops,
165223
})

lightning/src/ln/offers_tests.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
//! blinded paths are used.
4242
4343
use core::time::Duration;
44-
use crate::blinded_path::BlindedPath;
44+
use crate::blinded_path::{BlindedPath, IntroductionNode};
4545
use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
4646
use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self};
4747
use crate::ln::functional_test_utils::*;
@@ -259,8 +259,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
259259
assert_ne!(offer.signing_pubkey(), bob_id);
260260
assert!(!offer.paths().is_empty());
261261
for path in offer.paths() {
262-
assert_ne!(path.introduction_node_id, bob_id);
263-
assert_ne!(path.introduction_node_id, charlie_id);
262+
assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id));
263+
assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
264264
}
265265

266266
// Use a one-hop blinded path when Bob is announced and all his peers are Tor-only.
@@ -274,7 +274,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
274274
assert_ne!(offer.signing_pubkey(), bob_id);
275275
assert!(!offer.paths().is_empty());
276276
for path in offer.paths() {
277-
assert_eq!(path.introduction_node_id, bob_id);
277+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
278278
}
279279
}
280280

@@ -324,7 +324,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
324324
assert_ne!(offer.signing_pubkey(), bob_id);
325325
assert!(!offer.paths().is_empty());
326326
for path in offer.paths() {
327-
assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id());
327+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id()));
328328
}
329329
}
330330

@@ -373,7 +373,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
373373
assert_ne!(offer.signing_pubkey(), alice_id);
374374
assert!(!offer.paths().is_empty());
375375
for path in offer.paths() {
376-
assert_eq!(path.introduction_node_id, bob_id);
376+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
377377
}
378378

379379
let payment_id = PaymentId([1; 32]);
@@ -394,7 +394,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
394394
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
395395
assert_eq!(invoice_request.amount_msats(), None);
396396
assert_ne!(invoice_request.payer_id(), david_id);
397-
assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id);
397+
assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(charlie_id));
398398

399399
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
400400
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -407,7 +407,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
407407
assert_ne!(invoice.signing_pubkey(), alice_id);
408408
assert!(!invoice.payment_paths().is_empty());
409409
for (_, path) in invoice.payment_paths() {
410-
assert_eq!(path.introduction_node_id, bob_id);
410+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
411411
}
412412

413413
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -468,7 +468,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
468468
assert_ne!(refund.payer_id(), david_id);
469469
assert!(!refund.paths().is_empty());
470470
for path in refund.paths() {
471-
assert_eq!(path.introduction_node_id, charlie_id);
471+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
472472
}
473473
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
474474

@@ -487,7 +487,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
487487
assert_ne!(invoice.signing_pubkey(), alice_id);
488488
assert!(!invoice.payment_paths().is_empty());
489489
for (_, path) in invoice.payment_paths() {
490-
assert_eq!(path.introduction_node_id, bob_id);
490+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
491491
}
492492

493493
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -521,7 +521,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
521521
assert_ne!(offer.signing_pubkey(), alice_id);
522522
assert!(!offer.paths().is_empty());
523523
for path in offer.paths() {
524-
assert_eq!(path.introduction_node_id, alice_id);
524+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
525525
}
526526

527527
let payment_id = PaymentId([1; 32]);
@@ -534,7 +534,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
534534
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
535535
assert_eq!(invoice_request.amount_msats(), None);
536536
assert_ne!(invoice_request.payer_id(), bob_id);
537-
assert_eq!(reply_path.unwrap().introduction_node_id, bob_id);
537+
assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(bob_id));
538538

539539
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
540540
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -544,7 +544,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
544544
assert_ne!(invoice.signing_pubkey(), alice_id);
545545
assert!(!invoice.payment_paths().is_empty());
546546
for (_, path) in invoice.payment_paths() {
547-
assert_eq!(path.introduction_node_id, alice_id);
547+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
548548
}
549549

550550
route_bolt12_payment(bob, &[alice], &invoice);
@@ -584,7 +584,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
584584
assert_ne!(refund.payer_id(), bob_id);
585585
assert!(!refund.paths().is_empty());
586586
for path in refund.paths() {
587-
assert_eq!(path.introduction_node_id, bob_id);
587+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
588588
}
589589
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
590590

@@ -598,7 +598,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
598598
assert_ne!(invoice.signing_pubkey(), alice_id);
599599
assert!(!invoice.payment_paths().is_empty());
600600
for (_, path) in invoice.payment_paths() {
601-
assert_eq!(path.introduction_node_id, alice_id);
601+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
602602
}
603603

604604
route_bolt12_payment(bob, &[alice], &invoice);

lightning/src/offers/invoice.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,7 +1454,7 @@ mod tests {
14541454
use bitcoin::key::TweakedPublicKey;
14551455
use core::convert::TryFrom;
14561456
use core::time::Duration;
1457-
use crate::blinded_path::{BlindedHop, BlindedPath};
1457+
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
14581458
use crate::sign::KeyMaterial;
14591459
use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
14601460
use crate::ln::inbound_payment::ExpandedKey;
@@ -1802,7 +1802,7 @@ mod tests {
18021802
let secp_ctx = Secp256k1::new();
18031803

18041804
let blinded_path = BlindedPath {
1805-
introduction_node_id: pubkey(40),
1805+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
18061806
blinding_point: pubkey(41),
18071807
blinded_hops: vec![
18081808
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },

lightning/src/offers/offer.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ mod tests {
10631063
use core::convert::TryFrom;
10641064
use core::num::NonZeroU64;
10651065
use core::time::Duration;
1066-
use crate::blinded_path::{BlindedHop, BlindedPath};
1066+
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
10671067
use crate::sign::KeyMaterial;
10681068
use crate::ln::features::OfferFeatures;
10691069
use crate::ln::inbound_payment::ExpandedKey;
@@ -1234,7 +1234,7 @@ mod tests {
12341234
let secp_ctx = Secp256k1::new();
12351235

12361236
let blinded_path = BlindedPath {
1237-
introduction_node_id: pubkey(40),
1237+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
12381238
blinding_point: pubkey(41),
12391239
blinded_hops: vec![
12401240
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
@@ -1380,15 +1380,15 @@ mod tests {
13801380
fn builds_offer_with_paths() {
13811381
let paths = vec![
13821382
BlindedPath {
1383-
introduction_node_id: pubkey(40),
1383+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
13841384
blinding_point: pubkey(41),
13851385
blinded_hops: vec![
13861386
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
13871387
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
13881388
],
13891389
},
13901390
BlindedPath {
1391-
introduction_node_id: pubkey(40),
1391+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
13921392
blinding_point: pubkey(41),
13931393
blinded_hops: vec![
13941394
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
@@ -1570,15 +1570,15 @@ mod tests {
15701570
fn parses_offer_with_paths() {
15711571
let offer = OfferBuilder::new("foo".into(), pubkey(42))
15721572
.path(BlindedPath {
1573-
introduction_node_id: pubkey(40),
1573+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
15741574
blinding_point: pubkey(41),
15751575
blinded_hops: vec![
15761576
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
15771577
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
15781578
],
15791579
})
15801580
.path(BlindedPath {
1581-
introduction_node_id: pubkey(40),
1581+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
15821582
blinding_point: pubkey(41),
15831583
blinded_hops: vec![
15841584
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },

0 commit comments

Comments
 (0)