@@ -17,6 +17,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
17
17
18
18
use crate::ln::msgs::DecodeError;
19
19
use crate::offers::invoice::BlindedPayInfo;
20
+ use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
20
21
use crate::sign::EntropySource;
21
22
use crate::util::ser::{Readable, Writeable, Writer};
22
23
@@ -28,11 +29,11 @@ use crate::prelude::*;
28
29
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
29
30
pub struct BlindedPath {
30
31
/// To send to a blinded path, the sender first finds a route to the unblinded
31
- /// `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
32
33
/// message or payment's next hop and forward it along.
33
34
///
34
35
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
35
- pub introduction_node_id: PublicKey ,
36
+ pub introduction_node: IntroductionNode ,
36
37
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
37
38
/// message or payment.
38
39
///
@@ -42,6 +43,52 @@ pub struct BlindedPath {
42
43
pub blinded_hops: Vec<BlindedHop>,
43
44
}
44
45
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, Copy, 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
+
69
+ /// An interface for looking up the node id of a channel counterparty for the purpose of forwarding
70
+ /// an [`OnionMessage`].
71
+ ///
72
+ /// [`OnionMessage`]: crate::ln::msgs::OnionMessage
73
+ pub trait NodeIdLookUp {
74
+ /// Returns the node id of the forwarding node's channel counterparty with `short_channel_id`.
75
+ ///
76
+ /// Here, the forwarding node is referring to the node of the [`OnionMessenger`] parameterized
77
+ /// by the [`NodeIdLookUp`] and the counterparty to one of that node's peers.
78
+ ///
79
+ /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger
80
+ fn next_node_id(&self, short_channel_id: u64) -> Option<PublicKey>;
81
+ }
82
+
83
+ /// A [`NodeIdLookUp`] that always returns `None`.
84
+ pub struct EmptyNodeIdLookUp {}
85
+
86
+ impl NodeIdLookUp for EmptyNodeIdLookUp {
87
+ fn next_node_id(&self, _short_channel_id: u64) -> Option<PublicKey> {
88
+ None
89
+ }
90
+ }
91
+
45
92
/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
46
93
/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
47
94
/// and thus can be used to hide the identity of the recipient.
@@ -74,10 +121,10 @@ impl BlindedPath {
74
121
if node_pks.is_empty() { return Err(()) }
75
122
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
76
123
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
77
- let introduction_node_id = node_pks[0];
124
+ let introduction_node = IntroductionNode::NodeId( node_pks[0]) ;
78
125
79
126
Ok(BlindedPath {
80
- introduction_node_id ,
127
+ introduction_node ,
81
128
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
82
129
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
83
130
})
@@ -111,25 +158,59 @@ impl BlindedPath {
111
158
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
112
159
entropy_source: &ES, secp_ctx: &Secp256k1<T>
113
160
) -> Result<(BlindedPayInfo, Self), ()> {
161
+ let introduction_node = IntroductionNode::NodeId(
162
+ intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id)
163
+ );
114
164
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
115
165
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
116
166
117
167
let blinded_payinfo = payment::compute_payinfo(
118
168
intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
119
169
)?;
120
170
Ok((blinded_payinfo, BlindedPath {
121
- introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id) ,
171
+ introduction_node ,
122
172
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
123
173
blinded_hops: payment::blinded_hops(
124
174
secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
125
175
).map_err(|_| ())?,
126
176
}))
127
177
}
178
+
179
+ /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e.,
180
+ /// it is found in the network graph).
181
+ pub fn public_introduction_node_id<'a>(
182
+ &self, network_graph: &'a ReadOnlyNetworkGraph
183
+ ) -> Option<&'a NodeId> {
184
+ match &self.introduction_node {
185
+ IntroductionNode::NodeId(pubkey) => {
186
+ let node_id = NodeId::from_pubkey(pubkey);
187
+ network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key)
188
+ },
189
+ IntroductionNode::DirectedShortChannelId(direction, scid) => {
190
+ network_graph
191
+ .channel(*scid)
192
+ .map(|c| match direction {
193
+ Direction::NodeOne => &c.node_one,
194
+ Direction::NodeTwo => &c.node_two,
195
+ })
196
+ },
197
+ }
198
+ }
128
199
}
129
200
130
201
impl Writeable for BlindedPath {
131
202
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
132
- self.introduction_node_id.write(w)?;
203
+ match &self.introduction_node {
204
+ IntroductionNode::NodeId(pubkey) => pubkey.write(w)?,
205
+ IntroductionNode::DirectedShortChannelId(direction, scid) => {
206
+ match direction {
207
+ Direction::NodeOne => 0u8.write(w)?,
208
+ Direction::NodeTwo => 1u8.write(w)?,
209
+ }
210
+ scid.write(w)?;
211
+ },
212
+ }
213
+
133
214
self.blinding_point.write(w)?;
134
215
(self.blinded_hops.len() as u8).write(w)?;
135
216
for hop in &self.blinded_hops {
@@ -141,7 +222,17 @@ impl Writeable for BlindedPath {
141
222
142
223
impl Readable for BlindedPath {
143
224
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
144
- let introduction_node_id = Readable::read(r)?;
225
+ let mut first_byte: u8 = Readable::read(r)?;
226
+ let introduction_node = match first_byte {
227
+ 0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?),
228
+ 1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?),
229
+ 2|3 => {
230
+ use io::Read;
231
+ let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref());
232
+ IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?)
233
+ },
234
+ _ => return Err(DecodeError::InvalidValue),
235
+ };
145
236
let blinding_point = Readable::read(r)?;
146
237
let num_hops: u8 = Readable::read(r)?;
147
238
if num_hops == 0 { return Err(DecodeError::InvalidValue) }
@@ -150,7 +241,7 @@ impl Readable for BlindedPath {
150
241
blinded_hops.push(Readable::read(r)?);
151
242
}
152
243
Ok(BlindedPath {
153
- introduction_node_id ,
244
+ introduction_node ,
154
245
blinding_point,
155
246
blinded_hops,
156
247
})
@@ -162,3 +253,25 @@ impl_writeable!(BlindedHop, {
162
253
encrypted_payload
163
254
});
164
255
256
+ impl Direction {
257
+ /// Returns the [`NodeId`] from the inputs corresponding to the direction.
258
+ pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId {
259
+ match self {
260
+ Direction::NodeOne => core::cmp::min(node_a, node_b),
261
+ Direction::NodeTwo => core::cmp::max(node_a, node_b),
262
+ }
263
+ }
264
+
265
+ /// Returns the [`PublicKey`] from the inputs corresponding to the direction.
266
+ pub fn select_pubkey<'a>(&self, node_a: &'a PublicKey, node_b: &'a PublicKey) -> &'a PublicKey {
267
+ let (node_one, node_two) = if NodeId::from_pubkey(node_a) < NodeId::from_pubkey(node_b) {
268
+ (node_a, node_b)
269
+ } else {
270
+ (node_b, node_a)
271
+ };
272
+ match self {
273
+ Direction::NodeOne => node_one,
274
+ Direction::NodeTwo => node_two,
275
+ }
276
+ }
277
+ }
0 commit comments