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
#[inline]
-pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop, usize)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {
+pub(super) fn construct_onion_keys_callback<T, FType>(
+ secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, mut callback: FType
+) -> Result<(), secp256k1::Error>
+where
+ T: secp256k1::Signing,
+ FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&RouteHop>, usize)
+{
let mut blinded_priv = session_priv.clone();
let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
- for (idx, hop) in path.iter().enumerate() {
- let shared_secret = SharedSecret::new(&hop.pubkey, &blinded_priv);
+ let unblinded_hops_iter = path.hops.iter().map(|h| (&h.pubkey, Some(h)));
+ let blinded_pks_iter = path.blinded_tail.as_ref()
+ .map(|t| t.hops.iter()).unwrap_or([].iter())
+ .skip(1) // Skip the intro node because it's included in the unblinded hops
+ .map(|h| (&h.blinded_node_id, None));
+ for (idx, (pubkey, route_hop_opt)) in unblinded_hops_iter.chain(blinded_pks_iter).enumerate() {
+ let shared_secret = SharedSecret::new(pubkey, &blinded_priv);
let mut sha = Sha256::engine();
sha.input(&blinded_pub.serialize()[..]);
blinded_priv = blinded_priv.mul_tweak(&Scalar::from_be_bytes(blinding_factor).unwrap())?;
blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv);
- callback(shared_secret, blinding_factor, ephemeral_pubkey, hop, idx);
+ callback(shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx);
}
Ok(())
pub(super) fn construct_onion_keys<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey) -> Result<Vec<OnionKeys>, secp256k1::Error> {
let mut res = Vec::with_capacity(path.hops.len());
- construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| {
+ construct_onion_keys_callback(secp_ctx, &path, session_priv,
+ |shared_secret, _blinding_factor, ephemeral_pubkey, _, _|
+ {
let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref());
res.push(OnionKeys {
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 {
let mut error_packet_ret = None;
let mut is_from_final_node = false;
+ const BADONION: u16 = 0x8000;
+ const PERM: u16 = 0x4000;
+ const NODE: u16 = 0x2000;
+ const UPDATE: u16 = 0x1000;
+
// Handle packed channel/node updates for passing back for the route handler
- construct_onion_keys_callback(secp_ctx, &path.hops, session_priv, |shared_secret, _, _, route_hop, route_hop_idx| {
+ construct_onion_keys_callback(secp_ctx, &path, session_priv,
+ |shared_secret, _, _, route_hop_opt, route_hop_idx|
+ {
if res.is_some() { return; }
+ let route_hop = match route_hop_opt {
+ Some(hop) => hop,
+ None => {
+ // Got an error from within a blinded route.
+ error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
+ error_packet_ret = Some(vec![0; 32]);
+ is_from_final_node = false;
+ return
+ },
+ };
+
let amt_to_forward = htlc_msat - route_hop.fee_msat;
htlc_msat = amt_to_forward;
return
}
};
- const BADONION: u16 = 0x8000;
- const PERM: u16 = 0x4000;
- const NODE: u16 = 0x2000;
- const UPDATE: u16 = 0x1000;
let error_code = u16::from_be_bytes(error_code_slice.try_into().expect("len is 2"));
error_code_ret = Some(error_code);
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!(); }
}
}
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // 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();