Skip to content

Commit d1d963f

Browse files
tnulljkczyz
authored andcommitted
Intercept onion messages for unknown SCID hops
Allow integrations to intercept blinded onion-message hops that identify the next node by short channel id, so LSPS-style protocols can resolve those hops out of band instead of dropping the message. Co-Authored-By: HAL 9000
1 parent 2344577 commit d1d963f

6 files changed

Lines changed: 280 additions & 28 deletions

File tree

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use lightning_0_2::ln::channelmanager::PaymentId as PaymentId_0_2;
1717
use lightning_0_2::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_2;
1818
use lightning_0_2::ln::functional_test_utils as lightning_0_2_utils;
1919
use lightning_0_2::ln::msgs::ChannelMessageHandler as _;
20+
use lightning_0_2::ln::msgs::OnionMessage as OnionMessage_0_2;
21+
use lightning_0_2::onion_message::packet::Packet as Packet_0_2;
2022
use lightning_0_2::routing::router as router_0_2;
23+
use lightning_0_2::util::ser::MaybeReadable as MaybeReadable_0_2;
2124
use lightning_0_2::util::ser::Writeable as _;
2225

2326
use lightning_0_1::commitment_signed_dance as commitment_signed_dance_0_1;
@@ -45,23 +48,29 @@ use lightning_0_0_125::ln::msgs::ChannelMessageHandler as _;
4548
use lightning_0_0_125::routing::router as router_0_0_125;
4649
use lightning_0_0_125::util::ser::Writeable as _;
4750

51+
use lightning::blinded_path::message::NextMessageHop;
4852
use lightning::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER};
4953
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
5054
use lightning::ln::functional_test_utils::*;
55+
use lightning::ln::msgs;
5156
use lightning::ln::msgs::BaseMessageHandler as _;
5257
use lightning::ln::msgs::ChannelMessageHandler as _;
5358
use lightning::ln::msgs::MessageSendEvent;
5459
use lightning::ln::splicing_tests::*;
5560
use lightning::ln::types::ChannelId;
61+
use lightning::onion_message::packet::Packet;
5662
use lightning::sign::OutputSpender;
63+
use lightning::util::ser::{MaybeReadable, Writeable};
5764
use lightning::util::wallet_utils::WalletSourceSync;
5865

5966
use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
6067

6168
use bitcoin::script::Builder;
62-
use bitcoin::secp256k1::Secp256k1;
69+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
6370
use bitcoin::{opcodes, Amount, TxOut};
6471

72+
use lightning::io::Cursor;
73+
6574
use std::sync::Arc;
6675

6776
#[test]
@@ -700,3 +709,107 @@ fn do_upgrade_mid_htlc_forward(test: MidHtlcForwardCase) {
700709
expect_payment_claimable!(nodes[2], pay_hash, pay_secret, 1_000_000);
701710
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], pay_preimage);
702711
}
712+
713+
/// Constructs a dummy `OnionMessage` (current version) for use in serialization tests.
714+
fn dummy_onion_message() -> msgs::OnionMessage {
715+
let pubkey =
716+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
717+
msgs::OnionMessage {
718+
blinding_point: pubkey,
719+
onion_routing_packet: Packet {
720+
version: 0,
721+
public_key: pubkey,
722+
hop_data: vec![1; 64],
723+
hmac: [2; 32],
724+
},
725+
}
726+
}
727+
728+
/// Constructs a dummy `OnionMessage` (0.2 version) for use in serialization tests.
729+
fn dummy_onion_message_0_2() -> OnionMessage_0_2 {
730+
let pubkey = bitcoin::secp256k1::PublicKey::from_secret_key(
731+
&Secp256k1::new(),
732+
&SecretKey::from_slice(&[42; 32]).unwrap(),
733+
);
734+
OnionMessage_0_2 {
735+
blinding_point: pubkey,
736+
onion_routing_packet: Packet_0_2 {
737+
version: 0,
738+
public_key: pubkey,
739+
hop_data: vec![1; 64],
740+
hmac: [2; 32],
741+
},
742+
}
743+
}
744+
745+
#[test]
746+
fn test_onion_message_intercepted_upgrade_from_0_2() {
747+
// Ensure that an `Event::OnionMessageIntercepted` serialized by LDK 0.2 (which uses
748+
// `peer_node_id: PublicKey` in TLV field 0) can be deserialized by the current version,
749+
// producing `NextMessageHop::NodeId`.
750+
let pubkey =
751+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
752+
753+
let event_0_2 = Event_0_2::OnionMessageIntercepted {
754+
peer_node_id: pubkey,
755+
message: dummy_onion_message_0_2(),
756+
};
757+
758+
let serialized = lightning_0_2::util::ser::Writeable::encode(&event_0_2);
759+
760+
let mut reader = Cursor::new(&serialized);
761+
let deserialized = <Event as MaybeReadable>::read(&mut reader).unwrap().unwrap();
762+
763+
match deserialized {
764+
Event::OnionMessageIntercepted { next_hop, message } => {
765+
assert_eq!(next_hop, NextMessageHop::NodeId(pubkey));
766+
assert_eq!(message, dummy_onion_message());
767+
},
768+
_ => panic!("Expected OnionMessageIntercepted event"),
769+
}
770+
}
771+
772+
#[test]
773+
fn test_onion_message_intercepted_node_id_downgrade_to_0_2() {
774+
// Ensure that an `Event::OnionMessageIntercepted` with a `NodeId` next hop serialized by
775+
// the current version can be deserialized by LDK 0.2 (which expects `peer_node_id` in TLV
776+
// field 0).
777+
let pubkey =
778+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
779+
780+
let event = Event::OnionMessageIntercepted {
781+
next_hop: NextMessageHop::NodeId(pubkey),
782+
message: dummy_onion_message(),
783+
};
784+
785+
let serialized = event.encode();
786+
787+
let mut reader = Cursor::new(&serialized);
788+
let deserialized = <Event_0_2 as MaybeReadable_0_2>::read(&mut reader).unwrap().unwrap();
789+
790+
match deserialized {
791+
Event_0_2::OnionMessageIntercepted { peer_node_id, message } => {
792+
assert_eq!(peer_node_id, pubkey);
793+
assert_eq!(message, dummy_onion_message_0_2());
794+
},
795+
_ => panic!("Expected OnionMessageIntercepted event"),
796+
}
797+
}
798+
799+
#[test]
800+
fn test_onion_message_intercepted_scid_downgrade_to_0_2() {
801+
// Ensure that an `Event::OnionMessageIntercepted` with a `ShortChannelId` next hop
802+
// serialized by the current version cannot be deserialized by LDK 0.2, since the
803+
// `peer_node_id` field (0) is not written for SCID variants and LDK 0.2 requires it.
804+
let event = Event::OnionMessageIntercepted {
805+
next_hop: NextMessageHop::ShortChannelId(42),
806+
message: dummy_onion_message(),
807+
};
808+
809+
let serialized = event.encode();
810+
811+
// LDK 0.2 will try to read field 0 as required. Since it's absent, the read will fail.
812+
let mut reader = Cursor::new(&serialized);
813+
let result = <Event_0_2 as MaybeReadable_0_2>::read(&mut reader);
814+
assert!(result.is_err(), "LDK 0.2 should fail to decode a ShortChannelId variant");
815+
}

lightning/src/blinded_path/message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ pub enum NextMessageHop {
275275
ShortChannelId(u64),
276276
}
277277

278+
impl_ser_tlv_based_enum!(NextMessageHop,
279+
{0, NodeId} => (),
280+
{2, ShortChannelId} => (),
281+
);
282+
278283
/// An intermediate node, and possibly a short channel id leading to the next node.
279284
///
280285
/// Note:

lightning/src/events/mod.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod bump_transaction;
1818

1919
pub use bump_transaction::BumpTransactionEvent;
2020

21-
use crate::blinded_path::message::{BlindedMessagePath, OffersContext};
21+
use crate::blinded_path::message::{BlindedMessagePath, NextMessageHop, OffersContext};
2222
use crate::blinded_path::payment::{
2323
Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef,
2424
};
@@ -1836,9 +1836,13 @@ pub enum Event {
18361836
/// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments
18371837
BumpTransaction(BumpTransactionEvent),
18381838
/// We received an onion message that is intended to be forwarded to a peer
1839-
/// that is currently offline. This event will only be generated if the
1840-
/// `OnionMessenger` was initialized with
1841-
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs.
1839+
/// that is currently offline *or* that is intended to be forwarded along a channel with an
1840+
/// SCID unknown to us.
1841+
///
1842+
/// This event will only be generated if the `OnionMessenger` was initialized with
1843+
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs. The
1844+
/// [`NextMessageHop::ShortChannelId`] variant is only generated if `intercept_for_unknown_scids`
1845+
/// was set when constructing the `OnionMessenger`.
18421846
///
18431847
/// The offline peer should be awoken if possible on receipt of this event, such as via the LSPS5
18441848
/// protocol.
@@ -1852,9 +1856,10 @@ pub enum Event {
18521856
///
18531857
/// [`OnionMessenger::new_with_offline_peer_interception`]: crate::onion_message::messenger::OnionMessenger::new_with_offline_peer_interception
18541858
OnionMessageIntercepted {
1855-
/// The node id of the offline peer.
1856-
peer_node_id: PublicKey,
1857-
/// The onion message intended to be forwarded to `peer_node_id`.
1859+
/// The next hop (offline peer or unknown SCID).
1860+
next_hop: NextMessageHop,
1861+
/// The onion message intended to be forwarded to the offline peer or via the unknown
1862+
/// channel once established.
18581863
message: msgs::OnionMessage,
18591864
},
18601865
/// Indicates that an onion message supporting peer has come online and any messages previously
@@ -2436,12 +2441,25 @@ impl Writeable for Event {
24362441
35u8.write(writer)?;
24372442
// Never write ConnectionNeeded events as buffered onion messages aren't serialized.
24382443
},
2439-
&Event::OnionMessageIntercepted { ref peer_node_id, ref message } => {
2444+
&Event::OnionMessageIntercepted { ref next_hop, ref message } => {
24402445
37u8.write(writer)?;
2441-
write_tlv_fields!(writer, {
2442-
(0, peer_node_id, required),
2443-
(2, message, required),
2444-
});
2446+
match next_hop {
2447+
NextMessageHop::NodeId(peer_node_id) => {
2448+
// If we have the node_id, we keep writing it for backwards compatibility.
2449+
write_tlv_fields!(writer, {
2450+
(0, peer_node_id, required),
2451+
(1, next_hop, required),
2452+
(2, message, required),
2453+
});
2454+
},
2455+
NextMessageHop::ShortChannelId(_) => {
2456+
write_tlv_fields!(writer, {
2457+
// 0 used to be peer_node_id in LDK v0.2 and prior.
2458+
(1, next_hop, required),
2459+
(2, message, required),
2460+
});
2461+
},
2462+
}
24452463
},
24462464
&Event::OnionMessagePeerConnected { ref peer_node_id } => {
24472465
39u8.write(writer)?;
@@ -3069,11 +3087,16 @@ impl MaybeReadable for Event {
30693087
37u8 => {
30703088
let mut f = || {
30713089
_init_and_read_len_prefixed_tlv_fields!(reader, {
3072-
(0, peer_node_id, required),
3090+
(0, peer_node_id, option),
3091+
(1, next_hop, option),
30733092
(2, message, required),
30743093
});
3094+
3095+
let next_hop = next_hop
3096+
.or(peer_node_id.map(NextMessageHop::NodeId))
3097+
.ok_or(msgs::DecodeError::InvalidValue)?;
30753098
Ok(Some(Event::OnionMessageIntercepted {
3076-
peer_node_id: peer_node_id.0.unwrap(),
3099+
next_hop,
30773100
message: message.0.unwrap(),
30783101
}))
30793102
};

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4799,6 +4799,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(
47994799
&chan_mgrs[i],
48004800
IgnoringMessageHandler {},
48014801
IgnoringMessageHandler {},
4802+
true,
48024803
);
48034804
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
48044805
let wallet_source = Arc::new(test_utils::TestWalletSource::new(

0 commit comments

Comments
 (0)