use crate::routing::gossip::NetworkUpdate;
use crate::routing::router::{BlindedTail, Path, RouteHop};
use crate::sign::NodeSigner;
-use crate::util::chacha20::{ChaCha20, ChaChaReader};
+use crate::crypto::chacha20::ChaCha20;
+use crate::crypto::streams::ChaChaReader;
use crate::util::errors::{self, APIError};
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
use crate::util::logger::Logger;
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,
+ sender_intended_htlc_amt_msat: *final_value_msat,
total_msat,
- outgoing_cltv_value: cltv,
+ cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
intro_node_blinding_point: blinding_point.take(),
});
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,
+ sender_intended_htlc_amt_msat: value_msat,
+ cltv_expiry_height: cltv,
});
}
} else {
/// the hops can be of variable length.
pub(crate) const ONION_DATA_LEN: usize = 20*65;
+pub(super) const INVALID_ONION_BLINDING: u16 = 0x8000 | 0x4000 | 24;
+
#[inline]
fn shift_slice_right(arr: &mut [u8], amt: usize) {
for i in (amt..arr.len()).rev() {
let mut pos = 0;
for (i, (payload, keys)) in payloads.iter().zip(onion_keys.iter()).enumerate() {
- if i == payloads.len() - 1 { break; }
-
let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
for _ in 0..(packet_data.len() - pos) { // TODO: Batch this.
let mut dummy = [0; 1];
return Err(());
}
+ if i == payloads.len() - 1 { break; }
+
res.resize(pos, 0u8);
chacha.process_in_place(&mut res);
}
pub(crate) network_update: Option<NetworkUpdate>,
pub(crate) short_channel_id: Option<u64>,
pub(crate) payment_failed_permanently: bool,
+ pub(crate) failed_within_blinded_path: bool,
#[cfg(test)]
pub(crate) onion_error_code: Option<u16>,
#[cfg(test)]
pub(crate) onion_error_data: Option<Vec<u8>>,
}
+/// Note that we always decrypt `packet` in-place here even if the deserialization into
+/// [`msgs::DecodedOnionErrorPacket`] ultimately fails.
+fn decrypt_onion_error_packet(
+ packet: &mut Vec<u8>, shared_secret: SharedSecret
+) -> Result<msgs::DecodedOnionErrorPacket, msgs::DecodeError> {
+ let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
+ let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
+ chacha.process_in_place(packet);
+ msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(packet))
+}
+
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
/// OutboundRoute).
#[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>
+ secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec<u8>
) -> 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, ..
network_update: Option<NetworkUpdate>,
short_channel_id: Option<u64>,
payment_failed_permanently: bool,
+ failed_within_blinded_path: bool,
}
let mut res: Option<FailureLearnings> = None;
let mut htlc_msat = *first_hop_htlc_msat;
error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
error_packet_ret = Some(vec![0; 32]);
res = Some(FailureLearnings {
- network_update: None, short_channel_id: None, payment_failed_permanently: false
+ network_update: None, short_channel_id: None, payment_failed_permanently: false,
+ failed_within_blinded_path: true,
});
return
},
Some(hop) => hop,
None => {
// The failing hop is within a multi-hop blinded path.
- error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
- error_packet_ret = Some(vec![0; 32]);
+ #[cfg(not(test))] {
+ error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
+ error_packet_ret = Some(vec![0; 32]);
+ }
+ #[cfg(test)] {
+ // Actually parse the onion error data in tests so we can check that blinded hops fail
+ // back correctly.
+ let err_packet = decrypt_onion_error_packet(
+ &mut encrypted_packet, shared_secret
+ ).unwrap();
+ error_code_ret =
+ Some(u16::from_be_bytes(err_packet.failuremsg.get(0..2).unwrap().try_into().unwrap()));
+ error_packet_ret = Some(err_packet.failuremsg[2..].to_vec());
+ }
+
res = Some(FailureLearnings {
- network_update: None, short_channel_id: None, payment_failed_permanently: false
+ network_update: None, short_channel_id: None, payment_failed_permanently: false,
+ failed_within_blinded_path: true,
});
return
}
let amt_to_forward = htlc_msat - route_hop.fee_msat;
htlc_msat = amt_to_forward;
- let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
-
- let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len());
- decryption_tmp.resize(packet_decrypted.len(), 0);
- let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
- chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
- packet_decrypted = decryption_tmp;
-
- let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
+ let err_packet = match decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) {
Ok(p) => p,
Err(_) => return
};
});
let short_channel_id = Some(route_hop.short_channel_id);
res = Some(FailureLearnings {
- network_update, short_channel_id, payment_failed_permanently: is_from_final_node
+ network_update, short_channel_id, payment_failed_permanently: is_from_final_node,
+ failed_within_blinded_path: false
});
return
}
res = Some(FailureLearnings {
network_update, short_channel_id,
- payment_failed_permanently: error_code & PERM == PERM && is_from_final_node
+ payment_failed_permanently: error_code & PERM == PERM && is_from_final_node,
+ failed_within_blinded_path: false
});
let (description, title) = errors::get_onion_error_description(error_code);
}
}).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
if let Some(FailureLearnings {
- network_update, short_channel_id, payment_failed_permanently
+ network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path
}) = res {
DecodedOnionFailure {
- network_update, short_channel_id, payment_failed_permanently,
+ network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path,
#[cfg(test)]
onion_error_code: error_code_ret,
#[cfg(test)]
// payment not retryable only when garbage is from the final node
DecodedOnionFailure {
network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node,
+ failed_within_blinded_path: false,
#[cfg(test)]
onion_error_code: None,
#[cfg(test)]
}
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
+#[cfg_attr(test, derive(PartialEq))]
pub(super) struct HTLCFailReason(HTLCFailReasonRepr);
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
+#[cfg_attr(test, derive(PartialEq))]
enum HTLCFailReasonRepr {
LightningError {
err: msgs::OnionErrorPacket,
network_update: None,
payment_failed_permanently: false,
short_channel_id: Some(path.hops[0].short_channel_id),
+ failed_within_blinded_path: false,
#[cfg(test)]
onion_error_code: Some(*failure_code),
#[cfg(test)]
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,
+ blinding_point: Option<PublicKey>, 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) {
+ match decode_next_hop(
+ shared_secret, hop_data, hmac_bytes, Some(payment_hash), (blinding_point, 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 {
if hmac == [0; 32] {
#[cfg(test)]
{
- // In tests, make sure that the initial onion packet data is, at least, non-0.
- // We could do some fancy randomness test here, but, ehh, whatever.
- // This checks for the issue where you can calculate the path length given the
- // onion data as all the path entries that the originator sent will be here
- // as-is (and were originally 0s).
- // Of course reverse path calculation is still pretty easy given naive routing
- // algorithms, but this fixes the most-obvious case.
- let mut next_bytes = [0; 32];
- chacha_stream.read_exact(&mut next_bytes).unwrap();
- assert_ne!(next_bytes[..], [0; 32][..]);
- chacha_stream.read_exact(&mut next_bytes).unwrap();
- assert_ne!(next_bytes[..], [0; 32][..]);
+ if chacha_stream.read.position() < hop_data.len() as u64 - 64 {
+ // In tests, make sure that the initial onion packet data is, at least, non-0.
+ // We could do some fancy randomness test here, but, ehh, whatever.
+ // This checks for the issue where you can calculate the path length given the
+ // onion data as all the path entries that the originator sent will be here
+ // as-is (and were originally 0s).
+ // Of course reverse path calculation is still pretty easy given naive routing
+ // algorithms, but this fixes the most-obvious case.
+ let mut next_bytes = [0; 32];
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ chacha_stream.read_exact(&mut next_bytes).unwrap();
+ assert_ne!(next_bytes[..], [0; 32][..]);
+ }
}
return Ok((msg, None)); // We are the final destination for this packet
} else {