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.
This commit is contained in:
Valentine Wallace 2022-05-27 18:31:27 -07:00
parent 4c8dc2c2a0
commit 9051c38ebe
No known key found for this signature in database
GPG key ID: FD3E106A2CE099B4
6 changed files with 275 additions and 24 deletions

View file

@ -33,14 +33,14 @@ use io::{Cursor, Read};
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]
@ -52,7 +52,7 @@ pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
}
#[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
@ -260,7 +260,23 @@ impl AsMut<[u8]> for FixedSizeOnionPacket {
}
}
/// 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
{

View file

@ -28,25 +28,25 @@ pub struct BlindedRoute {
/// 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 {
@ -78,7 +78,7 @@ fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
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 {
@ -117,10 +117,10 @@ fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec
/// 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.
@ -128,7 +128,7 @@ pub(crate) struct ReceiveTlvs {
/// 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 {

View file

@ -10,10 +10,14 @@
//! 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;
@ -37,6 +41,23 @@ pub struct OnionMessenger<Signer: Sign, K: Deref, L: 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,
@ -53,6 +74,36 @@ 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: &[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
@ -69,3 +120,90 @@ pub type SimpleArcOnionMessenger<L> = OnionMessenger<InMemorySigner, Arc<KeysMan
///[`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)
}

View file

@ -16,5 +16,5 @@ mod utils;
// 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;

View file

@ -13,12 +13,19 @@ use bitcoin::secp256k1::PublicKey;
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,
@ -80,3 +87,72 @@ impl LengthReadable for Packet {
})
}
}
/// 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(())
}
}

View file

@ -16,14 +16,16 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
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();
@ -32,7 +34,7 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
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 {
@ -49,7 +51,14 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
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();
@ -73,7 +82,19 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
}
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(())
}