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.
This commit is contained in:
Valentine Wallace 2022-10-21 15:09:03 -04:00
parent 20eecd5a76
commit a4e242ba5f
No known key found for this signature in database
GPG Key ID: FD3E106A2CE099B4
3 changed files with 83 additions and 6 deletions

View File

@ -9,15 +9,21 @@
//! Creating blinded routes and related utilities live here.
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
use crate::chain::keysinterface::KeysInterface;
use crate::chain::keysinterface::{KeysInterface, Recipient};
use super::packet::ControlTlvs;
use super::utils;
use crate::ln::msgs::DecodeError;
use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
use crate::util::ser::{Readable, VecWriter, Writeable, Writer};
use crate::ln::onion_utils;
use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
use crate::io;
use core::mem;
use core::ops::Deref;
use crate::io::{self, Cursor};
use crate::prelude::*;
/// Onion messages can be sent and received to blinded routes, which serve to hide the identity of
@ -69,6 +75,41 @@ impl BlindedRoute {
blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
})
}
// Advance the blinded route by one hop, so make the second hop into the new introduction node.
pub(super) fn advance_by_one<K: Deref, T: secp256k1::Signing + secp256k1::Verification>
(&mut self, keys_manager: &K, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
where K::Target: KeysInterface
{
let control_tlvs_ss = keys_manager.ecdh(Recipient::Node, &self.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
let mut s = Cursor::new(&encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
mut next_node_id, next_blinding_override,
})}) => {
let mut new_blinding_point = match next_blinding_override {
Some(blinding_point) => blinding_point,
None => {
let blinding_factor = {
let mut sha = Sha256::engine();
sha.input(&self.blinding_point.serialize()[..]);
sha.input(control_tlvs_ss.as_ref());
Sha256::from_engine(sha).into_inner()
};
self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
.map_err(|_| ())?
}
};
mem::swap(&mut self.blinding_point, &mut new_blinding_point);
mem::swap(&mut self.introduction_node_id, &mut next_node_id);
Ok(())
},
_ => Err(())
}
}
}
/// Construct blinded hops for the given `unblinded_path`.

View File

@ -170,6 +170,20 @@ fn too_big_packet_error() {
assert_eq!(err, SendError::TooBigPacket);
}
#[test]
fn we_are_intro_node() {
// If we are sending straight to a blinded route and we are the introduction node, we need to
// advance the blinded route by 1 hop so the second hop is the new introduction node.
let nodes = create_nodes(3);
let test_msg = TestCustomMessage {};
let secp_ctx = Secp256k1::new();
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();
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), OnionMessageContents::Custom(test_msg.clone()), None).unwrap();
pass_along_path(&nodes, None);
}
#[test]
fn invalid_blinded_route_error() {
// Make sure we error as expected if a provided blinded route has 0 or 1 hops.

View File

@ -160,6 +160,15 @@ pub enum SendError {
InvalidMessage,
/// Our next-hop peer's buffer was full or our total outbound buffer was full.
BufferFull,
/// Failed to retrieve our node id from the provided [`KeysInterface`].
///
/// [`KeysInterface`]: crate::chain::keysinterface::KeysInterface
GetNodeIdFailed,
/// We attempted to send to a blinded route where we are the introduction node, and failed to
/// advance the blinded route to make the second hop the new introduction node. Either
/// [`KeysInterface::ecdh`] failed, we failed to tweak the current blinding point to get the
/// new blinding point, or we were attempting to send to ourselves.
BlindedRouteAdvanceFailed,
}
/// 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,
/// Send an onion message with contents `message` to `destination`, routing it through `intermediate_nodes`.
/// See [`OnionMessenger`] for example usage.
pub fn send_onion_message<T: CustomOnionMessageContents>(&self, intermediate_nodes: &[PublicKey], destination: Destination, message: OnionMessageContents<T>, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
pub fn send_onion_message<T: CustomOnionMessageContents>(&self, intermediate_nodes: &[PublicKey], mut destination: Destination, message: OnionMessageContents<T>, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination {
if blinded_hops.len() < 2 {
return Err(SendError::TooFewBlindedHops);
@ -210,6 +219,19 @@ impl<Signer: Sign, K: Deref, L: Deref, CMH: Deref> OnionMessenger<Signer, K, L,
let OnionMessageContents::Custom(ref msg) = message;
if msg.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
// If we are sending straight to a blinded route and we are the introduction node, we need to
// advance the blinded route by 1 hop so the second hop is the new introduction node.
if intermediate_nodes.len() == 0 {
if let Destination::BlindedRoute(ref mut blinded_route) = destination {
let our_node_id = self.keys_manager.get_node_id(Recipient::Node)
.map_err(|()| SendError::GetNodeIdFailed)?;
if blinded_route.introduction_node_id == our_node_id {
blinded_route.advance_by_one(&self.keys_manager, &self.secp_ctx)
.map_err(|()| SendError::BlindedRouteAdvanceFailed)?;
}
}
}
let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {