Skip to content

Commit a4e242b

Browse files
Onion messages: fix edge case where we are the introduction node
If we're sending straight to a blinded route with no unblinded intermediate hops, and we are the introduction node, we need to advance the blinded route by one hop so that the second hop is the new introduction node.
1 parent 20eecd5 commit a4e242b

File tree

3 files changed

+83
-6
lines changed

3 files changed

+83
-6
lines changed

lightning/src/onion_message/blinded_route.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@
99

1010
//! Creating blinded routes and related utilities live here.
1111
12-
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
12+
use bitcoin::hashes::{Hash, HashEngine};
13+
use bitcoin::hashes::sha256::Hash as Sha256;
14+
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
1315

14-
use crate::chain::keysinterface::KeysInterface;
16+
use crate::chain::keysinterface::{KeysInterface, Recipient};
17+
use super::packet::ControlTlvs;
1518
use super::utils;
1619
use crate::ln::msgs::DecodeError;
17-
use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
18-
use crate::util::ser::{Readable, VecWriter, Writeable, Writer};
20+
use crate::ln::onion_utils;
21+
use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
22+
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
1923

20-
use crate::io;
24+
use core::mem;
25+
use core::ops::Deref;
26+
use crate::io::{self, Cursor};
2127
use crate::prelude::*;
2228

2329
/// Onion messages can be sent and received to blinded routes, which serve to hide the identity of
@@ -69,6 +75,41 @@ impl BlindedRoute {
6975
blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
7076
})
7177
}
78+
79+
// Advance the blinded route by one hop, so make the second hop into the new introduction node.
80+
pub(super) fn advance_by_one<K: Deref, T: secp256k1::Signing + secp256k1::Verification>
81+
(&mut self, keys_manager: &K, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
82+
where K::Target: KeysInterface
83+
{
84+
let control_tlvs_ss = keys_manager.ecdh(Recipient::Node, &self.blinding_point, None)?;
85+
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
86+
let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
87+
let mut s = Cursor::new(&encrypted_control_tlvs);
88+
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
89+
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
90+
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
91+
mut next_node_id, next_blinding_override,
92+
})}) => {
93+
let mut new_blinding_point = match next_blinding_override {
94+
Some(blinding_point) => blinding_point,
95+
None => {
96+
let blinding_factor = {
97+
let mut sha = Sha256::engine();
98+
sha.input(&self.blinding_point.serialize()[..]);
99+
sha.input(control_tlvs_ss.as_ref());
100+
Sha256::from_engine(sha).into_inner()
101+
};
102+
self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
103+
.map_err(|_| ())?
104+
}
105+
};
106+
mem::swap(&mut self.blinding_point, &mut new_blinding_point);
107+
mem::swap(&mut self.introduction_node_id, &mut next_node_id);
108+
Ok(())
109+
},
110+
_ => Err(())
111+
}
112+
}
72113
}
73114

74115
/// Construct blinded hops for the given `unblinded_path`.

lightning/src/onion_message/functional_tests.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ fn too_big_packet_error() {
170170
assert_eq!(err, SendError::TooBigPacket);
171171
}
172172

173+
#[test]
174+
fn we_are_intro_node() {
175+
// If we are sending straight to a blinded route and we are the introduction node, we need to
176+
// advance the blinded route by 1 hop so the second hop is the new introduction node.
177+
let nodes = create_nodes(3);
178+
let test_msg = TestCustomMessage {};
179+
180+
let secp_ctx = Secp256k1::new();
181+
let blinded_route = BlindedRoute::new(&[nodes[0].get_node_pk(), nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
182+
183+
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
184+
pass_along_path(&nodes, None);
185+
}
186+
173187
#[test]
174188
fn invalid_blinded_route_error() {
175189
// Make sure we error as expected if a provided blinded route has 0 or 1 hops.

lightning/src/onion_message/messenger.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,15 @@ pub enum SendError {
160160
InvalidMessage,
161161
/// Our next-hop peer's buffer was full or our total outbound buffer was full.
162162
BufferFull,
163+
/// Failed to retrieve our node id from the provided [`KeysInterface`].
164+
///
165+
/// [`KeysInterface`]: crate::chain::keysinterface::KeysInterface
166+
GetNodeIdFailed,
167+
/// We attempted to send to a blinded route where we are the introduction node, and failed to
168+
/// advance the blinded route to make the second hop the new introduction node. Either
169+
/// [`KeysInterface::ecdh`] failed, we failed to tweak the current blinding point to get the
170+
/// new blinding point, or we were attempting to send to ourselves.
171+
BlindedRouteAdvanceFailed,
163172
}
164173

165174
/// Handler for custom onion messages. If you are using [`SimpleArcOnionMessenger`],
@@ -201,7 +210,7 @@ impl<Signer: Sign, K: Deref, L: Deref, CMH: Deref> OnionMessenger<Signer, K, L,
201210

202211
/// Send an onion message with contents `message` to `destination`, routing it through `intermediate_nodes`.
203212
/// See [`OnionMessenger`] for example usage.
204-
pub fn send_onion_message<T: CustomOnionMessageContents>(&self, intermediate_nodes: &[PublicKey], destination: Destination, message: OnionMessageContents<T>, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
213+
pub fn send_onion_message<T: CustomOnionMessageContents>(&self, intermediate_nodes: &[PublicKey], mut destination: Destination, message: OnionMessageContents<T>, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
205214
if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination {
206215
if blinded_hops.len() < 2 {
207216
return Err(SendError::TooFewBlindedHops);
@@ -210,6 +219,19 @@ impl<Signer: Sign, K: Deref, L: Deref, CMH: Deref> OnionMessenger<Signer, K, L,
210219
let OnionMessageContents::Custom(ref msg) = message;
211220
if msg.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
212221

222+
// If we are sending straight to a blinded route and we are the introduction node, we need to
223+
// advance the blinded route by 1 hop so the second hop is the new introduction node.
224+
if intermediate_nodes.len() == 0 {
225+
if let Destination::BlindedRoute(ref mut blinded_route) = destination {
226+
let our_node_id = self.keys_manager.get_node_id(Recipient::Node)
227+
.map_err(|()| SendError::GetNodeIdFailed)?;
228+
if blinded_route.introduction_node_id == our_node_id {
229+
blinded_route.advance_by_one(&self.keys_manager, &self.secp_ctx)
230+
.map_err(|()| SendError::BlindedRouteAdvanceFailed)?;
231+
}
232+
}
233+
}
234+
213235
let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
214236
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
215237
let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {

0 commit comments

Comments
 (0)