From a5b7cf2c69f2ac05279335006b1b172e2f0cb8c9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 13:22:53 -0400 Subject: [PATCH 01/13] Move blinded path util into blinded_path::utils This way it can be more easily reused for blinded payment paths. --- lightning/src/blinded_path/mod.rs | 16 ++++------------ lightning/src/blinded_path/utils.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index c52df1651fe..2022e06c99a 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -17,8 +17,8 @@ use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::onion_message::ControlTlvs; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; -use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; -use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer}; +use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer}; use core::mem; use core::ops::Deref; @@ -124,7 +124,7 @@ fn blinded_message_hops( }; blinded_hops.push(BlindedHop { blinded_node_id: prev_blinded_node_id, - encrypted_payload: encrypt_payload(payload, prev_ss), + encrypted_payload: utils::encrypt_payload(payload, prev_ss), }); } else { debug_assert!(false); } } @@ -135,21 +135,13 @@ fn blinded_message_hops( let final_payload = ReceiveTlvs { path_id: None }; blinded_hops.push(BlindedHop { blinded_node_id: final_blinded_node_id, - encrypted_payload: encrypt_payload(final_payload, final_ss), + encrypted_payload: utils::encrypt_payload(final_payload, final_ss), }); } else { debug_assert!(false) } Ok(blinded_hops) } -/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`]. -fn encrypt_payload(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { - let mut writer = VecWriter(Vec::new()); - let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload); - write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); - writer.0 -} - impl Writeable for BlindedPath { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.introduction_node_id.write(w)?; diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 1993ad93226..c1ecff4b4af 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -18,6 +18,8 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use super::BlindedPath; use crate::ln::onion_utils; use crate::onion_message::Destination; +use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter; +use crate::util::ser::{VecWriter, Writeable}; use crate::prelude::*; @@ -96,3 +98,12 @@ pub(crate) fn construct_keys_callback(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { + let mut writer = VecWriter(Vec::new()); + let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload); + write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); + writer.0 +} + From 381cc646c66538f69599ae4ee1224783eb5abe6a Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 13:42:57 -0400 Subject: [PATCH 02/13] Move some blinded path message code into message submodule. We'll similarly separate blinded path payments code into its own module. --- lightning/src/blinded_path/message.rs | 79 ++++++++++++++++++++++++ lightning/src/blinded_path/mod.rs | 77 +---------------------- lightning/src/onion_message/messenger.rs | 4 +- lightning/src/onion_message/packet.rs | 8 ++- 4 files changed, 90 insertions(+), 78 deletions(-) create mode 100644 lightning/src/blinded_path/message.rs diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs new file mode 100644 index 00000000000..024fb451d00 --- /dev/null +++ b/lightning/src/blinded_path/message.rs @@ -0,0 +1,79 @@ +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; +use crate::blinded_path::BlindedHop; +use crate::blinded_path::utils; +use crate::io; +use crate::prelude::*; +use crate::util::ser::{Writeable, Writer}; + +/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded +/// 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. + pub(crate) next_node_id: PublicKey, + /// Senders to a blinded path use this value to concatenate the route they find to the + /// introduction node with the blinded path. + pub(crate) next_blinding_override: Option, +} + +/// Similar to [`ForwardTlvs`], but these TLVs are for the final node. +pub(crate) struct ReceiveTlvs { + /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is + /// sending to. This is useful for receivers to check that said blinded path is being used in + /// the right context. + pub(crate) path_id: Option<[u8; 32]>, +} + +impl Writeable for ForwardTlvs { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + // TODO: write padding + encode_tlv_stream!(writer, { + (4, self.next_node_id, required), + (8, self.next_blinding_override, option) + }); + Ok(()) + } +} + +impl Writeable for ReceiveTlvs { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + // TODO: write padding + encode_tlv_stream!(writer, { + (6, self.path_id, option), + }); + Ok(()) + } +} + +/// Construct blinded onion message hops for the given `unblinded_path`. +pub(super) fn blinded_hops( + secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey +) -> Result, secp256k1::Error> { + 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, 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 { + next_node_id: pk, + next_blinding_override: None, + }; + blinded_hops.push(BlindedHop { + blinded_node_id: prev_blinded_node_id, + encrypted_payload: utils::encrypt_payload(payload, prev_ss), + }); + } else { debug_assert!(false); } + } + prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id)); + })?; + + if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id { + let final_payload = ReceiveTlvs { path_id: None }; + blinded_hops.push(BlindedHop { + blinded_node_id: final_blinded_node_id, + encrypted_payload: utils::encrypt_payload(final_payload, final_ss), + }); + } else { debug_assert!(false) } + + Ok(blinded_hops) +} diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 2022e06c99a..3ad96869f70 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -9,6 +9,7 @@ //! Creating blinded paths and related utilities live here. +pub(crate) mod message; pub(crate) mod utils; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; @@ -73,7 +74,7 @@ impl BlindedPath { Ok(BlindedPath { introduction_node_id, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), - blinded_hops: blinded_message_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, + blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) } @@ -89,7 +90,7 @@ impl BlindedPath { let mut s = Cursor::new(&encrypted_control_tlvs); let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs { + Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(message::ForwardTlvs { mut next_node_id, next_blinding_override, })}) => { let mut new_blinding_point = match next_blinding_override { @@ -108,40 +109,6 @@ impl BlindedPath { } } -/// Construct blinded onion message hops for the given `unblinded_path`. -fn blinded_message_hops( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey -) -> Result, secp256k1::Error> { - 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, 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 { - next_node_id: pk, - next_blinding_override: None, - }; - blinded_hops.push(BlindedHop { - blinded_node_id: prev_blinded_node_id, - encrypted_payload: utils::encrypt_payload(payload, prev_ss), - }); - } else { debug_assert!(false); } - } - prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id)); - })?; - - if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id { - let final_payload = ReceiveTlvs { path_id: None }; - blinded_hops.push(BlindedHop { - blinded_node_id: final_blinded_node_id, - encrypted_payload: utils::encrypt_payload(final_payload, final_ss), - }); - } else { debug_assert!(false) } - - Ok(blinded_hops) -} - impl Writeable for BlindedPath { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.introduction_node_id.write(w)?; @@ -177,41 +144,3 @@ impl_writeable!(BlindedHop, { encrypted_payload }); -/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded -/// 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. - pub(super) next_node_id: PublicKey, - /// Senders to a blinded path use this value to concatenate the route they find to the - /// introduction node with the blinded path. - pub(super) next_blinding_override: Option, -} - -/// Similar to [`ForwardTlvs`], but these TLVs are for the final node. -pub(crate) struct ReceiveTlvs { - /// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is - /// sending to. This is useful for receivers to check that said blinded path is being used in - /// the right context. - pub(super) path_id: Option<[u8; 32]>, -} - -impl Writeable for ForwardTlvs { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { - // TODO: write padding - encode_tlv_stream!(writer, { - (4, self.next_node_id, required), - (8, self.next_blinding_override, option) - }); - Ok(()) - } -} - -impl Writeable for ReceiveTlvs { - fn write(&self, writer: &mut W) -> Result<(), io::Error> { - // TODO: write padding - encode_tlv_stream!(writer, { - (6, self.path_id, option), - }); - Ok(()) - } -} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 7d5f4a22ec8..b6cdbbd7ca7 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,7 +15,9 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; -use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs, utils}; +use crate::blinded_path::BlindedPath; +use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::utils; use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient}; use crate::events::OnionMessageProvider; use crate::ln::features::{InitFeatures, NodeFeatures}; diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 8a5628f164c..9eb48a31086 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -12,7 +12,8 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::ecdh::SharedSecret; -use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::BlindedPath; +use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use super::messenger::CustomOnionMessageHandler; @@ -151,7 +152,8 @@ pub(super) enum ForwardControlTlvs { Blinded(Vec), /// 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 [`crate::blinded_path::ForwardTlvs`] for more info. + /// them into an intermediate Vec. See [`crate::blinded_path::message::ForwardTlvs`] for more + /// info. Unblinded(ForwardTlvs), } @@ -159,7 +161,7 @@ pub(super) enum ForwardControlTlvs { pub(super) enum ReceiveControlTlvs { /// See [`ForwardControlTlvs::Blinded`]. Blinded(Vec), - /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::ReceiveTlvs`]. + /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::message::ReceiveTlvs`]. Unblinded(ReceiveTlvs), } From fe5a076aa6aa535c441f56419a680f383fd24744 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 13:59:31 -0400 Subject: [PATCH 03/13] Move blinded message path util into message submodule --- lightning/src/blinded_path/message.rs | 42 ++++++++++++++++++++++-- lightning/src/blinded_path/mod.rs | 41 ++--------------------- lightning/src/onion_message/messenger.rs | 4 +-- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 024fb451d00..2549673b00f 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -1,9 +1,18 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; -use crate::blinded_path::BlindedHop; + +use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::blinded_path::utils; use crate::io; +use crate::io::Cursor; +use crate::ln::onion_utils; +use crate::onion_message::ControlTlvs; use crate::prelude::*; -use crate::util::ser::{Writeable, Writer}; +use crate::sign::{NodeSigner, Recipient}; +use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; +use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer}; + +use core::mem; +use core::ops::Deref; /// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded /// route, they are encoded into [`BlindedHop::encrypted_payload`]. @@ -77,3 +86,32 @@ pub(super) fn blinded_hops( Ok(blinded_hops) } + +// Advance the blinded onion message path by one hop, so make the second hop into the new +// introduction node. +pub(crate) fn advance_path_by_one( + path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1 +) -> Result<(), ()> where NS::Target: NodeSigner { + let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?; + let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); + let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload; + let mut s = Cursor::new(&encrypted_control_tlvs); + let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho) { + Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs { + mut next_node_id, next_blinding_override, + })}) => { + let mut new_blinding_point = match next_blinding_override { + Some(blinding_point) => blinding_point, + None => { + onion_utils::next_hop_pubkey(secp_ctx, path.blinding_point, + control_tlvs_ss.as_ref()).map_err(|_| ())? + } + }; + mem::swap(&mut path.blinding_point, &mut new_blinding_point); + mem::swap(&mut path.introduction_node_id, &mut next_node_id); + Ok(()) + }, + _ => Err(()) + } +} diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 3ad96869f70..6225f99869b 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -14,16 +14,11 @@ pub(crate) mod utils; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; -use crate::onion_message::ControlTlvs; +use crate::sign::EntropySource; use crate::ln::msgs::DecodeError; -use crate::ln::onion_utils; -use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; -use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer}; +use crate::util::ser::{Readable, Writeable, Writer}; -use core::mem; -use core::ops::Deref; -use crate::io::{self, Cursor}; +use crate::io; use crate::prelude::*; /// Onion messages and payments can be sent and received to blinded paths, which serve to hide the @@ -77,36 +72,6 @@ impl BlindedPath { blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) } - - // Advance the blinded onion message path by one hop, so make the second hop into the new - // introduction node. - pub(super) fn advance_message_path_by_one - (&mut self, node_signer: &NS, secp_ctx: &Secp256k1) -> Result<(), ()> - where NS::Target: NodeSigner - { - let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?; - let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); - let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload; - let mut s = Cursor::new(&encrypted_control_tlvs); - let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(message::ForwardTlvs { - mut next_node_id, next_blinding_override, - })}) => { - let mut new_blinding_point = match next_blinding_override { - Some(blinding_point) => blinding_point, - None => { - onion_utils::next_hop_pubkey(secp_ctx, self.blinding_point, - control_tlvs_ss.as_ref()).map_err(|_| ())? - } - }; - mem::swap(&mut self.blinding_point, &mut new_blinding_point); - mem::swap(&mut self.introduction_node_id, &mut next_node_id); - Ok(()) - }, - _ => Err(()) - } - } } impl Writeable for BlindedPath { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index b6cdbbd7ca7..09f207e36aa 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -16,7 +16,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; -use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs}; use crate::blinded_path::utils; use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient}; use crate::events::OnionMessageProvider; @@ -299,7 +299,7 @@ where let our_node_id = self.node_signer.get_node_id(Recipient::Node) .map_err(|()| SendError::GetNodeIdFailed)?; if blinded_path.introduction_node_id == our_node_id { - blinded_path.advance_message_path_by_one(&self.node_signer, &self.secp_ctx) + advance_path_by_one(blinded_path, &self.node_signer, &self.secp_ctx) .map_err(|()| SendError::BlindedPathAdvanceFailed)?; } } From 1b356619b3b178ef5a4f392056ed2fd5b7de2541 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 29 Mar 2023 23:55:59 -0400 Subject: [PATCH 04/13] Move Padding into blinded_path module for use in blinded payments --- lightning/src/blinded_path/utils.rs | 18 +++++++++++++++++- lightning/src/onion_message/packet.rs | 14 +------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index c1ecff4b4af..a188274a5d2 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -16,11 +16,13 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar}; use bitcoin::secp256k1::ecdh::SharedSecret; use super::BlindedPath; +use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::onion_message::Destination; use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter; -use crate::util::ser::{VecWriter, Writeable}; +use crate::util::ser::{Readable, VecWriter, Writeable}; +use crate::io; use crate::prelude::*; // TODO: DRY with onion_utils::construct_onion_keys_callback @@ -107,3 +109,17 @@ pub(super) fn encrypt_payload(payload: P, encrypted_tlvs_ss: [u8; writer.0 } +/// Blinded path encrypted payloads may be padded to ensure they are equal length. +/// +/// Reads padding to the end, ignoring what's read. +pub(crate) struct Padding {} +impl Readable for Padding { + #[inline] + fn read(reader: &mut R) -> Result { + loop { + let mut buf = [0; 8192]; + if reader.read(&mut buf[..])? == 0 { break; } + } + Ok(Self {}) + } +} diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 9eb48a31086..8b677e7bb61 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -14,6 +14,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use crate::blinded_path::BlindedPath; use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::utils::Padding; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use super::messenger::CustomOnionMessageHandler; @@ -306,16 +307,3 @@ impl Readable for ControlTlvs { Ok(payload_fmt) } } - -/// Reads padding to the end, ignoring what's read. -pub(crate) struct Padding {} -impl Readable for Padding { - #[inline] - fn read(reader: &mut R) -> Result { - loop { - let mut buf = [0; 8192]; - if reader.read(&mut buf[..])? == 0 { break; } - } - Ok(Self {}) - } -} From 7c1726b585af71897e15dcb83024d5cab55d4fcd Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 14:40:28 -0400 Subject: [PATCH 05/13] Update blinded path util to take iterator instead of slice Useful for blinded payment path construction. --- lightning/src/blinded_path/message.rs | 31 +++++----- lightning/src/blinded_path/utils.rs | 14 +++-- lightning/src/onion_message/messenger.rs | 76 ++++++++++++------------ 3 files changed, 64 insertions(+), 57 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 2549673b00f..ca0cb476655 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -60,21 +60,22 @@ pub(super) fn blinded_hops( 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, 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 { - next_node_id: pk, - next_blinding_override: None, - }; - blinded_hops.push(BlindedHop { - blinded_node_id: prev_blinded_node_id, - encrypted_payload: utils::encrypt_payload(payload, prev_ss), - }); - } else { debug_assert!(false); } - } - prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id)); - })?; + utils::construct_keys_callback(secp_ctx, unblinded_path.iter(), 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 { + next_node_id: pk, + next_blinding_override: None, + }; + blinded_hops.push(BlindedHop { + blinded_node_id: prev_blinded_node_id, + encrypted_payload: utils::encrypt_payload(payload, prev_ss), + }); + } else { debug_assert!(false); } + } + prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id)); + })?; if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id { let final_payload = ReceiveTlvs { path_id: None }; diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index a188274a5d2..1ac6519452c 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -27,11 +27,15 @@ use crate::prelude::*; // TODO: DRY with onion_utils::construct_onion_keys_callback #[inline] -pub(crate) fn construct_keys_callback, Option>)>( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], destination: Option, - session_priv: &SecretKey, mut callback: FType -) -> Result<(), secp256k1::Error> { +pub(crate) fn construct_keys_callback<'a, T, I, F>( + secp_ctx: &Secp256k1, unblinded_path: I, destination: Option, + session_priv: &SecretKey, mut callback: F +) -> Result<(), secp256k1::Error> +where + T: secp256k1::Signing + secp256k1::Verification, + I: Iterator, + F: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option, Option>), +{ 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(); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 09f207e36aa..1a6e2614b72 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -650,46 +650,48 @@ fn packet_payloads_and_keys Date: Fri, 16 Jun 2023 15:42:38 -0400 Subject: [PATCH 06/13] Minor BlindedHop docs update --- lightning/src/blinded_path/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 6225f99869b..691eda29a9f 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -40,13 +40,14 @@ pub struct BlindedPath { pub blinded_hops: Vec, } -/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified -/// by outside observers and thus can be used to hide the identity of the recipient. +/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to +/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers +/// and thus can be used to hide the identity of the recipient. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlindedHop { - /// The blinded node id of this hop in a blinded path. + /// The blinded node id of this hop in a [`BlindedPath`]. pub blinded_node_id: PublicKey, - /// The encrypted payload intended for this hop in a blinded path. + /// The encrypted payload intended for this hop in a [`BlindedPath`]. // The node sending to this blinded path will later encode this payload into the onion packet for // this hop. pub encrypted_payload: Vec, From 4a30d9e78a37b20b1d39e009ee3902649b21f65a Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 14 Aug 2023 19:54:31 -0400 Subject: [PATCH 07/13] Rename ser macro We want a similar macro for reading TLV streams without a length prefix, so rename this one to disambiguate. --- lightning/src/events/mod.rs | 18 +++++++++--------- lightning/src/ln/chan_utils.rs | 2 +- lightning/src/ln/channelmanager.rs | 4 ++-- lightning/src/offers/invoice_error.rs | 2 +- lightning/src/routing/gossip.rs | 4 ++-- lightning/src/routing/router.rs | 6 +++--- lightning/src/util/ser_macros.rs | 8 ++++---- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 567096b9852..a40a09fe512 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -821,12 +821,12 @@ pub enum Event { user_channel_id: u128, /// The reason the channel was closed. reason: ClosureReason, - /// Counterparty in the closed channel. - /// + /// Counterparty in the closed channel. + /// /// This field will be `None` for objects serialized prior to LDK 0.0.117. counterparty_node_id: Option, - /// Channel capacity of the closing channel (sats). - /// + /// Channel capacity of the closing channel (sats). + /// /// This field will be `None` for objects serialized prior to LDK 0.0.117. channel_capacity_sats: Option, }, @@ -1030,8 +1030,8 @@ impl Writeable for Event { (5, outbound_amount_forwarded_msat, option), }); }, - &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason, - ref counterparty_node_id, ref channel_capacity_sats + &Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason, + ref counterparty_node_id, ref channel_capacity_sats } => { 9u8.write(writer)?; // `user_channel_id` used to be a single u64 value. In order to remain backwards @@ -1368,7 +1368,7 @@ impl MaybeReadable for Event { }, 13u8 => { let f = || { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_id, required), (2, payment_hash, option), (4, path, required_vec), @@ -1433,7 +1433,7 @@ impl MaybeReadable for Event { }, 21u8 => { let f = || { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_id, required), (2, payment_hash, required), (4, path, required_vec), @@ -1449,7 +1449,7 @@ impl MaybeReadable for Event { }, 23u8 => { let f = || { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_id, required), (2, payment_hash, required), (4, path, required_vec), diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 85490afaec1..9ee564df59d 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1356,7 +1356,7 @@ impl Writeable for CommitmentTransaction { impl Readable for CommitmentTransaction { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, commitment_number, required), (2, to_broadcaster_value_sat, required), (4, to_countersignatory_value_sat, required), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index bd5b8e11212..eb13f6b4aa1 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -7809,7 +7809,7 @@ impl Writeable for ChannelDetails { impl Readable for ChannelDetails { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (1, inbound_scid_alias, option), (2, channel_id, required), (3, channel_type, option), @@ -8023,7 +8023,7 @@ impl Writeable for ClaimableHTLC { impl Readable for ClaimableHTLC { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, prev_hop, required), (1, total_msat, option), (2, value_ser, required), diff --git a/lightning/src/offers/invoice_error.rs b/lightning/src/offers/invoice_error.rs index 122049b9295..f587b271d12 100644 --- a/lightning/src/offers/invoice_error.rs +++ b/lightning/src/offers/invoice_error.rs @@ -70,7 +70,7 @@ impl Writeable for InvoiceError { impl Readable for InvoiceError { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (1, erroneous_field, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), (3, suggested_value, (option, encoding: (Vec, WithoutLength))), (5, error, (option, encoding: (UntrustedString, WithoutLength))), diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 64dbb45888b..434e4fe313d 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -1151,7 +1151,7 @@ impl Writeable for NodeAnnouncementInfo { impl Readable for NodeAnnouncementInfo { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, features, required), (2, last_update, required), (4, rgb, required), @@ -1259,7 +1259,7 @@ impl Readable for NodeInfo { // with zero inbound fees, causing that heuristic to provide little gain. Worse, because it // requires additional complexity and lookups during routing, it ends up being a // performance loss. Thus, we simply ignore the old field here and no longer track it. - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, _lowest_inbound_channel_fees, option), (2, announcement_info_wrap, upgradable_option), (4, channels, required_vec), diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 04fc9927bb7..891757a5b08 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -425,7 +425,7 @@ impl Readable for Route { cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta); paths.push(Path { hops, blinded_tail: None }); } - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)), (2, blinded_tails, optional_vec), }); @@ -467,7 +467,7 @@ impl Writeable for RouteParameters { impl Readable for RouteParameters { fn read(reader: &mut R) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_params, (required: ReadableArgs, 0)), (2, final_value_msat, required), (4, final_cltv_delta, option), @@ -575,7 +575,7 @@ impl Writeable for PaymentParameters { impl ReadableArgs for PaymentParameters { fn read(reader: &mut R, default_final_cltv_expiry_delta: u32) -> Result { - _init_and_read_tlv_fields!(reader, { + _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payee_pubkey, option), (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)), (2, features, (option: ReadableArgs, payee_pubkey.is_some())), diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index 2626509f34e..50451f7775c 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -794,7 +794,7 @@ macro_rules! _init_tlv_field_var { /// This is exported for use by other exported macros, do not use directly. #[doc(hidden)] #[macro_export] -macro_rules! _init_and_read_tlv_fields { +macro_rules! _init_and_read_len_prefixed_tlv_fields { ($reader: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { $( $crate::_init_tlv_field_var!($field, $fieldty); @@ -863,7 +863,7 @@ macro_rules! impl_writeable_tlv_based { impl $crate::util::ser::Readable for $st { fn read(reader: &mut R) -> Result { - $crate::_init_and_read_tlv_fields!(reader, { + $crate::_init_and_read_len_prefixed_tlv_fields!(reader, { $(($type, $field, $fieldty)),* }); Ok(Self { @@ -1015,7 +1015,7 @@ macro_rules! impl_writeable_tlv_based_enum { // Because read_tlv_fields creates a labeled loop, we cannot call it twice // in the same function body. Instead, we define a closure and call it. let f = || { - $crate::_init_and_read_tlv_fields!(reader, { + $crate::_init_and_read_len_prefixed_tlv_fields!(reader, { $(($type, $field, $fieldty)),* }); Ok($st::$variant_name { @@ -1069,7 +1069,7 @@ macro_rules! impl_writeable_tlv_based_enum_upgradable { // Because read_tlv_fields creates a labeled loop, we cannot call it twice // in the same function body. Instead, we define a closure and call it. let f = || { - $crate::_init_and_read_tlv_fields!(reader, { + $crate::_init_and_read_len_prefixed_tlv_fields!(reader, { $(($type, $field, $fieldty)),* }); Ok(Some($st::$variant_name { From cf64e3fba52d7b7f6abce352948d78b97bcb9700 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 23 Jun 2023 14:55:43 -0400 Subject: [PATCH 08/13] Add new _init_and_read_tlv_stream ser macro Useful for when you want to use _init_and_read_len_prefixed_tlv_fields but there is no length byte at the start of the TLV stream. --- lightning/src/onion_message/packet.rs | 11 ++++------- lightning/src/util/ser_macros.rs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 8b677e7bb61..8896941c875 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -274,19 +274,16 @@ pub(crate) enum ControlTlvs { } impl Readable for ControlTlvs { - fn read(mut r: &mut R) -> Result { - let mut _padding: Option = None; - let mut _short_channel_id: Option = None; - let mut next_node_id: Option = None; - let mut path_id: Option<[u8; 32]> = None; - let mut next_blinding_override: Option = None; - decode_tlv_stream!(&mut r, { + fn read(r: &mut R) -> Result { + _init_and_read_tlv_stream!(r, { (1, _padding, option), (2, _short_channel_id, option), (4, next_node_id, option), (6, path_id, option), (8, next_blinding_override, option), }); + let _padding: Option = _padding; + let _short_channel_id: Option = _short_channel_id; let valid_fwd_fmt = next_node_id.is_some() && path_id.is_none(); let valid_recv_fmt = next_node_id.is_none() && next_blinding_override.is_none(); diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index 50451f7775c..4f1fada952a 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -806,6 +806,19 @@ macro_rules! _init_and_read_len_prefixed_tlv_fields { } } +/// Equivalent to running [`_init_tlv_field_var`] then [`decode_tlv_stream`]. +macro_rules! _init_and_read_tlv_stream { + ($reader: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { + $( + $crate::_init_tlv_field_var!($field, $fieldty); + )* + + $crate::decode_tlv_stream!($reader, { + $(($type, $field, $fieldty)),* + }); + } +} + /// Implements [`Readable`]/[`Writeable`] for a struct storing it as a set of TLVs /// If `$fieldty` is `required`, then `$field` is a required field that is not an [`Option`] nor a [`Vec`]. /// If `$fieldty` is `(default_value, $default)`, then `$field` will be set to `$default` if not present. From d224f980ed208445d2425f697eb4563aaa432fa3 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 9 Aug 2023 14:07:58 -0700 Subject: [PATCH 09/13] Simplify onion message blinded hop construction Also adds a util for general blinded hop creation to be reused for blinded payment paths. --- lightning/src/blinded_path/message.rs | 35 ++++++--------------------- lightning/src/blinded_path/utils.rs | 26 ++++++++++++++++++-- lightning/src/onion_message/packet.rs | 12 ++++++++- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index ca0cb476655..d2e81444ef6 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -57,35 +57,14 @@ impl Writeable for ReceiveTlvs { pub(super) fn blinded_hops( secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey ) -> Result, secp256k1::Error> { - let mut blinded_hops = Vec::with_capacity(unblinded_path.len()); + let blinded_tlvs = unblinded_path.iter() + .skip(1) // The first node's TLVs contains the next node's pubkey + .map(|pk| { + ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None }) + }) + .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None }))); - let mut prev_ss_and_blinded_node_id = None; - utils::construct_keys_callback(secp_ctx, unblinded_path.iter(), 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 { - next_node_id: pk, - next_blinding_override: None, - }; - blinded_hops.push(BlindedHop { - blinded_node_id: prev_blinded_node_id, - encrypted_payload: utils::encrypt_payload(payload, prev_ss), - }); - } else { debug_assert!(false); } - } - prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id)); - })?; - - if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id { - let final_payload = ReceiveTlvs { path_id: None }; - blinded_hops.push(BlindedHop { - blinded_node_id: final_blinded_node_id, - encrypted_payload: utils::encrypt_payload(final_payload, final_ss), - }); - } else { debug_assert!(false) } - - Ok(blinded_hops) + utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv) } // Advance the blinded onion message path by one hop, so make the second hop into the new diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 1ac6519452c..9b1ce50b10e 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -15,7 +15,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar}; use bitcoin::secp256k1::ecdh::SharedSecret; -use super::BlindedPath; +use super::{BlindedHop, BlindedPath}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::onion_message::Destination; @@ -105,8 +105,30 @@ where Ok(()) } +// Panics if `unblinded_tlvs` length is less than `unblinded_pks` length +pub(super) fn construct_blinded_hops<'a, T, I1, I2>( + secp_ctx: &Secp256k1, unblinded_pks: I1, mut unblinded_tlvs: I2, session_priv: &SecretKey +) -> Result, secp256k1::Error> +where + T: secp256k1::Signing + secp256k1::Verification, + I1: ExactSizeIterator, + I2: Iterator, + I2::Item: Writeable +{ + let mut blinded_hops = Vec::with_capacity(unblinded_pks.len()); + construct_keys_callback( + secp_ctx, unblinded_pks, None, session_priv, + |blinded_node_id, _, _, encrypted_payload_rho, _, _| { + blinded_hops.push(BlindedHop { + blinded_node_id, + encrypted_payload: encrypt_payload(unblinded_tlvs.next().unwrap(), encrypted_payload_rho), + }); + })?; + Ok(blinded_hops) +} + /// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`]. -pub(super) fn encrypt_payload(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { +fn encrypt_payload(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { let mut writer = VecWriter(Vec::new()); let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload); write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 8896941c875..9eb2e21c265 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -265,7 +265,8 @@ ReadableArgs<(SharedSecret, &H, &L)> for Payload<(&self, w: &mut W) -> Result<(), io::Error> { + match self { + Self::Forward(tlvs) => tlvs.write(w), + Self::Receive(tlvs) => tlvs.write(w), + } + } +} From 76f8cc1cc66a27124b656d3a50fbf5792e9dd195 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Jun 2023 15:43:13 -0400 Subject: [PATCH 10/13] Support constructing BlindedPaths for payments. --- lightning/src/blinded_path/mod.rs | 22 ++++ lightning/src/blinded_path/payment.rs | 162 ++++++++++++++++++++++++++ lightning/src/blinded_path/utils.rs | 4 +- 3 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 lightning/src/blinded_path/payment.rs diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 691eda29a9f..89087a10cd4 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -9,6 +9,7 @@ //! Creating blinded paths and related utilities live here. +pub mod payment; pub(crate) mod message; pub(crate) mod utils; @@ -73,6 +74,27 @@ impl BlindedPath { blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) } + + /// Create a blinded path for a payment, to be forwarded along `path`. The last node + /// in `path` will be the destination node. + /// + /// Errors if `path` is empty or a node id in `path` is invalid. + // TODO: make all payloads the same size with padding + add dummy hops + pub fn new_for_payment( + intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey, + payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1 + ) -> Result { + let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); + let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); + + Ok(BlindedPath { + introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0), + blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), + blinded_hops: payment::blinded_hops( + secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret + ).map_err(|_| ())?, + }) + } } impl Writeable for BlindedPath { diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs new file mode 100644 index 00000000000..0f6cf01858d --- /dev/null +++ b/lightning/src/blinded_path/payment.rs @@ -0,0 +1,162 @@ +//! Data structures and methods for constructing [`BlindedPath`]s to send a payment over. +//! +//! [`BlindedPath`]: crate::blinded_path::BlindedPath + +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; + +use crate::blinded_path::BlindedHop; +use crate::blinded_path::utils; +use crate::io; +use crate::ln::PaymentSecret; +use crate::ln::features::BlindedHopFeatures; +use crate::ln::msgs::DecodeError; +use crate::prelude::*; +use crate::util::ser::{Readable, Writeable, Writer}; + +/// Data to construct a [`BlindedHop`] for forwarding a payment. +pub struct ForwardTlvs { + /// The short channel id this payment should be forwarded out over. + short_channel_id: u64, + /// Payment parameters for relaying over [`Self::short_channel_id`]. + payment_relay: PaymentRelay, + /// Payment constraints for relaying over [`Self::short_channel_id`]. + payment_constraints: PaymentConstraints, + /// Supported and required features when relaying a payment onion containing this object's + /// corresponding [`BlindedHop::encrypted_payload`]. + /// + /// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload + features: BlindedHopFeatures, +} + +/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and +/// may not be valid if received by another lightning implementation. +pub struct ReceiveTlvs { + /// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together. + payment_secret: PaymentSecret, + /// Constraints for the receiver of this payment. + payment_constraints: PaymentConstraints, +} + +/// Data to construct a [`BlindedHop`] for sending a payment over. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub(crate) enum BlindedPaymentTlvs { + /// This blinded payment data is for a forwarding node. + Forward(ForwardTlvs), + /// This blinded payment data is for the receiving node. + Receive(ReceiveTlvs), +} + +// Used to include forward and receive TLVs in the same iterator for encoding. +enum BlindedPaymentTlvsRef<'a> { + Forward(&'a ForwardTlvs), + Receive(&'a ReceiveTlvs), +} + +/// Parameters for relaying over a given [`BlindedHop`]. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub struct PaymentRelay { + /// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub cltv_expiry_delta: u16, + /// Liquidity fee charged (in millionths of the amount transferred) for relaying a payment over + /// this [`BlindedHop`], (i.e., 10,000 is 1%). + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub fee_proportional_millionths: u32, + /// Base fee charged (in millisatoshi) for relaying a payment over this [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub fee_base_msat: u32, +} + +/// Constraints for relaying over a given [`BlindedHop`]. +/// +/// [`BlindedHop`]: crate::blinded_path::BlindedHop +pub struct PaymentConstraints { + /// The maximum total CLTV delta that is acceptable when relaying a payment over this + /// [`BlindedHop`]. + /// + ///[`BlindedHop`]: crate::blinded_path::BlindedHop + pub max_cltv_expiry: u32, + /// The minimum value, in msat, that may be relayed over this [`BlindedHop`]. + pub htlc_minimum_msat: u64, +} + +impl_writeable_tlv_based!(ForwardTlvs, { + (2, short_channel_id, required), + (10, payment_relay, required), + (12, payment_constraints, required), + (14, features, required), +}); + +impl_writeable_tlv_based!(ReceiveTlvs, { + (12, payment_constraints, required), + (65536, payment_secret, required), +}); + +impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + // TODO: write padding + match self { + Self::Forward(tlvs) => tlvs.write(w)?, + Self::Receive(tlvs) => tlvs.write(w)?, + } + Ok(()) + } +} + +impl Readable for BlindedPaymentTlvs { + fn read(r: &mut R) -> Result { + _init_and_read_tlv_stream!(r, { + (1, _padding, option), + (2, scid, option), + (10, payment_relay, option), + (12, payment_constraints, required), + (14, features, option), + (65536, payment_secret, option), + }); + let _padding: Option = _padding; + + if let Some(short_channel_id) = scid { + if payment_secret.is_some() { return Err(DecodeError::InvalidValue) } + Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { + short_channel_id, + payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + features: features.ok_or(DecodeError::InvalidValue)?, + })) + } else { + if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) } + Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { + payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, + payment_constraints: payment_constraints.0.unwrap(), + })) + } + } +} + +/// Construct blinded payment hops for the given `intermediate_nodes` and payee info. +pub(super) fn blinded_hops( + secp_ctx: &Secp256k1, intermediate_nodes: &[(PublicKey, ForwardTlvs)], + payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey +) -> Result, secp256k1::Error> { + let pks = intermediate_nodes.iter().map(|(pk, _)| pk) + .chain(core::iter::once(&payee_node_id)); + let tlvs = intermediate_nodes.iter().map(|(_, tlvs)| BlindedPaymentTlvsRef::Forward(tlvs)) + .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); + utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) +} + +impl_writeable_msg!(PaymentRelay, { + cltv_expiry_delta, + fee_proportional_millionths, + fee_base_msat +}, {}); + +impl_writeable_msg!(PaymentConstraints, { + max_cltv_expiry, + htlc_minimum_msat +}, {}); diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 9b1ce50b10e..e1feed97a27 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -111,11 +111,11 @@ pub(super) fn construct_blinded_hops<'a, T, I1, I2>( ) -> Result, secp256k1::Error> where T: secp256k1::Signing + secp256k1::Verification, - I1: ExactSizeIterator, + I1: Iterator, I2: Iterator, I2::Item: Writeable { - let mut blinded_hops = Vec::with_capacity(unblinded_pks.len()); + let mut blinded_hops = Vec::with_capacity(unblinded_pks.size_hint().0); construct_keys_callback( secp_ctx, unblinded_pks, None, session_priv, |blinded_node_id, _, _, encrypted_payload_rho, _, _| { From 0ddd3cb684d32ac20718701bcab112c37b85ee62 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 1 Aug 2023 11:55:27 -0700 Subject: [PATCH 11/13] Blinded paths: rename encrypted_tlvs_ss to *_rho for precision The previous name can be confused for the shared secret that the rho is derived from. --- lightning/src/blinded_path/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index e1feed97a27..c62b4e6c261 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -128,9 +128,9 @@ where } /// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`]. -fn encrypt_payload(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { +fn encrypt_payload(payload: P, encrypted_tlvs_rho: [u8; 32]) -> Vec { let mut writer = VecWriter(Vec::new()); - let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload); + let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_rho, &payload); write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); writer.0 } From ebb0676e85f2c335be3213edf1081e0f6a59d777 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 9 Aug 2023 14:29:35 -0700 Subject: [PATCH 12/13] Fix documentation on onion message packet ControlTlvs --- lightning/src/onion_message/packet.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 9eb2e21c265..a76371b39eb 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -264,9 +264,9 @@ ReadableArgs<(SharedSecret, &H, &L)> for Payload< Date: Wed, 23 Aug 2023 11:24:25 -0400 Subject: [PATCH 13/13] Document _init_and_read_* ser macro requirements --- lightning/src/util/ser_macros.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index 4f1fada952a..247fdf60074 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -791,6 +791,9 @@ macro_rules! _init_tlv_field_var { /// Equivalent to running [`_init_tlv_field_var`] then [`read_tlv_fields`]. /// +/// If any unused values are read, their type MUST be specified or else `rustc` will read them as an +/// `i64`. +/// /// This is exported for use by other exported macros, do not use directly. #[doc(hidden)] #[macro_export] @@ -807,6 +810,9 @@ macro_rules! _init_and_read_len_prefixed_tlv_fields { } /// Equivalent to running [`_init_tlv_field_var`] then [`decode_tlv_stream`]. +/// +/// If any unused values are read, their type MUST be specified or else `rustc` will read them as an +/// `i64`. macro_rules! _init_and_read_tlv_stream { ($reader: ident, {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}) => { $(