Skip to content

Commit

Permalink
Support sending onion messages
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
valentinewallace committed Jun 1, 2022
1 parent 993a812 commit ab94a45
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 2 deletions.
202 changes: 200 additions & 2 deletions lightning/src/ln/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ use io::{self, Read};
use prelude::*;
use sync::{Arc, Mutex};

pub(crate) const SMALL_PACKET_LEN: usize = 1300;
pub(crate) const BIG_PACKET_LEN: usize = 32768;

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Packet {
pub(crate) version: u8,
Expand Down Expand Up @@ -79,6 +82,59 @@ impl ReadableArgs<u16> for Packet {
}
}

/// The payload of an onion message.
pub(crate) struct Payload {
/// Onion message payloads contain an encrypted TLV stream, containing both "control" TLVs and
/// sometimes user-provided custom "data" TLVs. See [`EncryptedTlvs`] for more information.
encrypted_tlvs: EncryptedTlvs,
// Coming soon:
// * custom TLVs
// * `message: Message` field
// * `reply_path: Option<BlindedRoute>` field
}

// Coming soon:
// enum Message {
// InvoiceRequest(InvoiceRequest),
// Invoice(Invoice),
// InvoiceError(InvoiceError),
// CustomMessage<T>,
// }

/// We want to avoid encoding and encrypting separately in order to avoid an intermediate Vec, thus
/// we encode and encrypt at the same time using the `SharedSecret` here.
impl Writeable for (Payload, SharedSecret) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match &self.0.encrypted_tlvs {
EncryptedTlvs::Blinded(encrypted_bytes) => {
encode_varint_length_prefixed_tlv!(w, {
(4, encrypted_bytes, vec_type)
})
},
EncryptedTlvs::Unblinded(control_tlvs) => {
encode_varint_length_prefixed_tlv!(w, {
(4, control_tlvs, (chacha, self.1))
})
}
}
Ok(())
}
}

/// Onion messages contain an encrypted TLV stream. This can be supplied by someone else, in the
/// case that we're sending to a blinded route, or created by us if we're constructing payloads for
/// unblinded hops in the onion message's path.
pub(crate) enum EncryptedTlvs {
/// If we're sending to a blinded route, the node that constructed the blinded route has provided
/// our onion message's `EncryptedTlvs`, already encrypted and encoded into bytes.
Blinded(Vec<u8>),
/// If we're receiving an onion message or constructing an onion message to send through any
/// unblinded nodes, we'll need to construct the onion message's `EncryptedTlvs` in their
/// unblinded state to avoid encoding them into an intermediate `Vec`.
// Below will later have an additional Vec<CustomTlv>
Unblinded(ControlTlvs),
}

/// Onion messages have "control" TLVs and "data" TLVs. Control TLVs are used to control the
/// direction and routing of an onion message from hop to hop, whereas data TLVs contain the onion
/// message content itself.
Expand Down Expand Up @@ -250,6 +306,23 @@ impl BlindedRoute {
}
}

/// The destination of an onion message.
pub enum Destination {
/// We're sending this onion message to a node.
Node(PublicKey),
/// We're sending this onion message to a blinded route.
BlindedRoute(BlindedRoute),
}

impl Destination {
fn num_hops(&self) -> usize {
match self {
Destination::Node(_) => 1,
Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => blinded_hops.len(),
}
}
}

/// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be
/// used to retrieve invoices and fulfill invoice requests from offers.
pub struct OnionMessenger<Signer: Sign, K: Deref, L: Deref>
Expand Down Expand Up @@ -281,6 +354,39 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
logger,
}
}

/// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
pub fn send_onion_message(&self, intermediate_nodes: Vec<PublicKey>, destination: Destination) -> Result<(), secp256k1::Error> {
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 {
(intermediate_nodes[0].clone(), PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
} else {
match destination {
Destination::Node(pk) => (pk.clone(), PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, .. }) =>
(introduction_node_id.clone(), blinding_point.clone()),
}
};
let (encrypted_data_keys, onion_packet_keys) = construct_sending_keys(
&self.secp_ctx, &intermediate_nodes, &destination, &blinding_secret)?;
let payloads = build_payloads(intermediate_nodes, destination, encrypted_data_keys);

let prng_seed = self.keys_manager.get_secure_random_bytes();
let onion_packet = onion_utils::construct_onion_message_packet(payloads, onion_packet_keys, prng_seed);

// TODO route_size_insane check
let mut pending_msg_events = self.pending_msg_events.lock().unwrap();
pending_msg_events.push(MessageSendEvent::SendOnionMessage {
node_id: introduction_node_id,
msg: msgs::OnionMessage {
blinding_point,
len: 1366,
onion_routing_packet: onion_packet,
}
});
Ok(())
}
}

impl<Signer: Sign, K: Deref, L: Deref> OnionMessageHandler for OnionMessenger<Signer, K, L>
Expand All @@ -302,9 +408,59 @@ impl<Signer: Sign, K: Deref, L: Deref> MessageSendEventsProvider for OnionMessen
}
}

/// Build an onion message's payloads for encoding in the onion packet.
fn build_payloads(intermediate_nodes: Vec<PublicKey>, destination: Destination, mut encrypted_tlvs_keys: Vec<SharedSecret>) -> Vec<(Payload, SharedSecret)> {
let num_intermediate_nodes = intermediate_nodes.len();
let num_payloads = num_intermediate_nodes + destination.num_hops();
assert_eq!(encrypted_tlvs_keys.len(), num_payloads);
let mut payloads = Vec::with_capacity(num_payloads);
let mut enc_tlv_keys = encrypted_tlvs_keys.drain(..);
for pk in intermediate_nodes.into_iter().skip(1) {
payloads.push((Payload {
encrypted_tlvs: EncryptedTlvs::Unblinded(ControlTlvs::Forward {
next_node_id: pk,
next_blinding_override: None,
})
}, enc_tlv_keys.next().unwrap()));
}
match destination {
Destination::Node(pk) => {
if num_intermediate_nodes != 0 {
payloads.push((Payload {
encrypted_tlvs: EncryptedTlvs::Unblinded(ControlTlvs::Forward {
next_node_id: pk,
next_blinding_override: None,
})
}, enc_tlv_keys.next().unwrap()));
}
payloads.push((Payload {
encrypted_tlvs: EncryptedTlvs::Unblinded(ControlTlvs::Receive {
path_id: None,
})
}, enc_tlv_keys.next().unwrap()));
},
Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, blinded_hops }) => {
if num_intermediate_nodes != 0 {
payloads.push((Payload {
encrypted_tlvs: EncryptedTlvs::Unblinded(ControlTlvs::Forward {
next_node_id: introduction_node_id,
next_blinding_override: Some(blinding_point),
})
}, enc_tlv_keys.next().unwrap()));
}
for hop in blinded_hops {
payloads.push((Payload {
encrypted_tlvs: EncryptedTlvs::Blinded(hop.encrypted_payload),
}, enc_tlv_keys.next().unwrap()));
}
}
}
payloads
}

#[allow(unused_assignments)]
#[inline]
fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification, FType: FnMut(PublicKey, SharedSecret, [u8; 32], PublicKey, SharedSecret)> (secp_ctx: &Secp256k1<T>, unblinded_path: &Vec<PublicKey>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification, FType: FnMut(PublicKey, SharedSecret, [u8; 32], PublicKey, SharedSecret)> (secp_ctx: &Secp256k1<T>, unblinded_path: &Vec<PublicKey>, destination: Option<&Destination>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
let mut msg_blinding_point_priv = session_priv.clone();
let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
Expand Down Expand Up @@ -352,6 +508,18 @@ fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification, FTyp
for pk in unblinded_path {
build_keys!(pk, false);
}
if let Some(dest) = destination {
match dest {
Destination::Node(pk) => {
build_keys!(pk, false);
},
Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => {
for hop in blinded_hops {
build_keys!(hop.blinded_node_id, true);
}
},
}
}
Ok(())
}

Expand All @@ -366,14 +534,44 @@ fn construct_blinded_route_keys<T: secp256k1::Signing + secp256k1::Verification>
let mut encrypted_data_keys = Vec::with_capacity(unblinded_path.len());
let mut blinded_node_pks = Vec::with_capacity(unblinded_path.len());

construct_keys_callback(secp_ctx, unblinded_path, session_priv, |blinded_hop_pubkey, _, _, _, encrypted_data_ss| {
construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_hop_pubkey, _, _, _, encrypted_data_ss| {
blinded_node_pks.push(blinded_hop_pubkey);
encrypted_data_keys.push(encrypted_data_ss);
})?;

Ok((encrypted_data_keys, blinded_node_pks))
}

/// Construct keys for sending an onion message along the given `path`.
///
/// Returns: `(encrypted_tlvs_keys, onion_packet_keys)`
/// where the encrypted tlvs keys are used to encrypt the [`EncryptedTlvs`] of the onion message and the
/// onion packet keys are used to encrypt the onion packet.
fn construct_sending_keys<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, unblinded_path: &Vec<PublicKey>, destination: &Destination, session_priv: &SecretKey
) -> Result<(Vec<SharedSecret>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
let num_hops = unblinded_path.len() + destination.num_hops();
let mut encrypted_data_keys = Vec::with_capacity(num_hops);
let mut onion_packet_keys = Vec::with_capacity(num_hops);

construct_keys_callback(secp_ctx, unblinded_path, Some(destination), session_priv, |_, onion_packet_ss, _blinding_factor, ephemeral_pubkey, encrypted_data_ss| {
encrypted_data_keys.push(encrypted_data_ss);

let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
onion_packet_keys.push(onion_utils::OnionKeys {
#[cfg(test)]
shared_secret: onion_packet_ss,
#[cfg(test)]
blinding_factor: _blinding_factor,
ephemeral_pubkey,
rho,
mu,
});
})?;

Ok((encrypted_data_keys, onion_packet_keys))
}

/// Useful for simplifying the parameters of [`SimpleArcChannelManager`] and
/// [`SimpleArcPeerManager`]. See their docs for more details.
pub type SimpleArcOnionMessenger<L> = OnionMessenger<InMemorySigner, Arc<KeysManager>, Arc<L>>;
Expand Down
18 changes: 18 additions & 0 deletions lightning/src/ln/onion_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,24 @@ pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_ke
payloads, onion_keys, PacketData::Payment(packet_data), Some(associated_data)).try_into().unwrap()
}

pub(super) fn construct_onion_message_packet(payloads: Vec<(onion_message::Payload, SharedSecret)>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32]) -> onion_message::Packet {
let payloads_serialized_len = payloads.iter()
.fold(0, |total, next_payload| total + next_payload.serialized_length() + 32 /* HMAC */ );
let hop_data_len = if payloads_serialized_len <= onion_message::SMALL_PACKET_LEN {
onion_message::SMALL_PACKET_LEN
} else if payloads_serialized_len <= onion_message::BIG_PACKET_LEN {
onion_message::BIG_PACKET_LEN
} else { payloads_serialized_len };

let mut packet_data = vec![0; hop_data_len];

let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
chacha.process_in_place(&mut packet_data);

construct_onion_packet_with_init_noise(
payloads, onion_keys, PacketData::Message(packet_data), None).try_into().unwrap()
}

#[cfg(test)]
// Used in testing to write bogus OnionHopDatas, which is otherwise not representable in
// msgs::OnionHopData.
Expand Down

0 comments on commit ab94a45

Please sign in to comment.