Skip to content

Commit 9051c38

Browse files
Support sending onion messages
This adds several utilities in service of then adding OnionMessenger::send_onion_message, which can send to either an unblinded pubkey or a blinded route. Sending custom TLVs and sending an onion message containing a reply path are not yet supported. We also need to split the construct_keys_callback macro into two macros to avoid an unused assignment warning.
1 parent 4c8dc2c commit 9051c38

File tree

6 files changed

+275
-24
lines changed

6 files changed

+275
-24
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ use io::{Cursor, Read};
3333
use core::convert::{AsMut, TryInto};
3434
use core::ops::Deref;
3535

36-
pub(super) struct OnionKeys {
36+
pub(crate) struct OnionKeys {
3737
#[cfg(test)]
38-
pub(super) shared_secret: SharedSecret,
38+
pub(crate) shared_secret: SharedSecret,
3939
#[cfg(test)]
40-
pub(super) blinding_factor: [u8; 32],
41-
pub(super) ephemeral_pubkey: PublicKey,
42-
pub(super) rho: [u8; 32],
43-
pub(super) mu: [u8; 32],
40+
pub(crate) blinding_factor: [u8; 32],
41+
pub(crate) ephemeral_pubkey: PublicKey,
42+
pub(crate) rho: [u8; 32],
43+
pub(crate) mu: [u8; 32],
4444
}
4545

4646
#[inline]
@@ -52,7 +52,7 @@ pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
5252
}
5353

5454
#[inline]
55-
pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
55+
pub(crate) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
5656
assert_eq!(shared_secret.len(), 32);
5757
({
5858
let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
@@ -260,7 +260,23 @@ impl AsMut<[u8]> for FixedSizeOnionPacket {
260260
}
261261
}
262262

263-
/// panics if route_size_insane(payloads)
263+
pub(crate) fn payloads_serialized_length<HD: Writeable>(payloads: &Vec<HD>) -> usize {
264+
payloads.iter().map(|p| p.serialized_length() + 32 /* HMAC */).sum()
265+
}
266+
267+
/// panics if payloads_serialized_length(payloads) > packet_data_len
268+
pub(crate) fn construct_onion_message_packet<HD: Writeable, P: Packet<Data = Vec<u8>>>(
269+
payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], packet_data_len: usize) -> P
270+
{
271+
let mut packet_data = vec![0; packet_data_len];
272+
273+
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
274+
chacha.process_in_place(&mut packet_data);
275+
276+
construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, packet_data, None)
277+
}
278+
279+
/// panics if payloads_serialized_length(payloads) > packet_data.len()
264280
fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
265281
mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P
266282
{

lightning/src/onion_message/blinded_route.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,25 @@ pub struct BlindedRoute {
2828
/// message's next hop and forward it along.
2929
///
3030
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
31-
introduction_node_id: PublicKey,
31+
pub(super) introduction_node_id: PublicKey,
3232
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
3333
/// message.
3434
///
3535
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
36-
blinding_point: PublicKey,
36+
pub(super) blinding_point: PublicKey,
3737
/// The hops composing the blinded route.
38-
blinded_hops: Vec<BlindedHop>,
38+
pub(super) blinded_hops: Vec<BlindedHop>,
3939
}
4040

4141
/// Used to construct the blinded hops portion of a blinded route. These hops cannot be identified
4242
/// by outside observers and thus can be used to hide the identity of the recipient.
4343
pub struct BlindedHop {
4444
/// The blinded node id of this hop in a blinded route.
45-
blinded_node_id: PublicKey,
45+
pub(super) blinded_node_id: PublicKey,
4646
/// The encrypted payload intended for this hop in a blinded route.
4747
// The node sending to this blinded route will later encode this payload into the onion packet for
4848
// this hop.
49-
encrypted_payload: Vec<u8>,
49+
pub(super) encrypted_payload: Vec<u8>,
5050
}
5151

5252
impl BlindedRoute {
@@ -78,7 +78,7 @@ fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
7878
let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
7979

8080
let mut prev_ss_and_blinded_node_id = None;
81-
utils::construct_keys_callback(secp_ctx, unblinded_path, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk| {
81+
utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
8282
if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
8383
if let Some(pk) = unblinded_pk {
8484
let payload = ForwardTlvs {
@@ -117,18 +117,18 @@ fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec
117117
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
118118
pub(crate) struct ForwardTlvs {
119119
/// The node id of the next hop in the onion message's path.
120-
next_node_id: PublicKey,
120+
pub(super) next_node_id: PublicKey,
121121
/// Senders to a blinded route use this value to concatenate the route they find to the
122122
/// introduction node with the blinded route.
123-
next_blinding_override: Option<PublicKey>,
123+
pub(super) next_blinding_override: Option<PublicKey>,
124124
}
125125

126126
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
127127
pub(crate) struct ReceiveTlvs {
128128
/// If `path_id` is `Some`, it is used to identify the blinded route that this onion message is
129129
/// sending to. This is useful for receivers to check that said blinded route is being used in
130130
/// the right context.
131-
path_id: Option<[u8; 32]>,
131+
pub(super) path_id: Option<[u8; 32]>,
132132
}
133133

134134
impl Writeable for ForwardTlvs {

lightning/src/onion_message/messenger.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
//! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for
1111
//! more information.
1212
13-
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
13+
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1414

1515
use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Sign};
1616
use ln::msgs;
17+
use ln::onion_utils;
18+
use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
19+
use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
20+
use super::utils;
1721
use util::logger::Logger;
1822

1923
use core::ops::Deref;
@@ -37,6 +41,23 @@ pub struct OnionMessenger<Signer: Sign, K: Deref, L: Deref>
3741
// custom_handler: CustomHandler, // handles custom onion messages
3842
}
3943

44+
/// The destination of an onion message.
45+
pub enum Destination {
46+
/// We're sending this onion message to a node.
47+
Node(PublicKey),
48+
/// We're sending this onion message to a blinded route.
49+
BlindedRoute(BlindedRoute),
50+
}
51+
52+
impl Destination {
53+
pub(super) fn num_hops(&self) -> usize {
54+
match self {
55+
Destination::Node(_) => 1,
56+
Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => blinded_hops.len(),
57+
}
58+
}
59+
}
60+
4061
impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
4162
where K::Target: KeysInterface<Signer = Signer>,
4263
L::Target: Logger,
@@ -53,6 +74,36 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
5374
logger,
5475
}
5576
}
77+
78+
/// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
79+
pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), secp256k1::Error> {
80+
let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
81+
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
82+
let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
83+
(intermediate_nodes[0], PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
84+
} else {
85+
match destination {
86+
Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
87+
Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, .. }) =>
88+
(introduction_node_id, blinding_point),
89+
}
90+
};
91+
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
92+
&self.secp_ctx, intermediate_nodes, destination, &blinding_secret)?;
93+
94+
let prng_seed = self.keys_manager.get_secure_random_bytes();
95+
let onion_packet = construct_onion_message_packet(packet_payloads, packet_keys, prng_seed);
96+
97+
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
98+
let pending_msgs = pending_per_peer_msgs.entry(introduction_node_id).or_insert(Vec::new());
99+
pending_msgs.push(
100+
msgs::OnionMessage {
101+
blinding_point,
102+
onion_routing_packet: onion_packet,
103+
}
104+
);
105+
Ok(())
106+
}
56107
}
57108

58109
// TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it
@@ -69,3 +120,90 @@ pub type SimpleArcOnionMessenger<L> = OnionMessenger<InMemorySigner, Arc<KeysMan
69120
///[`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
70121
///[`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
71122
pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger<InMemorySigner, &'a KeysManager, &'b L>;
123+
124+
/// Construct onion packet payloads and keys for sending an onion message along the given
125+
/// `unblinded_path` to the given `destination`.
126+
fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
127+
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey
128+
) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
129+
let num_hops = unblinded_path.len() + destination.num_hops();
130+
let mut payloads = Vec::with_capacity(num_hops);
131+
let mut onion_packet_keys = Vec::with_capacity(num_hops);
132+
133+
let (mut intro_node_id_blinding_pt, num_blinded_hops) = if let Destination::BlindedRoute(BlindedRoute {
134+
introduction_node_id, blinding_point, blinded_hops }) = &destination {
135+
(Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) } else { (None, 0) };
136+
let num_unblinded_hops = num_hops - num_blinded_hops;
137+
138+
let mut unblinded_path_idx = 0;
139+
let mut blinded_path_idx = 0;
140+
let mut prev_control_tlvs_ss = None;
141+
utils::construct_keys_callback(secp_ctx, unblinded_path, Some(destination), session_priv, |_, onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| {
142+
if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
143+
if let Some(ss) = prev_control_tlvs_ss.take() {
144+
payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
145+
ForwardTlvs {
146+
next_node_id: unblinded_pk_opt.unwrap(),
147+
next_blinding_override: None,
148+
}
149+
)), ss));
150+
}
151+
prev_control_tlvs_ss = Some(control_tlvs_ss);
152+
unblinded_path_idx += 1;
153+
} else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
154+
if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
155+
payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
156+
next_node_id: intro_node_id,
157+
next_blinding_override: Some(blinding_pt),
158+
})), control_tlvs_ss));
159+
}
160+
if let Some(encrypted_payload) = enc_payload_opt {
161+
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(encrypted_payload)),
162+
control_tlvs_ss));
163+
} else { debug_assert!(false); }
164+
blinded_path_idx += 1;
165+
} else if blinded_path_idx < num_blinded_hops - 1 && enc_payload_opt.is_some() {
166+
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
167+
control_tlvs_ss));
168+
blinded_path_idx += 1;
169+
} else if let Some(encrypted_payload) = enc_payload_opt {
170+
payloads.push((Payload::Receive {
171+
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
172+
}, control_tlvs_ss));
173+
}
174+
175+
let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
176+
onion_packet_keys.push(onion_utils::OnionKeys {
177+
#[cfg(test)]
178+
shared_secret: onion_packet_ss,
179+
#[cfg(test)]
180+
blinding_factor: [0; 32],
181+
ephemeral_pubkey,
182+
rho,
183+
mu,
184+
});
185+
})?;
186+
187+
if let Some(control_tlvs_ss) = prev_control_tlvs_ss {
188+
payloads.push((Payload::Receive {
189+
control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, })
190+
}, control_tlvs_ss));
191+
}
192+
193+
Ok((payloads, onion_packet_keys))
194+
}
195+
196+
fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Packet {
197+
// Spec rationale:
198+
// "`len` allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC
199+
// onion, but this should be used sparingly as it is reduces anonymity set, hence the
200+
// recommendation that it either look like an HTLC onion, or if larger, be a fixed size."
201+
let payloads_ser_len = onion_utils::payloads_serialized_length(&payloads);
202+
let hop_data_len = if payloads_ser_len <= SMALL_PACKET_HOP_DATA_LEN {
203+
SMALL_PACKET_HOP_DATA_LEN
204+
} else if payloads_ser_len <= BIG_PACKET_HOP_DATA_LEN {
205+
BIG_PACKET_HOP_DATA_LEN
206+
} else { payloads_ser_len };
207+
208+
onion_utils::construct_onion_message_packet::<_, _>(payloads, onion_keys, prng_seed, hop_data_len)
209+
}

lightning/src/onion_message/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ mod utils;
1616

1717
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
1818
pub use self::blinded_route::{BlindedRoute, BlindedHop};
19-
pub use self::messenger::{OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
19+
pub use self::messenger::{Destination, OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
2020
pub(crate) use self::packet::Packet;

lightning/src/onion_message/packet.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@ use bitcoin::secp256k1::PublicKey;
1313

1414
use ln::msgs::DecodeError;
1515
use ln::onion_utils;
16+
use super::blinded_route::{ForwardTlvs, ReceiveTlvs};
17+
use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
1618
use util::ser::{LengthRead, LengthReadable, Readable, Writeable, Writer};
1719

1820
use core::cmp;
1921
use io;
2022
use prelude::*;
2123

24+
// Per the spec, an onion message packet's `hop_data` field length should be
25+
// SMALL_PACKET_HOP_DATA_LEN if it fits, else BIG_PACKET_HOP_DATA_LEN if it fits.
26+
pub(super) const SMALL_PACKET_HOP_DATA_LEN: usize = 1300;
27+
pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768;
28+
2229
#[derive(Clone, Debug, PartialEq)]
2330
pub(crate) struct Packet {
2431
version: u8,
@@ -80,3 +87,72 @@ impl LengthReadable for Packet {
8087
})
8188
}
8289
}
90+
91+
/// Onion message payloads contain "control" TLVs and "data" TLVs. Control TLVs are used to route
92+
/// the onion message from hop to hop and for path verification, whereas data TLVs contain the onion
93+
/// message content itself, such as an invoice request.
94+
pub(super) enum Payload {
95+
/// This payload is for an intermediate hop.
96+
Forward(ForwardControlTlvs),
97+
/// This payload is for the final hop.
98+
Receive {
99+
control_tlvs: ReceiveControlTlvs,
100+
// Coming soon:
101+
// reply_path: Option<BlindedRoute>,
102+
// message: Message,
103+
}
104+
}
105+
106+
// Coming soon:
107+
// enum Message {
108+
// InvoiceRequest(InvoiceRequest),
109+
// Invoice(Invoice),
110+
// InvoiceError(InvoiceError),
111+
// CustomMessage<T>,
112+
// }
113+
114+
/// Forward control TLVs in their blinded and unblinded form.
115+
pub(super) enum ForwardControlTlvs {
116+
/// If we're sending to a blinded route, the node that constructed the blinded route has provided
117+
/// this hop's control TLVs, already encrypted into bytes.
118+
Blinded(Vec<u8>),
119+
/// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
120+
/// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
121+
/// them into an intermediate Vec. See [`super::blinded_route::ForwardTlvs`] for more info.
122+
Unblinded(ForwardTlvs),
123+
}
124+
125+
/// Receive control TLVs in their blinded and unblinded form.
126+
pub(super) enum ReceiveControlTlvs {
127+
/// See [`ForwardControlTlvs::Blinded`].
128+
Blinded(Vec<u8>),
129+
/// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_route::ReceiveTlvs`].
130+
Unblinded(ReceiveTlvs),
131+
}
132+
133+
// Uses the provided secret to simultaneously encode and encrypt the unblinded control TLVs.
134+
impl Writeable for (Payload, [u8; 32]) {
135+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
136+
match &self.0 {
137+
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) |
138+
Payload::Receive { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes)} => {
139+
encode_varint_length_prefixed_tlv!(w, {
140+
(4, encrypted_bytes, vec_type)
141+
})
142+
},
143+
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
144+
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
145+
encode_varint_length_prefixed_tlv!(w, {
146+
(4, write_adapter, required)
147+
})
148+
},
149+
Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs)} => {
150+
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
151+
encode_varint_length_prefixed_tlv!(w, {
152+
(4, write_adapter, required)
153+
})
154+
},
155+
}
156+
Ok(())
157+
}
158+
}

0 commit comments

Comments
 (0)