use crate::ln::msgs;
use crate::ln::wire::Encode;
use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::{Path, RouteHop};
+use crate::routing::router::{BlindedTail, Path, RouteHop};
+use crate::sign::NodeSigner;
use crate::util::chacha20::{ChaCha20, ChaChaReader};
use crate::util::errors::{self, APIError};
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
Hmac::from_engine(hmac).into_inner()
}
-pub(crate) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
+/// Calculates a pubkey for the next hop, such as the next hop's packet pubkey or blinding point.
+pub(crate) fn next_hop_pubkey<T: secp256k1::Signing + secp256k1::Verification>(
+ secp_ctx: &Secp256k1<T>, curr_pubkey: PublicKey, shared_secret: &[u8]
+) -> Result<PublicKey, secp256k1::Error> {
let blinding_factor = {
let mut sha = Sha256::engine();
- sha.input(&packet_pubkey.serialize()[..]);
- sha.input(packet_shared_secret);
+ sha.input(&curr_pubkey.serialize()[..]);
+ sha.input(shared_secret);
Sha256::from_engine(sha).into_inner()
};
- packet_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
+ curr_pubkey.mul_tweak(secp_ctx, &Scalar::from_be_bytes(blinding_factor).unwrap())
}
// can only fail if an intermediary hop has an invalid public key or session_priv is invalid
let mut cur_value_msat = 0u64;
let mut cur_cltv = starting_htlc_offset;
let mut last_short_channel_id = 0;
- let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(path.hops.len());
+ let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
+ path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len())
+ );
for (idx, hop) in path.hops.iter().rev().enumerate() {
// First hop gets special values so that it can check, on receipt, that everything is
// the intended recipient).
let value_msat = if cur_value_msat == 0 { hop.fee_msat } else { cur_value_msat };
let cltv = if cur_cltv == starting_htlc_offset { hop.cltv_expiry_delta + starting_htlc_offset } else { cur_cltv };
- res.insert(0, if idx == 0 {
- msgs::OutboundOnionPayload::Receive {
- payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
- Some(msgs::FinalOnionHopData {
- payment_secret: secret,
- total_msat,
- })
- } else { None },
- payment_metadata: recipient_onion.payment_metadata.take(),
- keysend_preimage: *keysend_preimage,
- custom_tlvs: recipient_onion.custom_tlvs.clone(),
- amt_msat: value_msat,
- outgoing_cltv_value: cltv,
+ if idx == 0 {
+ if let Some(BlindedTail {
+ blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, ..
+ }) = &path.blinded_tail {
+ let mut blinding_point = Some(*blinding_point);
+ for (i, blinded_hop) in hops.iter().enumerate() {
+ if i == hops.len() - 1 {
+ cur_value_msat += final_value_msat;
+ cur_cltv += excess_final_cltv_expiry_delta;
+ res.push(msgs::OutboundOnionPayload::BlindedReceive {
+ amt_msat: *final_value_msat,
+ total_msat,
+ outgoing_cltv_value: cltv,
+ encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+ intro_node_blinding_point: blinding_point.take(),
+ });
+ } else {
+ res.push(msgs::OutboundOnionPayload::BlindedForward {
+ encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
+ 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 },
+ payment_metadata: recipient_onion.payment_metadata.take(),
+ keysend_preimage: *keysend_preimage,
+ custom_tlvs: recipient_onion.custom_tlvs.clone(),
+ amt_msat: value_msat,
+ outgoing_cltv_value: cltv,
+ });
}
} else {
- msgs::OutboundOnionPayload::Forward {
+ res.insert(0, msgs::OutboundOnionPayload::Forward {
short_channel_id: last_short_channel_id,
amt_to_forward: value_msat,
outgoing_cltv_value: cltv,
- }
- });
+ });
+ }
cur_value_msat += hop.fee_msat;
if cur_value_msat >= 21000000 * 100000000 * 1000 {
return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()});
encrypt_failure_packet(shared_secret, &failure_packet.encode()[..])
}
+pub(crate) struct DecodedOnionFailure {
+ pub(crate) network_update: Option<NetworkUpdate>,
+ pub(crate) short_channel_id: Option<u64>,
+ pub(crate) payment_retryable: bool,
+ #[cfg(test)]
+ pub(crate) onion_error_code: Option<u16>,
+ #[cfg(test)]
+ pub(crate) onion_error_data: Option<Vec<u8>>,
+}
+
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
/// OutboundRoute).
-/// Returns update, a boolean indicating that the payment itself failed, the short channel id of
-/// the responsible channel, and the error code.
#[inline]
pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>
-) -> (Option<NetworkUpdate>, Option<u64>, bool, Option<u16>, Option<Vec<u8>>)
-where L::Target: Logger {
+) -> DecodedOnionFailure where L::Target: Logger {
let (path, session_priv, first_hop_htlc_msat) = if let &HTLCSource::OutboundRoute {
ref path, ref session_priv, ref first_hop_htlc_msat, ..
} = htlc_source {
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.
network_update = Some(NetworkUpdate::ChannelFailure {
short_channel_id: route_hop.short_channel_id,
is_permanent: false,
log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description);
}
}).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
- if let Some((channel_update, short_channel_id, payment_retryable)) = res {
- (channel_update, short_channel_id, payment_retryable, error_code_ret, error_packet_ret)
+ if let Some((network_update, short_channel_id, payment_retryable)) = res {
+ DecodedOnionFailure {
+ network_update, short_channel_id, payment_retryable,
+ #[cfg(test)]
+ onion_error_code: error_code_ret,
+ #[cfg(test)]
+ onion_error_data: error_packet_ret
+ }
} else {
// only not set either packet unparseable or hmac does not match with any
// payment not retryable only when garbage is from the final node
- (None, None, !is_from_final_node, None, None)
+ DecodedOnionFailure {
+ network_update: None, short_channel_id: None, payment_retryable: !is_from_final_node,
+ #[cfg(test)]
+ onion_error_code: None,
+ #[cfg(test)]
+ onion_error_data: None
+ }
}
}
pub(super) fn decode_onion_failure<T: secp256k1::Signing, L: Deref>(
&self, secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource
- ) -> (Option<NetworkUpdate>, Option<u64>, bool, Option<u16>, Option<Vec<u8>>)
- where L::Target: Logger {
+ ) -> DecodedOnionFailure where L::Target: Logger {
match self.0 {
HTLCFailReasonRepr::LightningError { ref err } => {
process_onion_failure(secp_ctx, logger, &htlc_source, err.data.clone())
},
+ #[allow(unused)]
HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
// we get a fail_malformed_htlc from the first hop
// TODO: We'd like to generate a NetworkUpdate for temporary
// generally ignores its view of our own channels as we provide them via
// ChannelDetails.
if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source {
- (None, Some(path.hops[0].short_channel_id), true, Some(*failure_code), Some(data.clone()))
+ DecodedOnionFailure {
+ network_update: None,
+ payment_retryable: true,
+ short_channel_id: Some(path.hops[0].short_channel_id),
+ #[cfg(test)]
+ onion_error_code: Some(*failure_code),
+ #[cfg(test)]
+ onion_error_data: Some(data.clone()),
+ }
} else { unreachable!(); }
}
}
},
}
-pub(crate) fn decode_next_payment_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result<Hop, OnionDecodeErr> {
- match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), ()) {
+pub(crate) fn decode_next_payment_hop<NS: Deref>(
+ shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
+ node_signer: &NS,
+) -> Result<Hop, OnionDecodeErr> where NS::Target: NodeSigner {
+ match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) {
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
Ok(Hop::Forward {
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
+ short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()).unwrap();