From: Valentine Wallace Date: Thu, 16 Mar 2023 01:56:57 +0000 (-0400) Subject: Move blinded_path and its utils into a new module X-Git-Tag: v0.0.115~5^2~16 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=efed905a4f90a3ca86b08cd6fb111e7c790ce20d;p=rust-lightning Move blinded_path and its utils into a new module --- diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index aa3045ccb..5239c1e79 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -10,13 +10,13 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; use core::convert::{Infallible, TryFrom}; +use lightning::blinded_path::BlindedPath; use lightning::chain::keysinterface::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice}; use lightning::offers::invoice_request::InvoiceRequest; use lightning::offers::parse::SemanticError; -use lightning::onion_message::BlindedPath; use lightning::util::ser::Writeable; #[inline] diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 9adaa3e95..fab303050 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -10,13 +10,13 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; use core::convert::{Infallible, TryFrom}; +use lightning::blinded_path::BlindedPath; use lightning::chain::keysinterface::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; use lightning::offers::invoice::{BlindedPayInfo, UnsignedInvoice}; use lightning::offers::parse::SemanticError; use lightning::offers::refund::Refund; -use lightning::onion_message::BlindedPath; use lightning::util::ser::Writeable; #[inline] diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs new file mode 100644 index 000000000..a4128d6a3 --- /dev/null +++ b/lightning/src/blinded_path/mod.rs @@ -0,0 +1,232 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Creating blinded paths and related utilities live here. + +pub(crate) mod utils; + +use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; + +use crate::chain::keysinterface::{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 core::mem; +use core::ops::Deref; +use crate::io::{self, Cursor}; +use crate::prelude::*; + +/// Onion messages can be sent and received to blinded paths, which serve to hide the identity of +/// the recipient. +#[derive(Clone, Debug, PartialEq)] +pub struct BlindedPath { + /// To send to a blinded path, the sender first finds a route to the unblinded + /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion + /// message's next hop and forward it along. + /// + /// [`encrypted_payload`]: BlindedHop::encrypted_payload + pub(crate) introduction_node_id: PublicKey, + /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion + /// message. + /// + /// [`encrypted_payload`]: BlindedHop::encrypted_payload + pub(crate) blinding_point: PublicKey, + /// The hops composing the blinded path. + pub(crate) 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. +#[derive(Clone, Debug, PartialEq)] +pub struct BlindedHop { + /// The blinded node id of this hop in a blinded path. + pub(crate) blinded_node_id: PublicKey, + /// The encrypted payload intended for this hop in a blinded path. + // The node sending to this blinded path will later encode this payload into the onion packet for + // this hop. + pub(crate) encrypted_payload: Vec, +} + +impl BlindedPath { + /// Create a blinded path to be forwarded along `node_pks`. The last node pubkey in `node_pks` + /// will be the destination node. + /// + /// Errors if less than two hops are provided or if `node_pk`(s) are invalid. + // TODO: make all payloads the same size with padding + add dummy hops + pub fn new + (node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1) -> Result + { + if node_pks.len() < 2 { return Err(()) } + let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); + let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); + let introduction_node_id = node_pks[0]; + + Ok(BlindedPath { + introduction_node_id, + blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), + blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, + }) + } + + // Advance the blinded path by one hop, so make the second hop into the new introduction node. + pub(super) fn advance_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(ForwardTlvs { + mut next_node_id, next_blinding_override, + })}) => { + let mut new_blinding_point = match next_blinding_override { + Some(blinding_point) => blinding_point, + None => { + let blinding_factor = { + let mut sha = Sha256::engine(); + sha.input(&self.blinding_point.serialize()[..]); + sha.input(control_tlvs_ss.as_ref()); + Sha256::from_engine(sha).into_inner() + }; + self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap()) + .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(()) + } + } +} + +/// Construct blinded hops for the given `unblinded_path`. +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: 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: 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)?; + self.blinding_point.write(w)?; + (self.blinded_hops.len() as u8).write(w)?; + for hop in &self.blinded_hops { + hop.write(w)?; + } + Ok(()) + } +} + +impl Readable for BlindedPath { + fn read(r: &mut R) -> Result { + let introduction_node_id = Readable::read(r)?; + let blinding_point = Readable::read(r)?; + let num_hops: u8 = Readable::read(r)?; + if num_hops == 0 { return Err(DecodeError::InvalidValue) } + let mut blinded_hops: Vec = Vec::with_capacity(num_hops.into()); + for _ in 0..num_hops { + blinded_hops.push(Readable::read(r)?); + } + Ok(BlindedPath { + introduction_node_id, + blinding_point, + blinded_hops, + }) + } +} + +impl_writeable!(BlindedHop, { + blinded_node_id, + 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/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs new file mode 100644 index 000000000..1993ad932 --- /dev/null +++ b/lightning/src/blinded_path/utils.rs @@ -0,0 +1,98 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Onion message utility methods live here. + +use bitcoin::hashes::{Hash, HashEngine}; +use bitcoin::hashes::hmac::{Hmac, HmacEngine}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar}; +use bitcoin::secp256k1::ecdh::SharedSecret; + +use super::BlindedPath; +use crate::ln::onion_utils; +use crate::onion_message::Destination; + +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> { + 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(); + let mut onion_packet_pubkey = msg_blinding_point.clone(); + + macro_rules! build_keys { + ($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 hop_pk_blinding_factor = { + let mut hmac = HmacEngine::::new(b"blinded_node_id"); + hmac.input(encrypted_data_ss.as_ref()); + Hmac::from_engine(hmac).into_inner() + }; + $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())? + }; + let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv); + + 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, $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(); + sha.input(&msg_blinding_point.serialize()[..]); + sha.input(encrypted_data_ss.as_ref()); + Sha256::from_engine(sha).into_inner() + }; + + msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?; + msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv); + + let onion_packet_pubkey_blinding_factor = { + let mut sha = Sha256::engine(); + sha.input(&onion_packet_pubkey.serialize()[..]); + sha.input(onion_packet_ss.as_ref()); + Sha256::from_engine(sha).into_inner() + }; + onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?; + onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv); + }; + } + + for pk in unblinded_path { + build_keys_in_loop!(*pk, false, None); + } + if let Some(dest) = destination { + match dest { + Destination::Node(pk) => { + build_keys!(pk, false, None); + }, + Destination::BlindedPath(BlindedPath { blinded_hops, .. }) => { + for hop in blinded_hops { + build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload)); + } + }, + } + } + Ok(()) +} diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index ad16914c3..668f752e6 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -81,6 +81,7 @@ pub mod ln; pub mod offers; pub mod routing; pub mod onion_message; +pub mod blinded_path; pub mod events; #[cfg(feature = "std")] diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index b2717f733..5c3148008 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -29,7 +29,7 @@ //! //! # use lightning::ln::PaymentHash; //! # use lightning::offers::invoice::BlindedPayInfo; -//! # use lightning::onion_message::BlindedPath; +//! # use lightning::blinded_path::BlindedPath; //! # //! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() } //! # fn create_payment_hash() -> PaymentHash { unimplemented!() } @@ -104,6 +104,7 @@ use bitcoin::util::schnorr::TweakedPublicKey; use core::convert::{Infallible, TryFrom}; use core::time::Duration; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures}; use crate::ln::inbound_payment::ExpandedKey; @@ -115,7 +116,6 @@ use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents}; use crate::offers::signer; -use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer}; use crate::prelude::*; @@ -926,6 +926,7 @@ mod tests { use bitcoin::util::schnorr::TweakedPublicKey; use core::convert::TryFrom; use core::time::Duration; + use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::chain::keysinterface::KeyMaterial; use crate::ln::features::Bolt12InvoiceFeatures; use crate::ln::inbound_payment::ExpandedKey; @@ -937,7 +938,6 @@ mod tests { use crate::offers::payer::PayerTlvStreamRef; use crate::offers::refund::RefundBuilder; use crate::offers::test_utils::*; - use crate::onion_message::{BlindedHop, BlindedPath}; use crate::util::ser::{BigSize, Iterable, Writeable}; trait ToBytes { diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 92fabd6fd..e8aeb2c82 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -60,6 +60,7 @@ use core::convert::{Infallible, TryFrom}; use core::ops::Deref; use crate::chain::keysinterface::EntropySource; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; @@ -70,7 +71,6 @@ use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamR use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; -use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index d2918e809..2d25f8d22 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -27,7 +27,7 @@ //! use lightning::offers::parse::ParseError; //! use lightning::util::ser::{Readable, Writeable}; //! -//! # use lightning::onion_message::BlindedPath; +//! # use lightning::blinded_path::BlindedPath; //! # #[cfg(feature = "std")] //! # use std::time::SystemTime; //! # @@ -76,6 +76,7 @@ use core::str::FromStr; use core::time::Duration; use crate::chain::keysinterface::EntropySource; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::MAX_VALUE_MSAT; @@ -83,7 +84,6 @@ use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceReq use crate::offers::merkle::TlvStream; use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; -use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -832,13 +832,13 @@ mod tests { use core::convert::TryFrom; use core::num::NonZeroU64; use core::time::Duration; + use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::chain::keysinterface::KeyMaterial; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::parse::{ParseError, SemanticError}; use crate::offers::test_utils::*; - use crate::onion_message::{BlindedHop, BlindedPath}; use crate::util::ser::{BigSize, Writeable}; use crate::util::string::PrintableString; diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index f677a2a9c..1aa36167b 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -32,7 +32,7 @@ //! use lightning::offers::refund::{Refund, RefundBuilder}; //! use lightning::util::ser::{Readable, Writeable}; //! -//! # use lightning::onion_message::BlindedPath; +//! # use lightning::blinded_path::BlindedPath; //! # #[cfg(feature = "std")] //! # use std::time::SystemTime; //! # @@ -80,6 +80,7 @@ use core::str::FromStr; use core::time::Duration; use crate::chain::keysinterface::EntropySource; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; @@ -90,7 +91,6 @@ use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; -use crate::onion_message::BlindedPath; use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -697,6 +697,7 @@ mod tests { use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey}; use core::convert::TryFrom; use core::time::Duration; + use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::chain::keysinterface::KeyMaterial; use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; @@ -706,7 +707,6 @@ mod tests { use crate::offers::parse::{ParseError, SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; - use crate::onion_message::{BlindedHop, BlindedPath}; use crate::util::ser::{BigSize, Writeable}; use crate::util::string::PrintableString; diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 43664079d..8ded4a66e 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -13,11 +13,11 @@ use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; use core::convert::Infallible; use core::time::Duration; +use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::chain::keysinterface::EntropySource; use crate::ln::PaymentHash; use crate::ln::features::BlindedHopFeatures; use crate::offers::invoice::BlindedPayInfo; -use crate::onion_message::{BlindedHop, BlindedPath}; pub(super) fn payer_keys() -> KeyPair { let secp_ctx = Secp256k1::new(); diff --git a/lightning/src/onion_message/blinded_path.rs b/lightning/src/onion_message/blinded_path.rs deleted file mode 100644 index 946e5e7cf..000000000 --- a/lightning/src/onion_message/blinded_path.rs +++ /dev/null @@ -1,231 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! Creating blinded paths and related utilities live here. - -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; - -use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient}; -use super::packet::ControlTlvs; -use super::utils; -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 core::mem; -use core::ops::Deref; -use crate::io::{self, Cursor}; -use crate::prelude::*; - -/// Onion messages can be sent and received to blinded paths, which serve to hide the identity of -/// the recipient. -#[derive(Clone, Debug, PartialEq)] -pub struct BlindedPath { - /// To send to a blinded path, the sender first finds a route to the unblinded - /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion - /// message's next hop and forward it along. - /// - /// [`encrypted_payload`]: BlindedHop::encrypted_payload - pub(crate) introduction_node_id: PublicKey, - /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion - /// message. - /// - /// [`encrypted_payload`]: BlindedHop::encrypted_payload - pub(crate) blinding_point: PublicKey, - /// The hops composing the blinded path. - pub(crate) 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. -#[derive(Clone, Debug, PartialEq)] -pub struct BlindedHop { - /// The blinded node id of this hop in a blinded path. - pub(crate) blinded_node_id: PublicKey, - /// The encrypted payload intended for this hop in a blinded path. - // The node sending to this blinded path will later encode this payload into the onion packet for - // this hop. - pub(crate) encrypted_payload: Vec, -} - -impl BlindedPath { - /// Create a blinded path to be forwarded along `node_pks`. The last node pubkey in `node_pks` - /// will be the destination node. - /// - /// Errors if less than two hops are provided or if `node_pk`(s) are invalid. - // TODO: make all payloads the same size with padding + add dummy hops - pub fn new - (node_pks: &[PublicKey], entropy_source: &ES, secp_ctx: &Secp256k1) -> Result - { - if node_pks.len() < 2 { return Err(()) } - let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); - let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); - let introduction_node_id = node_pks[0]; - - Ok(BlindedPath { - introduction_node_id, - blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), - blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, - }) - } - - // Advance the blinded path by one hop, so make the second hop into the new introduction node. - pub(super) fn advance_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(ForwardTlvs { - mut next_node_id, next_blinding_override, - })}) => { - let mut new_blinding_point = match next_blinding_override { - Some(blinding_point) => blinding_point, - None => { - let blinding_factor = { - let mut sha = Sha256::engine(); - sha.input(&self.blinding_point.serialize()[..]); - sha.input(control_tlvs_ss.as_ref()); - Sha256::from_engine(sha).into_inner() - }; - self.blinding_point.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap()) - .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(()) - } - } -} - -/// Construct blinded hops for the given `unblinded_path`. -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: 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: 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)?; - self.blinding_point.write(w)?; - (self.blinded_hops.len() as u8).write(w)?; - for hop in &self.blinded_hops { - hop.write(w)?; - } - Ok(()) - } -} - -impl Readable for BlindedPath { - fn read(r: &mut R) -> Result { - let introduction_node_id = Readable::read(r)?; - let blinding_point = Readable::read(r)?; - let num_hops: u8 = Readable::read(r)?; - if num_hops == 0 { return Err(DecodeError::InvalidValue) } - let mut blinded_hops: Vec = Vec::with_capacity(num_hops.into()); - for _ in 0..num_hops { - blinded_hops.push(Readable::read(r)?); - } - Ok(BlindedPath { - introduction_node_id, - blinding_point, - blinded_hops, - }) - } -} - -impl_writeable!(BlindedHop, { - blinded_node_id, - 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/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 89582305c..df13aa2cb 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -9,10 +9,11 @@ //! Onion message testing and test utilities live here. +use crate::blinded_path::BlindedPath; use crate::chain::keysinterface::{NodeSigner, Recipient}; use crate::ln::features::InitFeatures; use crate::ln::msgs::{self, DecodeError, OnionMessageHandler}; -use super::{BlindedPath, CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError}; +use super::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError}; use crate::util::ser::{Writeable, Writer}; use crate::util::test_utils; diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index b9269c7cf..72b67dc92 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,16 +15,15 @@ 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::chain::keysinterface::{EntropySource, KeysManager, NodeSigner, Recipient}; use crate::events::OnionMessageProvider; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs::{self, OnionMessageHandler}; use crate::ln::onion_utils; use crate::ln::peer_handler::IgnoringMessageHandler; -use super::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs}; pub use super::packet::{CustomOnionMessageContents, OnionMessageContents}; use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN}; -use super::utils; use crate::util::logger::Logger; use crate::util::ser::Writeable; @@ -43,9 +42,10 @@ use crate::prelude::*; /// # extern crate bitcoin; /// # use bitcoin::hashes::_export::_core::time::Duration; /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +/// # use lightning::blinded_path::BlindedPath; /// # use lightning::chain::keysinterface::KeysManager; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; -/// # use lightning::onion_message::{BlindedPath, CustomOnionMessageContents, Destination, OnionMessageContents, OnionMessenger}; +/// # use lightning::onion_message::{CustomOnionMessageContents, Destination, OnionMessageContents, OnionMessenger}; /// # use lightning::util::logger::{Logger, Record}; /// # use lightning::util::ser::{Writeable, Writer}; /// # use lightning::io; diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index e735b428e..713b83c62 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -18,16 +18,13 @@ //! information on its usage. //! //! [offers]: -//! [blinded paths]: crate::onion_message::BlindedPath +//! [blinded paths]: crate::blinded_path::BlindedPath -mod blinded_path; mod messenger; mod packet; -mod utils; #[cfg(test)] mod functional_tests; // Re-export structs so they can be imported with just the `onion_message::` module prefix. -pub use self::blinded_path::{BlindedPath, BlindedHop}; pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, Destination, OnionMessageContents, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; -pub(crate) use self::packet::Packet; +pub(crate) use self::packet::{ControlTlvs, Packet}; diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index d22ff0682..2fb2407db 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -12,9 +12,9 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::ecdh::SharedSecret; +use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; -use super::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs}; use super::messenger::CustomOnionMessageHandler; use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; use crate::util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer}; @@ -149,7 +149,7 @@ 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 [`super::blinded_path::ForwardTlvs`] for more info. + /// them into an intermediate Vec. See [`crate::blinded_path::ForwardTlvs`] for more info. Unblinded(ForwardTlvs), } @@ -157,7 +157,7 @@ pub(super) enum ForwardControlTlvs { pub(super) enum ReceiveControlTlvs { /// See [`ForwardControlTlvs::Blinded`]. Blinded(Vec), - /// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_path::ReceiveTlvs`]. + /// See [`ForwardControlTlvs::Unblinded`] and [`crate::blinded_path::ReceiveTlvs`]. Unblinded(ReceiveTlvs), } @@ -255,7 +255,7 @@ impl ReadableArgs<(SharedSecret, &H)> for Payload< /// When reading a packet off the wire, we don't know a priori whether the packet is to be forwarded /// or received. Thus we read a ControlTlvs rather than reading a ForwardControlTlvs or /// ReceiveControlTlvs directly. -pub(super) enum ControlTlvs { +pub(crate) enum ControlTlvs { /// This onion message is intended to be forwarded. Forward(ForwardTlvs), /// This onion message is intended to be received. diff --git a/lightning/src/onion_message/utils.rs b/lightning/src/onion_message/utils.rs deleted file mode 100644 index ae9e0a7f6..000000000 --- a/lightning/src/onion_message/utils.rs +++ /dev/null @@ -1,98 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! Onion message utility methods live here. - -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::hmac::{Hmac, HmacEngine}; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey, Scalar}; -use bitcoin::secp256k1::ecdh::SharedSecret; - -use crate::ln::onion_utils; -use super::blinded_path::BlindedPath; -use super::messenger::Destination; - -use crate::prelude::*; - -// TODO: DRY with onion_utils::construct_onion_keys_callback -#[inline] -pub(super) fn construct_keys_callback, Option>)>( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], destination: Option, - 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(); - let mut onion_packet_pubkey = msg_blinding_point.clone(); - - macro_rules! build_keys { - ($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 hop_pk_blinding_factor = { - let mut hmac = HmacEngine::::new(b"blinded_node_id"); - hmac.input(encrypted_data_ss.as_ref()); - Hmac::from_engine(hmac).into_inner() - }; - $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())? - }; - let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv); - - 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, $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(); - sha.input(&msg_blinding_point.serialize()[..]); - sha.input(encrypted_data_ss.as_ref()); - Sha256::from_engine(sha).into_inner() - }; - - msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?; - msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv); - - let onion_packet_pubkey_blinding_factor = { - let mut sha = Sha256::engine(); - sha.input(&onion_packet_pubkey.serialize()[..]); - sha.input(onion_packet_ss.as_ref()); - Sha256::from_engine(sha).into_inner() - }; - onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?; - onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv); - }; - } - - for pk in unblinded_path { - build_keys_in_loop!(*pk, false, None); - } - if let Some(dest) = destination { - match dest { - Destination::Node(pk) => { - build_keys!(pk, false, None); - }, - Destination::BlindedPath(BlindedPath { blinded_hops, .. }) => { - for hop in blinded_hops { - build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload)); - } - }, - } - } - Ok(()) -}