Skip to content

Commit 6ca4994

Browse files
authored
Merge pull request #1791 from valentinewallace/2022-10-we-are-intro-node
Onion messages: fix edge case where we are the intro node
2 parents ad7ff0b + 7af02d0 commit 6ca4994

File tree

4 files changed

+91
-13
lines changed

4 files changed

+91
-13
lines changed

lightning/src/onion_message/blinded_route.rs

+46-5
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

+20
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,26 @@ fn too_big_packet_error() {
167167
assert_eq!(err, SendError::TooBigPacket);
168168
}
169169

170+
#[test]
171+
fn we_are_intro_node() {
172+
// If we are sending straight to a blinded route and we are the introduction node, we need to
173+
// advance the blinded route by 1 hop so the second hop is the new introduction node.
174+
let mut nodes = create_nodes(3);
175+
let test_msg = TestCustomMessage {};
176+
177+
let secp_ctx = Secp256k1::new();
178+
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();
179+
180+
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
181+
pass_along_path(&nodes, None);
182+
183+
// Try with a two-hop blinded route where we are the introduction node.
184+
let blinded_route = BlindedRoute::new(&[nodes[0].get_node_pk(), nodes[1].get_node_pk()], &*nodes[1].keys_manager, &secp_ctx).unwrap();
185+
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), OnionMessageContents::Custom(test_msg), None).unwrap();
186+
nodes.remove(2);
187+
pass_along_path(&nodes, None);
188+
}
189+
170190
#[test]
171191
fn invalid_blinded_route_error() {
172192
// Make sure we error as expected if a provided blinded route has 0 or 1 hops.

lightning/src/onion_message/messenger.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ pub enum SendError {
154154
InvalidMessage,
155155
/// Our next-hop peer's buffer was full or our total outbound buffer was full.
156156
BufferFull,
157+
/// Failed to retrieve our node id from the provided [`KeysInterface`].
158+
///
159+
/// [`KeysInterface`]: crate::chain::keysinterface::KeysInterface
160+
GetNodeIdFailed,
161+
/// We attempted to send to a blinded route where we are the introduction node, and failed to
162+
/// advance the blinded route to make the second hop the new introduction node. Either
163+
/// [`KeysInterface::ecdh`] failed, we failed to tweak the current blinding point to get the
164+
/// new blinding point, or we were attempting to send to ourselves.
165+
BlindedRouteAdvanceFailed,
157166
}
158167

159168
/// Handler for custom onion messages. If you are using [`SimpleArcOnionMessenger`],
@@ -198,7 +207,7 @@ impl<Signer: Sign, K: Deref, L: Deref, CMH: Deref> OnionMessenger<Signer, K, L,
198207

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

219+
// If we are sending straight to a blinded route and we are the introduction node, we need to
220+
// advance the blinded route by 1 hop so the second hop is the new introduction node.
221+
if intermediate_nodes.len() == 0 {
222+
if let Destination::BlindedRoute(ref mut blinded_route) = destination {
223+
let our_node_id = self.keys_manager.get_node_id(Recipient::Node)
224+
.map_err(|()| SendError::GetNodeIdFailed)?;
225+
if blinded_route.introduction_node_id == our_node_id {
226+
blinded_route.advance_by_one(&self.keys_manager, &self.secp_ctx)
227+
.map_err(|()| SendError::BlindedRouteAdvanceFailed)?;
228+
}
229+
}
230+
}
231+
210232
let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
211233
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
212234
let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
@@ -488,12 +510,8 @@ fn packet_payloads_and_keys<T: CustomOnionMessageContents, S: secp256k1::Signing
488510
next_blinding_override: Some(blinding_pt),
489511
})), control_tlvs_ss));
490512
}
491-
if let Some(encrypted_payload) = enc_payload_opt {
492-
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(encrypted_payload)),
493-
control_tlvs_ss));
494-
} else { debug_assert!(false); }
495-
blinded_path_idx += 1;
496-
} else if blinded_path_idx < num_blinded_hops - 1 && enc_payload_opt.is_some() {
513+
}
514+
if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
497515
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
498516
control_tlvs_ss));
499517
blinded_path_idx += 1;

lightning/src/util/chacha20poly1305rfc.rs

-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
228228
/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
229229
/// deserialized. This allows us to avoid an intermediate Vec allocation.
230230
pub(crate) struct ChaChaPolyReadAdapter<R: Readable> {
231-
#[allow(unused)] // This will be used soon for onion messages
232231
pub readable: R,
233232
}
234233

0 commit comments

Comments
 (0)