X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonion_utils.rs;h=fab73cf73ec4320cfbe499a29d947ce168b5ad41;hb=refs%2Fheads%2Fupstream%2Fmain;hp=53ef0729aa171ba56ff33ba73a55cb4ed922df83;hpb=658e2c0a48b50adb522cea30e05d88e824d45bd3;p=rust-lightning diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 53ef0729..fab73cf7 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -7,14 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. +use crate::blinded_path::BlindedHop; use crate::crypto::chacha20::ChaCha20; use crate::crypto::streams::ChaChaReader; +use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields}; +use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::ln::msgs; -use crate::ln::wire::Encode; -use crate::ln::{PaymentHash, PaymentPreimage}; +use crate::ln::types::{PaymentHash, PaymentPreimage}; use crate::routing::gossip::NetworkUpdate; -use crate::routing::router::{BlindedTail, Path, RouteHop}; +use crate::routing::router::{Path, RouteHop, RouteParameters}; use crate::sign::NodeSigner; use crate::util::errors::{self, APIError}; use crate::util::logger::Logger; @@ -30,10 +32,11 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; use crate::io::{Cursor, Read}; -use crate::prelude::*; -use core::convert::{AsMut, TryInto}; use core::ops::Deref; +#[allow(unused_imports)] +use crate::prelude::*; + pub(crate) struct OnionKeys { #[cfg(test)] pub(crate) shared_secret: SharedSecret, @@ -173,18 +176,60 @@ pub(super) fn construct_onion_keys( } /// returns the hop data, as well as the first-hop value_msat and CLTV value we should send. -pub(super) fn build_onion_payloads( - path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields, +pub(super) fn build_onion_payloads<'a>( + path: &'a Path, total_msat: u64, recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32, keysend_preimage: &Option, -) -> Result<(Vec, u64, u32), APIError> { - let mut cur_value_msat = 0u64; - let mut cur_cltv = starting_htlc_offset; - let mut last_short_channel_id = 0; +) -> Result<(Vec>, u64, u32), APIError> { let mut res: Vec = Vec::with_capacity( path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()), ); + let blinded_tail_with_hop_iter = path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter { + hops: bt.hops.iter(), + blinding_point: bt.blinding_point, + final_value_msat: bt.final_value_msat, + excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta, + }); + + let (value_msat, cltv) = build_onion_payloads_callback( + path.hops.iter(), + blinded_tail_with_hop_iter, + total_msat, + recipient_onion, + starting_htlc_offset, + keysend_preimage, + |action, payload| match action { + PayloadCallbackAction::PushBack => res.push(payload), + PayloadCallbackAction::PushFront => res.insert(0, payload), + }, + )?; + Ok((res, value_msat, cltv)) +} - for (idx, hop) in path.hops.iter().rev().enumerate() { +struct BlindedTailHopIter<'a, I: Iterator> { + hops: I, + blinding_point: PublicKey, + final_value_msat: u64, + excess_final_cltv_expiry_delta: u32, +} +enum PayloadCallbackAction { + PushBack, + PushFront, +} +fn build_onion_payloads_callback<'a, H, B, F>( + hops: H, mut blinded_tail: Option>, total_msat: u64, + recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32, + keysend_preimage: &Option, mut callback: F, +) -> Result<(u64, u32), APIError> +where + H: DoubleEndedIterator, + B: ExactSizeIterator, + F: FnMut(PayloadCallbackAction, msgs::OutboundOnionPayload<'a>), +{ + let mut cur_value_msat = 0u64; + let mut cur_cltv = starting_htlc_offset; + let mut last_short_channel_id = 0; + + for (idx, hop) in hops.rev().enumerate() { // First hop gets special values so that it can check, on receipt, that everything is // exactly as it should be (and the next hop isn't trying to probe to find out if we're // the intended recipient). @@ -195,46 +240,55 @@ pub(super) fn build_onion_payloads( cur_cltv }; if idx == 0 { - if let Some(BlindedTail { + if let Some(BlindedTailHopIter { blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, .. - }) = &path.blinded_tail + }) = blinded_tail.take() { - let mut blinding_point = Some(*blinding_point); - for (i, blinded_hop) in hops.iter().enumerate() { - if i == hops.len() - 1 { + let mut blinding_point = Some(blinding_point); + let hops_len = hops.len(); + for (i, blinded_hop) in hops.enumerate() { + if i == hops_len - 1 { cur_value_msat += final_value_msat; - res.push(msgs::OutboundOnionPayload::BlindedReceive { - sender_intended_htlc_amt_msat: *final_value_msat, - total_msat, - cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta, - encrypted_tlvs: blinded_hop.encrypted_payload.clone(), - intro_node_blinding_point: blinding_point.take(), - keysend_preimage: *keysend_preimage, - }); + callback( + PayloadCallbackAction::PushBack, + msgs::OutboundOnionPayload::BlindedReceive { + sender_intended_htlc_amt_msat: final_value_msat, + total_msat, + cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta, + encrypted_tlvs: &blinded_hop.encrypted_payload, + intro_node_blinding_point: blinding_point.take(), + keysend_preimage: *keysend_preimage, + custom_tlvs: &recipient_onion.custom_tlvs, + }, + ); } else { - res.push(msgs::OutboundOnionPayload::BlindedForward { - encrypted_tlvs: blinded_hop.encrypted_payload.clone(), - intro_node_blinding_point: blinding_point.take(), - }); + callback( + PayloadCallbackAction::PushBack, + msgs::OutboundOnionPayload::BlindedForward { + encrypted_tlvs: &blinded_hop.encrypted_payload, + intro_node_blinding_point: blinding_point.take(), + }, + ); } } } else { - res.push(msgs::OutboundOnionPayload::Receive { - payment_data: if let Some(secret) = recipient_onion.payment_secret.take() { - Some(msgs::FinalOnionHopData { payment_secret: secret, total_msat }) - } else { - None + callback( + PayloadCallbackAction::PushBack, + msgs::OutboundOnionPayload::Receive { + payment_data: recipient_onion.payment_secret.map(|payment_secret| { + msgs::FinalOnionHopData { payment_secret, total_msat } + }), + payment_metadata: recipient_onion.payment_metadata.as_ref(), + keysend_preimage: *keysend_preimage, + custom_tlvs: &recipient_onion.custom_tlvs, + sender_intended_htlc_amt_msat: value_msat, + cltv_expiry_height: cltv, }, - payment_metadata: recipient_onion.payment_metadata.take(), - keysend_preimage: *keysend_preimage, - custom_tlvs: recipient_onion.custom_tlvs.clone(), - sender_intended_htlc_amt_msat: value_msat, - cltv_expiry_height: cltv, - }); + ); } } else { let payload = msgs::OutboundOnionPayload::Forward { @@ -242,7 +296,7 @@ pub(super) fn build_onion_payloads( amt_to_forward: value_msat, outgoing_cltv_value: cltv, }; - res.insert(0, payload); + callback(PayloadCallbackAction::PushFront, payload); } cur_value_msat += hop.fee_msat; if cur_value_msat >= 21000000 * 100000000 * 1000 { @@ -254,7 +308,78 @@ pub(super) fn build_onion_payloads( } last_short_channel_id = hop.short_channel_id; } - Ok((res, cur_value_msat, cur_cltv)) + Ok((cur_value_msat, cur_cltv)) +} + +pub(crate) const MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY: u64 = 100_000_000; + +pub(crate) fn set_max_path_length( + route_params: &mut RouteParameters, recipient_onion: &RecipientOnionFields, + keysend_preimage: Option, best_block_height: u32, +) -> Result<(), ()> { + const PAYLOAD_HMAC_LEN: usize = 32; + let unblinded_intermed_payload_len = msgs::OutboundOnionPayload::Forward { + short_channel_id: 42, + amt_to_forward: TOTAL_BITCOIN_SUPPLY_SATOSHIS, + outgoing_cltv_value: route_params.payment_params.max_total_cltv_expiry_delta, + } + .serialized_length() + .saturating_add(PAYLOAD_HMAC_LEN); + + const OVERPAY_ESTIMATE_MULTIPLER: u64 = 3; + let final_value_msat_with_overpay_buffer = core::cmp::max( + route_params.final_value_msat.saturating_mul(OVERPAY_ESTIMATE_MULTIPLER), + MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, + ); + + let blinded_tail_opt = route_params + .payment_params + .payee + .blinded_route_hints() + .iter() + .map(|(_, path)| path) + .max_by_key(|path| path.serialized_length()) + .map(|largest_path| BlindedTailHopIter { + hops: largest_path.blinded_hops.iter(), + blinding_point: largest_path.blinding_point, + final_value_msat: final_value_msat_with_overpay_buffer, + excess_final_cltv_expiry_delta: 0, + }); + + let unblinded_route_hop = RouteHop { + pubkey: PublicKey::from_slice(&[2; 33]).unwrap(), + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: final_value_msat_with_overpay_buffer, + cltv_expiry_delta: route_params.payment_params.max_total_cltv_expiry_delta, + maybe_announced_channel: false, + }; + let mut num_reserved_bytes: usize = 0; + let build_payloads_res = build_onion_payloads_callback( + core::iter::once(&unblinded_route_hop), + blinded_tail_opt, + final_value_msat_with_overpay_buffer, + &recipient_onion, + best_block_height, + &keysend_preimage, + |_, payload| { + num_reserved_bytes = num_reserved_bytes + .saturating_add(payload.serialized_length()) + .saturating_add(PAYLOAD_HMAC_LEN); + }, + ); + debug_assert!(build_payloads_res.is_ok()); + + let max_path_length = 1300usize + .checked_sub(num_reserved_bytes) + .map(|p| p / unblinded_intermed_payload_len) + .and_then(|l| u8::try_from(l.saturating_add(1)).ok()) + .ok_or(())?; + + route_params.payment_params.max_path_length = + core::cmp::min(max_path_length, route_params.payment_params.max_path_length); + Ok(()) } /// Length of the onion data packet. Before TLV-based onions this was 20 65-byte hops, though now @@ -291,6 +416,24 @@ pub(super) fn construct_onion_packet( ) } +#[allow(unused)] +pub(super) fn construct_trampoline_onion_packet( + payloads: Vec, onion_keys: Vec, + prng_seed: [u8; 32], associated_data: &PaymentHash, length: u16, +) -> Result { + let mut packet_data = vec![0u8; length as usize]; + + let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); + chacha.process(&vec![0u8; length as usize], &mut packet_data); + + construct_onion_packet_with_init_noise::<_, _>( + payloads, + onion_keys, + packet_data, + Some(associated_data), + ) +} + #[cfg(test)] /// Used in testing to write bogus `BogusOnionHopData` as well as `RawOnionHopData`, which is /// otherwise not representable in `msgs::OnionHopData`. @@ -662,106 +805,16 @@ where { let update_len = u16::from_be_bytes(update_len_slice.try_into().expect("len is 2")) as usize; - if let Some(mut update_slice) = err_packet + if err_packet .failuremsg .get(debug_field_size + 4..debug_field_size + 4 + update_len) + .is_some() { - // Historically, the BOLTs were unclear if the message type - // bytes should be included here or not. The BOLTs have now - // been updated to indicate that they *are* included, but many - // nodes still send messages without the type bytes, so we - // support both here. - // TODO: Switch to hard require the type prefix, as the current - // permissiveness introduces the (although small) possibility - // that we fail to decode legitimate channel updates that - // happen to start with ChannelUpdate::TYPE, i.e., [0x01, 0x02]. - if update_slice.len() > 2 - && update_slice[0..2] == msgs::ChannelUpdate::TYPE.to_be_bytes() - { - update_slice = &update_slice[2..]; - } else { - log_trace!(logger, "Failure provided features a channel update without type prefix. Deprecated, but allowing for now."); - } - let update_opt = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice)); - if update_opt.is_ok() || update_slice.is_empty() { - // if channel_update should NOT have caused the failure: - // MAY treat the channel_update as invalid. - let is_chan_update_invalid = match error_code & 0xff { - 7 => false, - 11 => { - update_opt.is_ok() - && amt_to_forward - > update_opt.as_ref().unwrap().contents.htlc_minimum_msat - }, - 12 => { - update_opt.is_ok() - && amt_to_forward - .checked_mul( - update_opt - .as_ref() - .unwrap() - .contents - .fee_proportional_millionths as u64, - ) - .map(|prop_fee| prop_fee / 1_000_000) - .and_then(|prop_fee| { - prop_fee.checked_add( - update_opt.as_ref().unwrap().contents.fee_base_msat - as u64, - ) - }) - .map(|fee_msats| route_hop.fee_msat >= fee_msats) - .unwrap_or(false) - }, - 13 => { - update_opt.is_ok() - && route_hop.cltv_expiry_delta as u16 - >= update_opt.as_ref().unwrap().contents.cltv_expiry_delta - }, - 14 => false, // expiry_too_soon; always valid? - 20 => update_opt.as_ref().unwrap().contents.flags & 2 == 0, - _ => false, // unknown error code; take channel_update as valid - }; - if is_chan_update_invalid { - // This probably indicates the node which forwarded - // to the node in question corrupted something. - network_update = Some(NetworkUpdate::ChannelFailure { - short_channel_id: route_hop.short_channel_id, - is_permanent: true, - }); - } else { - if let Ok(chan_update) = update_opt { - // Make sure the ChannelUpdate contains the expected - // short channel id. - if failing_route_hop.short_channel_id - == chan_update.contents.short_channel_id - { - short_channel_id = Some(failing_route_hop.short_channel_id); - } else { - log_info!(logger, "Node provided a channel_update for which it was not authoritative, ignoring."); - } - network_update = - Some(NetworkUpdate::ChannelUpdateMessage { msg: chan_update }) - } else { - // The node in question intentionally encoded a 0-length channel update. This is - // likely due to https://github.com/ElementsProject/lightning/issues/6200. - short_channel_id = Some(failing_route_hop.short_channel_id); - network_update = Some(NetworkUpdate::ChannelFailure { - short_channel_id: failing_route_hop.short_channel_id, - is_permanent: false, - }); - } - }; - } else { - // If the channel_update had a non-zero length (i.e. was - // present) but we couldn't read it, treat it as a total - // node failure. - log_info!( - logger, - "Failed to read a channel_update of len {} in an onion", - update_slice.len() - ); - } + network_update = Some(NetworkUpdate::ChannelFailure { + short_channel_id: failing_route_hop.short_channel_id, + is_permanent: false, + }); + short_channel_id = Some(failing_route_hop.short_channel_id); } } if network_update.is_none() { @@ -1054,6 +1107,21 @@ pub(crate) enum Hop { }, } +impl Hop { + pub(crate) fn is_intro_node_blinded_forward(&self) -> bool { + match self { + Self::Forward { + next_hop_data: + msgs::InboundOnionPayload::BlindedForward { + intro_node_blinding_point: Some(_), .. + }, + .. + } => true, + _ => false, + } + } +} + /// Error returned when we fail to decode the onion packet. #[derive(Debug)] pub(crate) enum OnionDecodeErr { @@ -1089,7 +1157,7 @@ where /// `cur_block_height` should be set to the best known block height + 1. pub fn create_payment_onion( secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, total_msat: u64, - recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash, + recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash, keysend_preimage: &Option, prng_seed: [u8; 32], ) -> Result<(msgs::OnionPacket, u64, u32), APIError> { let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| { @@ -1205,11 +1273,13 @@ mod tests { use crate::io; use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::ln::msgs; - use crate::ln::PaymentHash; - use crate::prelude::*; + use crate::ln::types::PaymentHash; use crate::routing::router::{Path, Route, RouteHop}; use crate::util::ser::{VecWriter, Writeable, Writer}; + #[allow(unused_imports)] + use crate::prelude::*; + use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey};