use core::convert::{AsMut, TryInto};
use core::ops::Deref;
-pub(super) struct OnionKeys {
+pub(crate) struct OnionKeys {
#[cfg(test)]
- pub(super) shared_secret: SharedSecret,
+ pub(crate) shared_secret: SharedSecret,
#[cfg(test)]
- pub(super) blinding_factor: [u8; 32],
- pub(super) ephemeral_pubkey: PublicKey,
- pub(super) rho: [u8; 32],
- pub(super) mu: [u8; 32],
+ pub(crate) blinding_factor: [u8; 32],
+ pub(crate) ephemeral_pubkey: PublicKey,
+ pub(crate) rho: [u8; 32],
+ pub(crate) mu: [u8; 32],
}
#[inline]
}
#[inline]
-pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
+pub(crate) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
assert_eq!(shared_secret.len(), 32);
({
let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
}
}
-/// panics if route_size_insane(payloads)
+pub(crate) fn payloads_serialized_length<HD: Writeable>(payloads: &Vec<HD>) -> usize {
+ payloads.iter().map(|p| p.serialized_length() + 32 /* HMAC */).sum()
+}
+
+/// panics if payloads_serialized_length(payloads) > packet_data_len
+pub(crate) fn construct_onion_message_packet<HD: Writeable, P: Packet<Data = Vec<u8>>>(
+ payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], packet_data_len: usize) -> P
+{
+ let mut packet_data = vec![0; packet_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, packet_data, None)
+}
+
+/// panics if payloads_serialized_length(payloads) > packet_data.len()
fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P
{
/// message's next hop and forward it along.
///
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
- introduction_node_id: PublicKey,
+ pub(super) introduction_node_id: PublicKey,
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
/// message.
///
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
- blinding_point: PublicKey,
+ pub(super) blinding_point: PublicKey,
/// The hops composing the blinded route.
- blinded_hops: Vec<BlindedHop>,
+ pub(super) blinded_hops: Vec<BlindedHop>,
}
/// Used to construct the blinded hops portion of a blinded route. These hops cannot be identified
/// by outside observers and thus can be used to hide the identity of the recipient.
pub struct BlindedHop {
/// The blinded node id of this hop in a blinded route.
- blinded_node_id: PublicKey,
+ pub(super) blinded_node_id: PublicKey,
/// The encrypted payload intended for this hop in a blinded route.
// The node sending to this blinded route will later encode this payload into the onion packet for
// this hop.
- encrypted_payload: Vec<u8>,
+ pub(super) encrypted_payload: Vec<u8>,
}
impl BlindedRoute {
let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
let mut prev_ss_and_blinded_node_id = None;
- utils::construct_keys_callback(secp_ctx, unblinded_path, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk| {
+ utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
if let Some(pk) = unblinded_pk {
let payload = ForwardTlvs {
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
pub(crate) struct ForwardTlvs {
/// The node id of the next hop in the onion message's path.
- next_node_id: PublicKey,
+ pub(super) next_node_id: PublicKey,
/// Senders to a blinded route use this value to concatenate the route they find to the
/// introduction node with the blinded route.
- next_blinding_override: Option<PublicKey>,
+ pub(super) next_blinding_override: Option<PublicKey>,
}
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
/// If `path_id` is `Some`, it is used to identify the blinded route that this onion message is
/// sending to. This is useful for receivers to check that said blinded route is being used in
/// the right context.
- path_id: Option<[u8; 32]>,
+ pub(super) path_id: Option<[u8; 32]>,
}
impl Writeable for ForwardTlvs {
//! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for
//! more information.
-use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Sign};
use ln::msgs;
+use ln::onion_utils;
+use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
+use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
+use super::utils;
use util::logger::Logger;
use core::ops::Deref;
// custom_handler: CustomHandler, // handles custom onion messages
}
+/// 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 {
+ pub(super) fn num_hops(&self) -> usize {
+ match self {
+ Destination::Node(_) => 1,
+ Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => blinded_hops.len(),
+ }
+ }
+}
+
impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
where K::Target: KeysInterface<Signer = Signer>,
L::Target: Logger,
logger,
}
}
+
+ /// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
+ pub fn send_onion_message(&self, intermediate_nodes: &[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], PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
+ } else {
+ match destination {
+ Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
+ Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, .. }) =>
+ (introduction_node_id, blinding_point),
+ }
+ };
+ let (packet_payloads, packet_keys) = packet_payloads_and_keys(
+ &self.secp_ctx, intermediate_nodes, destination, &blinding_secret)?;
+
+ let prng_seed = self.keys_manager.get_secure_random_bytes();
+ let onion_packet = construct_onion_message_packet(packet_payloads, packet_keys, prng_seed);
+
+ let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
+ let pending_msgs = pending_per_peer_msgs.entry(introduction_node_id).or_insert(Vec::new());
+ pending_msgs.push(
+ msgs::OnionMessage {
+ blinding_point,
+ onion_routing_packet: onion_packet,
+ }
+ );
+ Ok(())
+ }
}
// TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it
///[`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
///[`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger<InMemorySigner, &'a KeysManager, &'b L>;
+
+/// Construct onion packet payloads and keys for sending an onion message along the given
+/// `unblinded_path` to the given `destination`.
+fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
+ secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey
+) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
+ let num_hops = unblinded_path.len() + destination.num_hops();
+ let mut payloads = Vec::with_capacity(num_hops);
+ let mut onion_packet_keys = Vec::with_capacity(num_hops);
+
+ let (mut intro_node_id_blinding_pt, num_blinded_hops) = if let Destination::BlindedRoute(BlindedRoute {
+ introduction_node_id, blinding_point, blinded_hops }) = &destination {
+ (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) } else { (None, 0) };
+ let num_unblinded_hops = num_hops - num_blinded_hops;
+
+ let mut unblinded_path_idx = 0;
+ let mut blinded_path_idx = 0;
+ let mut prev_control_tlvs_ss = None;
+ 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| {
+ if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
+ if let Some(ss) = prev_control_tlvs_ss.take() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
+ ForwardTlvs {
+ next_node_id: unblinded_pk_opt.unwrap(),
+ next_blinding_override: None,
+ }
+ )), ss));
+ }
+ prev_control_tlvs_ss = Some(control_tlvs_ss);
+ unblinded_path_idx += 1;
+ } else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
+ if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
+ next_node_id: intro_node_id,
+ 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() {
+ payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
+ control_tlvs_ss));
+ blinded_path_idx += 1;
+ } else if let Some(encrypted_payload) = enc_payload_opt {
+ payloads.push((Payload::Receive {
+ control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
+ }, control_tlvs_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: [0; 32],
+ ephemeral_pubkey,
+ rho,
+ mu,
+ });
+ })?;
+
+ if let Some(control_tlvs_ss) = prev_control_tlvs_ss {
+ payloads.push((Payload::Receive {
+ control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, })
+ }, control_tlvs_ss));
+ }
+
+ Ok((payloads, onion_packet_keys))
+}
+
+fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Packet {
+ // Spec rationale:
+ // "`len` allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC
+ // onion, but this should be used sparingly as it is reduces anonymity set, hence the
+ // recommendation that it either look like an HTLC onion, or if larger, be a fixed size."
+ let payloads_ser_len = onion_utils::payloads_serialized_length(&payloads);
+ let hop_data_len = if payloads_ser_len <= SMALL_PACKET_HOP_DATA_LEN {
+ SMALL_PACKET_HOP_DATA_LEN
+ } else if payloads_ser_len <= BIG_PACKET_HOP_DATA_LEN {
+ BIG_PACKET_HOP_DATA_LEN
+ } else { payloads_ser_len };
+
+ onion_utils::construct_onion_message_packet::<_, _>(payloads, onion_keys, prng_seed, hop_data_len)
+}
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
pub use self::blinded_route::{BlindedRoute, BlindedHop};
-pub use self::messenger::{OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+pub use self::messenger::{Destination, OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
pub(crate) use self::packet::Packet;
use ln::msgs::DecodeError;
use ln::onion_utils;
+use super::blinded_route::{ForwardTlvs, ReceiveTlvs};
+use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
use util::ser::{LengthRead, LengthReadable, Readable, Writeable, Writer};
use core::cmp;
use io;
use prelude::*;
+// Per the spec, an onion message packet's `hop_data` field length should be
+// SMALL_PACKET_HOP_DATA_LEN if it fits, else BIG_PACKET_HOP_DATA_LEN if it fits.
+pub(super) const SMALL_PACKET_HOP_DATA_LEN: usize = 1300;
+pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768;
+
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Packet {
version: u8,
})
}
}
+
+/// Onion message payloads contain "control" TLVs and "data" TLVs. Control TLVs are used to route
+/// the onion message from hop to hop and for path verification, whereas data TLVs contain the onion
+/// message content itself, such as an invoice request.
+pub(super) enum Payload {
+ /// This payload is for an intermediate hop.
+ Forward(ForwardControlTlvs),
+ /// This payload is for the final hop.
+ Receive {
+ control_tlvs: ReceiveControlTlvs,
+ // Coming soon:
+ // reply_path: Option<BlindedRoute>,
+ // message: Message,
+ }
+}
+
+// Coming soon:
+// enum Message {
+// InvoiceRequest(InvoiceRequest),
+// Invoice(Invoice),
+// InvoiceError(InvoiceError),
+// CustomMessage<T>,
+// }
+
+/// Forward control TLVs in their blinded and unblinded form.
+pub(super) enum ForwardControlTlvs {
+ /// If we're sending to a blinded route, the node that constructed the blinded route has provided
+ /// this hop's control TLVs, already encrypted into bytes.
+ Blinded(Vec<u8>),
+ /// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
+ /// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
+ /// them into an intermediate Vec. See [`super::blinded_route::ForwardTlvs`] for more info.
+ Unblinded(ForwardTlvs),
+}
+
+/// Receive control TLVs in their blinded and unblinded form.
+pub(super) enum ReceiveControlTlvs {
+ /// See [`ForwardControlTlvs::Blinded`].
+ Blinded(Vec<u8>),
+ /// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_route::ReceiveTlvs`].
+ Unblinded(ReceiveTlvs),
+}
+
+// Uses the provided secret to simultaneously encode and encrypt the unblinded control TLVs.
+impl Writeable for (Payload, [u8; 32]) {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ match &self.0 {
+ Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) |
+ Payload::Receive { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes)} => {
+ encode_varint_length_prefixed_tlv!(w, {
+ (4, encrypted_bytes, vec_type)
+ })
+ },
+ Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
+ let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
+ encode_varint_length_prefixed_tlv!(w, {
+ (4, write_adapter, required)
+ })
+ },
+ Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs)} => {
+ let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
+ encode_varint_length_prefixed_tlv!(w, {
+ (4, write_adapter, required)
+ })
+ },
+ }
+ Ok(())
+ }
+}
use bitcoin::secp256k1::ecdh::SharedSecret;
use ln::onion_utils;
+use super::blinded_route::BlindedRoute;
+use super::messenger::Destination;
use prelude::*;
// TODO: DRY with onion_utils::construct_onion_keys_callback
#[inline]
pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
- FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>)>(
- secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey],
+ FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
+ secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
session_priv: &SecretKey, mut callback: FType
) -> Result<(), secp256k1::Error> {
let mut msg_blinding_point_priv = session_priv.clone();
let mut onion_packet_pubkey = msg_blinding_point.clone();
macro_rules! build_keys {
- ($pk: expr, $blinded: expr) => {
+ ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {{
let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
let blinded_hop_pk = if $blinded { $pk } else {
let rho = onion_utils::gen_rho_from_shared_secret(encrypted_data_ss.as_ref());
let unblinded_pk_opt = if $blinded { None } else { Some($pk) };
- callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt);
+ callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt, $encrypted_payload);
+ (encrypted_data_ss, onion_packet_ss)
+ }}
+ }
+
+ macro_rules! build_keys_in_loop {
+ ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {
+ let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload);
let msg_blinding_point_blinding_factor = {
let mut sha = Sha256::engine();
}
for pk in unblinded_path {
- build_keys!(*pk, false);
+ build_keys_in_loop!(*pk, false, None);
+ }
+ if let Some(dest) = destination {
+ match dest {
+ Destination::Node(pk) => {
+ build_keys!(pk, false, None);
+ },
+ Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => {
+ for hop in blinded_hops {
+ build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload));
+ }
+ },
+ }
}
Ok(())
}