Merge pull request #1791 from valentinewallace/2022-10-we-are-intro-node

Onion messages: fix edge case where we are the intro node
This commit is contained in:
Matt Corallo 2022-11-01 21:12:30 +00:00 committed by GitHub
commit 6ca49948c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 13 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

@ -167,6 +167,26 @@ 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 mut 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);
// Try with a two-hop blinded route where we are the introduction node.
let blinded_route = BlindedRoute::new(&[nodes[0].get_node_pk(), nodes[1].get_node_pk()], &*nodes[1].keys_manager, &secp_ctx).unwrap();
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), OnionMessageContents::Custom(test_msg), None).unwrap();
nodes.remove(2);
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

@ -154,6 +154,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`],
@ -198,7 +207,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);
@ -207,6 +216,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 {
@ -488,12 +510,8 @@ fn packet_payloads_and_keys<T: CustomOnionMessageContents, S: secp256k1::Signing
next_blinding_override: Some(blinding_pt),
})), control_tlvs_ss));
}
if let Some(encrypted_payload) = enc_payload_opt {
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(encrypted_payload)),
control_tlvs_ss));
} else { debug_assert!(false); }
blinded_path_idx += 1;
} else if blinded_path_idx < num_blinded_hops - 1 && enc_payload_opt.is_some() {
}
if blinded_path_idx < num_blinded_hops.saturating_sub(1) && enc_payload_opt.is_some() {
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
control_tlvs_ss));
blinded_path_idx += 1;

View file

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